Build and optionally publish a booth image for sharing or deployment.
booth build compiles the Boothfile into a Docker image and optionally pushes it to a container registry. The image tag defaults to a content hash, so identical configurations produce identical tags — enabling caching and instruction-level repeatability.
It is also how you create a fully reproducible environment: build the image once, store it (by tag or, better, by digest), and reuse that image instead of rebuilding. The stored image is the only artifact that freezes every dependency — base image, apt packages, and transitive libraries — byte-for-byte. See Reproducibility for the full picture and the three tiers of repeatability.
./booth build # build locally
./booth build --push ghcr.io/myteam # build and push
./booth build --push ghcr.io/myteam --name my-env --tag v1.0Back to README
- Overview
- Flags
- Image Naming
- Content-Based Tagging
- Pushing to a Registry
- Authentication
- Build Arguments
- Examples
- Implementation Plan
The current booth run flow builds a local image before launching it, but that image is tagged for local use only (codingbooth-local:<project>-<variant>-<version>) and cannot be pushed to a registry.
booth build solves this by producing a properly named image that can be:
- Used locally with
booth run --image <image> - Pushed to any container registry for sharing with teammates or CI
| Flag | Default | Description |
|---|---|---|
--push <registry> |
(none) | Push to the given registry after building. |
--name <name> |
Booth name (project name) | Image name. |
--tag <tag> |
Content hash (24 chars) | Image tag. |
--build-arg KEY=VALUE |
(from config) | Additional Docker build arguments. Repeatable. |
--code <path> |
. (current directory) |
Path to the project directory. |
--variant <variant> |
From .booth/config.toml |
Override the variant. |
--version <version> |
From .booth/config.toml |
Override the CodingBooth version. |
--verbose |
false |
Show detailed output. |
--dryrun |
false |
Print the docker command without executing. |
The full image reference is constructed as:
<registry>/<name>:<tag>
When --push is omitted (local build):
<name>:<tag>
Examples:
| Flags | Image |
|---|---|
| (none) | myproject:a3f8b2c1d4e5f6a7b8c9d0e1 |
--push ghcr.io/myteam |
ghcr.io/myteam/myproject:a3f8b2c1d4e5f6a7b8c9d0e1 |
--push ghcr.io/myteam --name custom |
ghcr.io/myteam/custom:a3f8b2c1d4e5f6a7b8c9d0e1 |
--push ghcr.io/myteam --tag v1.0 |
ghcr.io/myteam/myproject:v1.0 |
--name my-env --tag latest |
my-env:latest |
When --tag is not specified, the tag is derived from a SHA-256 hash (truncated to 24 hex characters) of a JSON document containing:
{
"boothfile": "<Boothfile content, trimmed>",
"build-args": ["KEY1=VALUE1", "KEY2=VALUE2"],
"variant": "codeserver",
"version": "0.35.0"
}boothfile— the raw Boothfile content with leading/trailing whitespace trimmed.build-args— sorted list of all build arguments (from config + CLI--build-argflags).variant— the resolved variant name.version— the resolved CodingBooth version.
This means:
- Same config = same tag. Two machines with identical
.booth/produce the same hash. - Skip-if-exists. Before building, check if the image already exists (locally or in registry). If it does, skip the build and print the image name.
- Any change = new tag. Changing the Boothfile or build args produces a different hash, so stale images are never reused.
Note: A matching tag proves the recipe is identical, not that the bytes are. A later rebuild can resolve unpinned transitive dependencies or apt packages to newer versions under the same tag. For byte-for-byte guarantees, store and reuse the built image rather than rebuilding — see Reproducibility.
Use --push <registry> to build and push in one step:
./booth build --push ghcr.io/myteamBuild backend:
- Without
--push: Usesdocker build. Image stays local. - With
--push: Usesdocker buildfollowed bydocker push.
Error handling: If the push fails (e.g. authentication missing), CodingBooth surfaces Docker's error and suggests the fix:
Error: push to ghcr.io/myteam/myproject:a3f8b2c1d4e5f6a7b8c9d0e1 failed.
Ensure you are logged in: docker login ghcr.io
CodingBooth does not manage registry credentials. Authentication is handled entirely by Docker:
# Log in to your registry before pushing
docker login ghcr.io
docker login registry.example.com
# Credentials are stored in ~/.docker/config.json
# Credential helpers (gcloud, ecr, acr) work as normalIf authentication is missing or expired, the push fails with Docker's own error message. CodingBooth suggests the appropriate docker login command based on the registry.
Common registries:
| Registry | Login command |
|---|---|
| Docker Hub | docker login |
| GitHub Container Registry | docker login ghcr.io |
| Google Artifact Registry | gcloud auth configure-docker |
| AWS ECR | aws ecr get-login-password | docker login ... |
| Azure ACR | az acr login --name <registry> |
Build arguments come from two sources, merged in order:
.booth/config.tomlbuild-argsarray- CLI
--build-arg KEY=VALUEflags (can override config values)
CodingBooth also injects these automatically:
BOOTH_VARIANT_TAG=<variant>BOOTH_VERSION_TAG=<version>BOOTH_SETUPS=<setups-dir>
./booth build
# ✅ Built: myproject:a3f8b2c1d4e5f6a7b8c9d0e1docker login ghcr.io
./booth build --push ghcr.io/myteam
# ✅ Pushed: ghcr.io/myteam/myproject:a3f8b2c1d4e5f6a7b8c9d0e1./booth build --push ghcr.io/myteam --name my-env --tag v1.0
# ✅ Pushed: ghcr.io/myteam/my-env:v1.0./booth build --build-arg PYTHON_VERSION=3.13 --build-arg NODE_VERSION=22
# ✅ Built: myproject:b7e2f1a9c3d8e4f5a6b7c8d9./booth build --push ghcr.io/myteam
# ✅ Pushed: ghcr.io/myteam/myproject:a3f8b2c1d4e5f6a7b8c9d0e1
./booth run --image ghcr.io/myteam/myproject:a3f8b2c1d4e5f6a7b8c9d0e1Add case "build": to main.go dispatcher, routing to a new buildBooth(version) function in build.go.
Parse build-specific flags: --push <registry>, --name, --tag, --build-arg, --code, --variant, --version, --verbose, --dryrun.
Reuse boothinit.InitializeAppContext for resolving config from .booth/config.toml (variant, version, build-args).
New function in pkg/booth/build_hash.go:
func ComputeBuildHash(boothfileContent string, buildArgs []string, variant string, version string) string
- Construct JSON object with the four fields (build-args sorted).
- SHA-256 hash the JSON bytes.
- Hex-encode and truncate to 24 characters.
New function in pkg/booth/build_image.go:
func AssembleImageName(registry, name, tag string) string
- If registry is empty:
<name>:<tag> - If registry is set:
<registry>/<name>:<tag>
Reuse the existing normalizeDockerFile() + compileBoothfile() logic from ensure_docker_image.go to produce the Dockerfile from the Boothfile. May need to extract/refactor these into shared functions.
- Local (no
--push): Call existingdocker.DockerBuild()with-t <image>and build-args. - Push (
--push <registry>): New functiondocker.DockerBuildAndPush(flags, args, imageName)that runsdocker buildfollowed bydocker push. This is more reliable thandocker buildx build --pushbecause it avoids buildx driver networking issues (e.g., docker-container driver cannot reach localhost registries). On failure, detect the registry host from the--pushvalue and suggestdocker login <host>.
Before building, check if the image exists:
- Local:
docker image inspect <image> - Remote:
docker manifest inspect <image>(requires registry access)
If it exists, print the image name and skip. Add --force to override.
Print the final image reference:
✅ Built: <image> (local)
✅ Pushed: <image> (with --push)
Add booth build to help.go and include it in the main help output.
- Unit test for
ComputeBuildHash— deterministic output, sorted args, content changes produce different hashes. - Unit test for
AssembleImageName— all combinations of registry/name/tag. - Dryrun test — verify the docker command that would be executed.
- Integration test — build a simple Boothfile locally (no push).