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 all
Builds 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 build
Applies 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 tag
Creates 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 manifest
Copies 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 deploy
PROJECT_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: true
optibuild all
optibuild build tag manifest
optibuild deploy
optibuild -c my-config.conf all
optibuild --verbose build
The 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-server
Then 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 deploy
Create ~/.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/config
Example 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 deploy
Install 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-stdin
Common 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-builder
Building 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 all
Result:
- 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