Building Custom Bitnami Images: A Guide for Self-Hosted Container Images

The container ecosystem experienced a shift when Broadcom acquired VMware, subsequently implementing monetization strategies across the Bitnami portfolio. As detailed in my previous analysis of Broadcom's Bitnami monetization impact, these change altered how organizations access trusted container images.
Understanding the Technical Challenge
The monetization changes mean organizations previously pulling images like bitnami/redis or bitnami/postgresql without restrictions now face authentication requirements and usage limitations. While Bitnami offers commercial solutions for production environments, some teams need alternatives for development, testing, or learning purposes.
The solution involves:
- Cloning Bitnami's open-source container definitions
- Automating the build process with GitHub Actions
- Storing images in a private container registry (AWS ECR)
- Updating Helm charts to reference custom images
Important Disclaimers
This guide is for educational and development purposes. Building your own container images introduces complexity and responsibility that many organizations may not be prepared to handle. Consider these factors:
- Bitnami's Official Stance: Bitnami explicitly does not recommend self-building their images for production environments
- Support Limitations: Self-built images void any support agreements with Bitnami
- Security Responsibility: You become responsible for timely security updates and vulnerability management
- Maintenance Overhead: Managing a custom image pipeline requires dedicated resources and expertise
When This Approach Makes Sense:
- Development and testing environments where cost control is priority
- Organizations with mature DevOps teams capable of maintaining custom pipelines
- Scenarios where you need custom modifications to base images
- Learning environments for understanding container build processes
Recommended Alternatives:
- Bitnami's commercial offerings for production workloads
- Official upstream images where available
- Other trusted community-maintained alternatives
This guide demonstrates the technical feasibility of building custom Bitnami images using GitHub Actions and AWS ECR, plus how to update your Helm charts to consume these self-hosted images. Use this knowledge responsibly and consider the trade-offs carefully.
Setting Up the Infrastructure
Repository Structure
Create a dedicated repository for your container images with this structure:
container-images/
├── containers/bitnami/ # Bitnami container definitions
│ ├── nginx/
│ ├── redis/
│ ├── postgresql/
│ └── ...
├── .github/workflows/ # GitHub Actions workflows
└── README.md
AWS ECR Registry Provisioning
Before building images, provision ECR repositories for each application you'll host:
# Create ECR repositories for common applications
aws ecr create-repository --repository-name acmeorg-bitnami-redis --region us-east-1
aws ecr create-repository --repository-name acmeorg-bitnami-nginx --region us-east-1
aws ecr create-repository --repository-name acmeorg-bitnami-postgresql --region us-east-1
❗For Infrastructure as Code approaches, use tools like Terraform or AWS CDK to manage these repositories.
Build Locally
This is straightforward. Just go inside the bitnami container folder and the tool you want to build image for. Assume we want to build an image for Redis. Enter the folder:
containers/bitnami/redis/8.2/debian-12
Now just run:
docker buildx build --platform linux/arm64 -t bitnami-redis . --load
Because I’m building on Apple Silicon (ARM64), I use the --platform flag to avoid accidental mismatches. This guarantees the image is built for the correct architecture expected by my Kubernetes cluster.
Now you can test the REDIS:
docker run --rm bitnami-redis redis-server --version
Output:
redis 21:11:53.26 INFO ==>
Redis server v=8.2.1 sha=00000000:1 malloc=jemalloc-5.3.0 bits=64 build=6242b285f6fb7236
Building the Automation Pipeline
GitHub Actions Workflow
Create a flexible workflow that handles multiple image types and versions. In the following example, I've written a workflow that is assuming a role in AWS. It pushes the images to the pre-created repositories in the previous step.
name: Build and Push Container Image to ECR
on:
workflow_dispatch:
inputs:
image_type:
description: 'Type of image to build'
required: true
type: choice
options:
- 'bitnami'
default: 'bitnami'
image_name:
description: 'Image name (e.g., nginx, redis, postgresql)'
required: true
type: string
image_version:
description: 'Image version (e.g., 1.29, 8.2, latest)'
required: true
type: string
default: 'latest'
os_flavor:
description: 'Operating system flavor'
required: true
type: choice
options:
- 'debian-12'
default: 'debian-12'
aws_region:
description: 'AWS region'
required: true
type: string
default: 'us-east-1'
env:
AWS_REGION: ${{ inputs.aws_region }}
ORG_PREFIX: "acmeorg"
permissions:
id-token: write
contents: read
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
role-session-name: GitHubActions-ECR-Push
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Determine build context and Dockerfile path
id: build-context
run: |
IMAGE_TYPE="${{ inputs.image_type }}"
IMAGE_NAME="${{ inputs.image_name }}"
IMAGE_VERSION="${{ inputs.image_version }}"
OS_FLAVOR="${{ inputs.os_flavor }}"
if [ "$IMAGE_TYPE" == "bitnami" ]; then
BUILD_CONTEXT="containers/bitnami/${IMAGE_NAME}/${IMAGE_VERSION}/${OS_FLAVOR}"
DOCKERFILE_PATH="${BUILD_CONTEXT}/Dockerfile"
FULL_IMAGE_NAME="bitnami-${IMAGE_NAME}"
ECR_REPOSITORY="${{ env.ORG_PREFIX }}-bitnami-${IMAGE_NAME}"
ECR_REPOSITORY="${ECR_REPOSITORY,,}"
elif [ "$IMAGE_TYPE" == "custom" ]; then
BUILD_CONTEXT="containers/custom/${IMAGE_NAME}/${IMAGE_VERSION}/${OS_FLAVOR}"
DOCKERFILE_PATH="${BUILD_CONTEXT}/Dockerfile"
FULL_IMAGE_NAME="${IMAGE_NAME}"
ECR_REPOSITORY="${{ env.ORG_PREFIX }}-custom-${IMAGE_NAME}"
ECR_REPOSITORY="${ECR_REPOSITORY,,}"
fi
# Verify Dockerfile exists
if [ ! -f "$DOCKERFILE_PATH" ]; then
echo "Dockerfile not found at: $DOCKERFILE_PATH"
echo "Available paths:"
find . -name "Dockerfile" -path "./containers/${IMAGE_TYPE}/${IMAGE_NAME}/*" | head -10
exit 1
fi
echo "build-context=$BUILD_CONTEXT" >> $GITHUB_OUTPUT
echo "dockerfile-path=$DOCKERFILE_PATH" >> $GITHUB_OUTPUT
echo "full-image-name=$FULL_IMAGE_NAME" >> $GITHUB_OUTPUT
echo "ecr-repository=$ECR_REPOSITORY" >> $GITHUB_OUTPUT
- name: Verify ECR repository exists
run: |
ECR_REPOSITORY="${{ steps.build-context.outputs.ecr-repository }}"
if ! aws ecr describe-repositories --repository-names "$ECR_REPOSITORY" --region ${{ env.AWS_REGION }} >/dev/null 2>&1; then
echo "ECR repository '$ECR_REPOSITORY' does not exist in region ${{ env.AWS_REGION }}"
echo "Please create the repository first using AWS CLI or Console"
exit 1
else
echo "ECR repository $ECR_REPOSITORY exists"
fi
- name: Generate image tags
id: tags
run: |
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
ECR_REPOSITORY="${{ steps.build-context.outputs.ecr-repository }}"
IMAGE_VERSION="${{ inputs.image_version }}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
GIT_SHA=$(git rev-parse --short HEAD)
# Generate tags
TAGS="${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_VERSION}"
TAGS="${TAGS},${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_VERSION}-${TIMESTAMP}"
TAGS="${TAGS},${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_VERSION}-${GIT_SHA}"
echo "tags=$TAGS" >> $GITHUB_OUTPUT
echo "primary-tag=${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_VERSION}" >> $GITHUB_OUTPUT
- name: Check if image exists
run: |
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
ECR_REPOSITORY="${{ steps.build-context.outputs.ecr-repository }}"
IMAGE_VERSION="${{ inputs.image_version }}"
if aws ecr describe-images --repository-name "$ECR_REPOSITORY" --image-ids imageTag="$IMAGE_VERSION" --region ${{ env.AWS_REGION }} >/dev/null 2>&1; then
echo "Image $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_VERSION already exists. Skipping build."
exit 0
else
echo "Image does not exist, proceeding with build."
fi
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ${{ steps.build-context.outputs.build-context }}
file: ${{ steps.build-context.outputs.dockerfile-path }}
push: true
tags: ${{ steps.tags.outputs.tags }}
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.title=${{ steps.build-context.outputs.full-image-name }}
org.opencontainers.image.version=${{ inputs.image_version }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Output image details
run: |
echo "Successfully built and pushed image!"
echo "Primary tag: ${{ steps.tags.outputs.primary-tag }}"
echo "Pull command: docker pull ${{ steps.tags.outputs.primary-tag }}"
Key Workflow Features
The workflow supports both Bitnami and you could also add a custom image type, allowing you to extend beyond Bitnami's catalog as needed. Each build creates multiple tags including timestamped and Git SHA variants for precise version tracking.
It utilizes GitHub Actions cache to accelerate subsequent builds of the same image. I've also added a check for ECR repository existence and prevents duplicate builds of existing image versions.
You can extend this by implementing the Semantic Release and use tags instead. It's totally up to you.
Adding Bitnami Applications
Copying Container Definitions
To add a new application from Bitnami's catalog:
# Clone Bitnami's containers repository
git clone https://github.com/bitnami/containers.git bitnami-source
cd bitnami-source
# Copy specific application (e.g., Redis)
cp -r bitnami/redis ../containers/bitnami/
# Clean up
cd ..
rm -rf bitnami-source
Ensure the copied application follows Bitnami's standard structure:
containers/bitnami/redis/
├── 8.2/
│ └── debian-12/
│ ├── Dockerfile
│ ├── prebuildfs/
│ └── rootfs/
├── docker-compose.yml
└── README.md
Updating Helm Charts for Custom Images
Understanding Chart Dependencies
Most Bitnami Helm charts reference multiple container images. For Redis, the Chart.yaml typically includes:
annotations:
images: |
- name: kubectl
image: bitnami/kubectl:1.33.3
- name: redis
image: bitnami/redis:8.2.0
- name: redis-exporter
image: bitnami/redis-exporter:1.74.0
- name: redis-sentinel
image: bitnami/redis-sentinel:8.0.3
Selective Image Replacement
You don't need to build every referenced image. Focus on the components you actually use in your deployment.
Values Configuration
Update your values.yaml to reference your custom registry:
# Primary Redis configuration
image:
registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
repository: acmeorg-bitnami-redis
tag: 8.2.0
digest: ""
# Redis Sentinel (if enabled)
sentinel:
enabled: false # Disable if not needed
# If enabled:
# image:
# registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
# repository: acmeorg-bitnami-redis-sentinel
# tag: 8.0.3
# Redis Exporter (if metrics enabled)
metrics:
enabled: false # Disable if not needed
# If enabled:
# image:
# registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
# repository: acmeorg-bitnami-redis-exporter
# tag: 1.74.0
# kubectl image (used for init containers)
kubectl:
image:
registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
repository: acmeorg-bitnami-kubectl
tag: 1.33.3
❗In the value file, you need to enable the allowInsecureImages: true
global:
imageRegistry: ""
## E.g.
## imagePullSecrets:
## - myRegistryKeySecretName
##
imagePullSecrets: []
defaultStorageClass: ""
storageClass: ""
## Security parameters
##
security:
## @param global.security.allowInsecureImages Allows skipping image verification
allowInsecureImages: true <<<<< MAKE SURE YOU HAVE THIS
Deployment Example
Deploy Redis using your custom images:
# Add Bitnami Helm repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Deploy with custom values
helm install my-redis bitnami/redis \
--values values.yaml \
--namespace redis \
--create-namespace
Advanced Configuration
Multi-Architecture Builds
For some environments you can set ARM64 (like AWS Graviton instances), just modify the workflow to build multi-platform images:
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
# ... other configuration
Security Scanning
Integrate container scanning into your pipeline:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.tags.outputs.primary-tag }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Automated Updates
Create scheduled workflows to check for new Bitnami releases:
name: Check for Updates
on:
schedule:
- cron: '0 6 * * 1' # Weekly on Mondays
jobs:
check-updates:
runs-on: ubuntu-latest
steps:
- name: Check Bitnami repository for updates
run: |
# Script to compare your versions with upstream
# Create issues or PRs for detected updates
Conclusion
Building your own Bitnami images is a technical exercise that requires careful consideration of the trade-offs involved. While this guide demonstrates the technical feasibility, remember that Bitnami does not recommend this approach for production environments.
Key Takeaways:
- Educational Value: Understanding how to build container images strengthens your DevOps knowledge
- Development Use Cases: This approach can be valuable for development and testing environments
- Production Considerations: For production workloads, evaluate Bitnami's commercial offerings or other supported alternatives
- Responsibility: Self-built images require ongoing maintenance, security updates, and vulnerability management
The GitHub Actions workflow presented here provides an idea for those who choose this path. Whether you use this for learning, development environments, or as part of a larger container strategy, approach it with full understanding of the responsibilities involved.
Organizations should carefully evaluate their specific needs, compliance requirements, and operational capabilities before implementing self-hosted container registries. For many use cases, Bitnami's commercial offerings or other officially supported alternatives is more appropriate.
Comments 0