baker-cli
A small, pragmatic Python CLI that controls your Docker build cascades uniformly locally and in CI:
- Targets & Bundles are defined in YAML
- Tags are created by checksum (self / self+deps) or by expressions (ENV, files, Git-SHA, ...)
- Build only when necessary: Existence check locally or in registry
- Optionally generates a
docker-bake.hcland builds viadocker buildx bake - Build-Args are definable, get interpolated and flow into the hash
- Configuration values can be overridden via CLI (
--set key=value)
Contents
Quickstart
1) Installation with venv (Recommended)
mkdir my-project
cd my-project
python3 -m venv .venv
source .venv/bin/activate
pip install baker-cli
baker init
2) Global installation (pip/pipx)
# With pip
pip install baker-cli
# Or with pipx (recommended for global CLIs)
pipx install baker-cli
# Initialize project (current directory or target folder)
baker init
# or
baker init ./my-project
2) Development (local, .venv)
# Create virtual environment
python -m venv .venv
source .venv/bin/activate
# Install project locally (editable)
pip install -U pip
pip install -e .
# Initialize project (if not yet present)
baker init
# Optional: Generate CI workflow
baker ci --settings build-settings.yml
# Example: Plan & Build
baker plan
baker build --push --targets base
Prerequisites
- Python 3.11+
- Docker (with
buildxplugin)
Repository Layout
demo/ # Project name
├── build-settings.yml # Build configuration
├── sqlite/ # Sample Stage "sqlite"
│ └── Dockerfile # Related Dockerfile
└── ui/ # Sample Stage "ui"
└── Dockerfile # Related Dockerfile
Configuration (build-settings.yml)
Targets
targets:
cascade-base:
dockerfile: Dockerfile.sqlite
context: .
tags:
- "cascade-base:{{ checksum_self }}"
build-args:
CONDUCTOR_VERSION: "3.16.0"
JAVA_VERSION: "17"
cascade-ui:
dockerfile: ui/Dockerfile
context: .
tags:
- "cascade-ui:{{ checksum_self }}"
depends_on:
- cascade-base
build-args:
BASE_IMAGE: "cascade-base:{{ checksum_self }}"
Bundles
bundles:
all:
targets:
- cascade-base
- cascade-ui
sqlite:
targets:
- cascade-base
Interpolation & Expressions
targets:
my-target:
tags:
- "my-app:{{ env.BUILD_VERSION }}"
- "my-app:{{ git.short_sha }}"
- "my-app:{{ file_hash('package.json') }}"
build-args:
VERSION: "{{ env.BUILD_VERSION }}"
COMMIT_SHA: "{{ git.full_sha }}"
Env Files (env_files / --env-file)
env() expressions resolve against the process environment. To keep builds
self-contained (independent of any shell/CI wiring), baker can load one or more
dotenv-style files and merge them into that resolution scope.
Declare them in build-settings.yml and/or pass them on the CLI:
# build-settings.yml
env_files:
- ops/env/.env # always loaded (if present)
- ops/env/.env.${env("STAGE_NAME")} # path itself may be interpolated
targets:
base:
build_args:
# Value now comes from the env file(s) above (optional override)
BASE_IMAGE_SOURCE: ${env("BASE_IMAGE_SOURCE","")}
# Append additional files ad-hoc (repeatable). Works on plan/gen-hcl/gen-docker/build/rm.
baker build --targets base --env-file ops/env/.env --env-file ops/env/.env.staging
Rules:
- Paths are resolved relative to the current working directory. Non-existent files are skipped silently (env files are optional by design).
- Precedence: the real process/CI environment always wins over file
values (dotenv convention). Among files, later files override earlier ones;
CLI
--env-fileentries are applied after the settings-declaredenv_files. - Scope: loaded variables only feed
env()resolution (registry, owner, tags, build_args). They do not get injected into the Docker build unless explicitly referenced via abuild_argsentry (so no accidental secret leaks). - Format:
KEY=VALUElines, optional leadingexport,#comments and blank lines ignored, surrounding quotes stripped. No in-file interpolation.
Tag Expressions (Functions)
{{ checksum_self }}- Hash of Dockerfile + context{{ checksum_deps }}- Hash of dependencies{{ env.VAR_NAME }}- Environment variable{{ git.short_sha }}- Short Git commit hash{{ git.full_sha }}- Full Git commit hash{{ file_hash('path/to/file') }}- Hash of specific file{{ timestamp }}- Current timestamp
Build-Args & Hashing
Build-args are interpolated and included in the hash calculation:
targets:
my-target:
build-args:
VERSION: "{{ env.BUILD_VERSION }}"
FEATURE_FLAG: "{{ env.ENABLE_FEATURE }}"
# These args flow into the checksum calculation
CLI
plan
Show what would be built:
# Show plan for specific targets
python baker.py plan --targets cascade-base
# Show plan with existence check
python baker.py plan --check local --targets cascade-base
# Show plan for bundles
python baker.py plan --targets all
gen-hcl
Generate docker-bake.hcl file:
# Generate HCL file
python baker.py gen-hcl --targets cascade-base
# Generate for all targets
python baker.py gen-hcl --targets all
gen-docker
Generate Dockerfiles from Jinja2 templates with platform-specific recipes:
# Generate Dockerfiles for debian variant (default)
baker gen-docker
# Generate for alpine variant
baker gen-docker --variant alpine
# Generate for specific targets only
baker gen-docker --targets base dev --variant alpine
# Dry-run: show what would be generated
baker gen-docker --dry-run
# Show diff against existing Dockerfiles
baker gen-docker --diff --variant alpine
Setup:
- Add
dockerfile_templateto your targets inbuild-settings.yml:
targets:
base:
dockerfile: docker/Dockerfile.base # Generated output
dockerfile_template: docker-templates/base/Dockerfile.j2 # Source template
context: .
- Create template files using Jinja2 syntax with recipes:
# docker-templates/base/Dockerfile.j2
FROM {{ base_image }}
# Use platform-specific recipe for package installation
{{ recipe("install_packages", packages=["curl", "ca-certificates"]) }}
# Conditional recipe
{% if has_recipe("compile_postgres") %}
{{ recipe("compile_postgres", pg_version="16.4") }}
{% endif %}
- (Optional) Create variant-specific configs in
docker-templates/base/variants/:
# variants/alpine.yml
system_packages:
- curl
- ca-certificates
- (Optional) Define custom recipes in
dockerfile-recipes.yml:
recipes:
my_custom_recipe:
debian: |
RUN apt-get install -y {{ packages | join(' ') }}
alpine: |
RUN apk add {{ packages | join(' ') }}
Built-in Recipes:
| Recipe | Description |
|---|---|
install_packages |
Install system packages (apt/apk) |
install_build_packages |
Install build dependencies |
cleanup_build_packages |
Remove build dependencies |
pip_install |
Install Python packages |
create_user |
Create non-root user |
compile_postgres |
Build minimal psql (debian only) |
install_postgres_client |
Install psql via package manager |
compile_nginx |
Build nginx with minimal modules (debian only) |
install_nginx |
Install nginx via package manager |
cleanup_caches |
Remove pip/yarn/uv caches |
build
Build Docker images:
# Build locally
baker build --check local --no-push --targets base
# Build and push
baker build --check remote --push --targets base
# Build with Dockerfile generation (for templated projects)
baker build --gen-docker --variant debian --targets base
# Build alpine variant
baker build --gen-docker --variant alpine --targets base
Global Overrides (--set)
Override configuration values:
# Override build args
python baker.py build --set CONDUCTOR_VERSION=3.17.0 --targets cascade-base
# Override multiple values
python baker.py build --set CONDUCTOR_VERSION=3.17.0 --set JAVA_VERSION=21 --targets cascade-base
Existence Check & Push Strategy
Local Check
python baker.py build --check local --push=off --targets cascade-base
- Checks if image exists locally
- Skips build if found
Registry Check
python baker.py build --check registry --push=on --targets cascade-base
- Checks if image exists in registry
- Skips build if found
- Pushes after successful build
No Check
python baker.py build --check=off --push=on --targets cascade-base
- Always builds
- Pushes after successful build
GitHub Actions Example
name: Build and Push
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: pip install pyyaml
- name: Build images
run: |
python baker.py build \
--check registry \
--push=on \
--targets all \
--set BUILD_VERSION=${{ github.sha }}
rm
Remove local Docker images for specific or all targets:
# Dry-run: show what would be removed for specific targets
baker rm --targets base app --dry-run
# Remove only primary tags for all targets
baker rm
# Remove all tags for selected targets
baker rm --targets base app --all-tags
# Force remove (dangling/used) images
baker rm --targets base --force
Tips & Best Practices
1. Use Checksums for Reproducible Builds
targets:
my-target:
tags:
- "my-app:{{ checksum_self }}"
2. Leverage Dependencies
targets:
base:
dockerfile: Dockerfile.base
app:
dockerfile: Dockerfile.app
depends_on:
- base
build-args:
BASE_IMAGE: "base:{{ checksum_self }}"
3. Use Environment Variables for Dynamic Values
targets:
my-target:
build-args:
VERSION: "{{ env.BUILD_VERSION }}"
COMMIT_SHA: "{{ git.short_sha }}"
4. Group Related Targets in Bundles
bundles:
production:
targets:
- base
- app
- worker
development:
targets:
- base
- dev-tools
Troubleshooting
Common Issues
1. Docker Buildx Not Available
# Enable buildx
docker buildx create --use
2. Registry Authentication
# Login to registry
docker login my-registry.com
3. Build Context Issues
# Ensure context includes all necessary files
targets:
my-target:
context: . # Use project root
dockerfile: path/to/Dockerfile
4. Tag Collisions
# Use unique tags
targets:
my-target:
tags:
- "my-app:{{ checksum_self }}"
- "my-app:latest" # Only if appropriate
Security Notes
1. Build-Args Security
- Build-args are visible in image history
- Don't pass secrets via build-args
- Use multi-stage builds for sensitive data
2. Registry Security
- Use authenticated registries
- Scan images for vulnerabilities
- Use specific tags, avoid
latest
3. Context Security
- Use
.dockerignoreto exclude sensitive files - Minimize build context size
- Review Dockerfile for security best practices
Advanced Usage
Custom Tag Functions
targets:
my-target:
tags:
- "my-app:{{ env.BUILD_VERSION }}-{{ git.short_sha }}"
- "my-app:{{ file_hash('package.json') }}"
Conditional Builds
targets:
my-target:
dockerfile: Dockerfile
tags:
- "my-app:{{ checksum_self }}"
# Only build if specific conditions are met
build-args:
BUILD_TYPE: "{{ env.BUILD_TYPE }}"
Multi-Architecture Builds
targets:
my-target:
platforms:
- linux/amd64
- linux/arm64
tags:
- "my-app:{{ checksum_self }}"
This baker-cli provides a powerful yet simple way to manage Docker builds with consistency between local development and CI/CD pipelines.
Metadata
Release files for baker-cli 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| baker_cli-1.2.0.tar.gz | 36.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| baker_cli-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.5 kB
Release files / baker_cli-1.2.0.tar.gz
| Download URL | baker_cli-1.2.0.tar.gz |
|---|---|
| Size | 36.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c9452eb44cc418b5dc5b8b7d109be1b4fdec0fb65c09423d300eb1167792c227
|
|
BLAKE2b-256 checksum How to use checksums |
a286f34f256d285ef0d8a80b4f89aba930bf90a567bc9cc1b6675e1736932012
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / baker_cli-1.2.0-py3-none-any.whl
| Download URL | baker_cli-1.2.0-py3-none-any.whl |
|---|---|
| Size | 37.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3dabb975af5d802d75a145f68ce798ae7013cfa33a2fcc29c7e2a560c14a884
|
|
BLAKE2b-256 checksum How to use checksums |
4c01c81c852fadaf975974f98af1a76967485b5c36c34023092862293c7d32f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|