A bash-based orchestration framework for building, tagging, and deploying multi-platform Docker images.
- Simple is great
- Configuration over convention - describe what to build, not how
- Platform-explicit tagging - all image tags include platform suffix (-amd64, -arm64)
- Private registry as source of truth - all operations happen there first
- Clear separation of concerns - four distinct, independent phases
- Build multi-platform Docker images (amd64 + arm64)
- Support for local and remote buildx builders
- Automatic platform detection
- Efficient tagging using crane (no data transfer)
- Multiplatform manifest creation
- Deploy to GitHub Container Registry and Docker Hub
- Build caching via registry
- Flexible phase execution (run all or specific phases)
dockerwith buildx supportcrane(for efficient image operations)bash4.0+
Install crane:
# macOS
brew install crane
# Linux
curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_$(uname -s)_$(uname -m).tar.gz" | tar -xz crane
sudo mv crane /usr/local/bin/- Create a
build.conffile in your project:
PROJECT_PATH="."
VERSION="1.0.5"
IMAGE_NAME="myproject"
REMOTE_BUILDER="amd64-builder" # Optional: for cross-platform builds
TAGS=(
"latest"
"1.0"
)
BUILD_ARGS=(
"PHP_VERSION=8.3"
)
GITHUB_ORG="optimode"
DOCKERHUB_ORG="optimode"
DEPLOY_TO_GITHUB=true
DEPLOY_TO_DOCKERHUB=true- Run the build:
/path/to/optibuild allBuilds Docker images for each platform with version tag.
Output (private registry):
registry.optimode.net/myproject:1.0.5-amd64
registry.optimode.net/myproject:1.0.5-arm64
Run separately:
optibuild buildApplies additional tags to already-built images (all platform-specific).
Output (private registry):
registry.optimode.net/myproject:latest-amd64
registry.optimode.net/myproject:latest-arm64
registry.optimode.net/myproject:1.0-amd64
registry.optimode.net/myproject:1.0-arm64
Run separately:
optibuild tagCreates multiplatform manifests (platform-agnostic tags).
Output (private registry):
registry.optimode.net/myproject:1.0.5 → points to -amd64 and -arm64
registry.optimode.net/myproject:latest → points to -amd64 and -arm64
registry.optimode.net/myproject:1.0 → points to -amd64 and -arm64
Run separately:
optibuild manifestCopies everything from private to public registries.
Output (public registries):
ghcr.io/optimode/myproject:1.0.5-amd64
ghcr.io/optimode/myproject:1.0.5-arm64
ghcr.io/optimode/myproject:1.0.5
ghcr.io/optimode/myproject:latest-amd64
ghcr.io/optimode/myproject:latest-arm64
ghcr.io/optimode/myproject:latest
... (all tags)
docker.io/optimode/myproject:* (same structure)
Run separately:
optibuild deployPROJECT_PATH="." # Path to project directory (where Dockerfile is)
VERSION="1.0.5" # Exact version to build
IMAGE_NAME="myproject" # Image name (without registry)# Registry settings
PRIVATE_REGISTRY="registry.optimode.net" # Default: registry.optimode.net
# Build settings
DOCKERFILE="Dockerfile" # Default: Dockerfile
BUILD_CONTEXT="." # Default: .
# Platform configuration
LOCAL_PLATFORM="" # Auto-detected if empty
REMOTE_PLATFORM="" # Auto-detected as opposite of local
REMOTE_BUILDER="" # Name of remote buildx builder (empty = local only)
# Tags (beyond the version itself)
TAGS=(
"latest"
"1.0"
"1"
)
# Build arguments
BUILD_ARGS=(
"PHP_VERSION=8.3"
"COMPOSER_VERSION=2.7"
)
# Deploy targets
GITHUB_ORG="optimode" # For ghcr.io/optimode/...
DOCKERHUB_ORG="optimode" # For docker.io/optimode/...
DEPLOY_TO_GITHUB=true # Default: true
DEPLOY_TO_DOCKERHUB=true # Default: trueoptibuild alloptibuild build tag manifestoptibuild deployoptibuild -c my-config.conf alloptibuild --verbose buildThe framework automatically detects your local platform:
- Mac M1/M2 (ARM64) → builds for
linux/arm64locally - Linux AMD64 → builds for
linux/amd64locally
If REMOTE_BUILDER is configured, it builds for the opposite platform using that builder.
For cross-platform builds, set up a remote buildx builder:
# On your remote AMD64 server
docker buildx create --name amd64-builder --driver docker-container
# From your local Mac
docker buildx create \
--name amd64-builder \
--driver docker-container \
--platform linux/amd64 \
ssh://user@remote-serverThen in your build.conf:
REMOTE_BUILDER="amd64-builder"The framework supports automatic authentication to GitHub Container Registry and Docker Hub during deployment.
Tokens are searched in the following order:
- Environment variables
- Framework config file:
~/.optibuild/config - Project
build.conffile
Required variables (use any of these):
GITHUB_TOKEN- Personal Access Token withwrite:packagesscopeGITHUB_CR_PAT- Container Registry Personal Access Token
Required variables:
DOCKERHUB_TOKENorDOCKERHUB_PASSWORD- Access token or passwordDOCKERHUB_USERNAME(optional, defaults toDOCKERHUB_ORG)
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxx"
export DOCKERHUB_TOKEN="dckr_pat_xxxxxxxxxxxxx"
optibuild deployCreate ~/.optibuild/config:
# Copy the example file
mkdir -p ~/.optibuild
cp examples/framework-config-example ~/.optibuild/config
# Edit with your actual tokens
vim ~/.optibuild/config
# Make it readable only by you
chmod 600 ~/.optibuild/configExample content:
# GitHub Container Registry
GITHUB_TOKEN="ghp_xxxxxxxxxxxxx"
# Docker Hub
DOCKERHUB_USERNAME="yourusername"
DOCKERHUB_TOKEN="dckr_pat_xxxxxxxxxxxxx"Add to your project's build.conf:
GITHUB_TOKEN="ghp_xxxxxxxxxxxxx"
DOCKERHUB_TOKEN="dckr_pat_xxxxxxxxxxxxx"Warning: Don't commit tokens to git! Add build.conf to .gitignore if it contains secrets.
GitHub Personal Access Token:
- Go to https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Select scopes:
write:packages,read:packages,delete:packages - Generate and copy the token
Docker Hub Access Token:
- Go to https://hub.docker.com/settings/security
- Click "New Access Token"
- Give it a description and select "Read, Write, Delete" permissions
- Generate and copy the token
optibuild/
├── lib/
│ ├── common.sh # Shared utilities, logging, error handling
│ ├── config.sh # Configuration loading and validation
│ ├── build.sh # Build phase functions
│ ├── tag.sh # Tag phase functions
│ ├── manifest.sh # Manifest creation functions
│ └── deploy.sh # Deploy phase functions (with auto-authentication)
├── optibuild # Main orchestrator script (executable)
├── README.md
├── .gitignore
└── examples/
├── framework-config-example # Template for ~/.optibuild/config
└── sample-project/
├── build.conf # Project configuration with all options
├── Dockerfile # Sample Dockerfile
└── README.md # Usage instructions
Run phases in order:
optibuild build # First build
optibuild tag # Then tag
optibuild manifest # Then create manifests
optibuild deploy # Finally deployInstall crane (see Requirements section above).
Install Docker Desktop or Docker CLI with buildx plugin.
The framework handles authentication automatically if tokens are configured (see "Authentication for Deployment" section).
Manual login (if needed):
# Private registry
docker login registry.optimode.net
# GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# Docker Hub
echo $DOCKERHUB_TOKEN | docker login docker.io -u USERNAME --password-stdinCommon issues:
- "GitHub token not found": Set
GITHUB_TOKENin environment,~/.optibuild/config, orbuild.conf - "Docker Hub token not found": Set
DOCKERHUB_TOKENin environment,~/.optibuild/config, orbuild.conf - "Failed to login": Check token validity and permissions
- Already logged in: The framework automatically detects existing authentication
Test remote builder:
docker buildx ls
docker buildx inspect amd64-builderBuilding a Dovecot mail server image:
# build.conf
PROJECT_PATH="."
VERSION="2.4.0"
IMAGE_NAME="dovecot"
REMOTE_BUILDER="amd64-builder"
TAGS=(
"latest"
"2.4"
"2"
)
BUILD_ARGS=(
"DOVECOT_VERSION=2.4.0"
"DEBIAN_VERSION=bookworm"
)
GITHUB_ORG="optimode"
DOCKERHUB_ORG="optimode"Run:
optibuild allResult:
- Private registry has all platform-specific images and manifests
- GitHub Container Registry has complete multiplatform image
- Docker Hub has complete multiplatform image
- Users can pull
docker pull optimode/dovecot:lateston any platform
- Fail fast: Any error in any phase stops the entire pipeline
- No partial states: Either everything succeeds or nothing is deployed
- Clear error messages: Always indicates what failed and why
- Debugging: Use
--verboseflag for detailed output
This is a simple bash framework and licensed under the MIT License - see the LICENSE file for details. Use it however you like.
Optimode (Laszlo Malina) GitHub: @optimode