Skip to main content

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.hcl and builds via docker 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

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 buildx plugin)

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-file entries are applied after the settings-declared env_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 a build_args entry (so no accidental secret leaks).
  • Format: KEY=VALUE lines, optional leading export , # 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:

  1. Add dockerfile_template to your targets in build-settings.yml:
targets:
  base:
    dockerfile: docker/Dockerfile.base          # Generated output
    dockerfile_template: docker-templates/base/Dockerfile.j2  # Source template
    context: .
  1. 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 %}
  1. (Optional) Create variant-specific configs in docker-templates/base/variants/:
# variants/alpine.yml
system_packages:
  - curl
  - ca-certificates
  1. (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 }}"
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 .dockerignore to 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)

Source distribution for baker-cli 1.2.0
File Size Uploaded
baker_cli-1.2.0.tar.gz 36.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for baker-cli 1.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.1.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page