Isolated Image Builder¶
The image-builder profile builds and publishes OCI images without giving workflow
code the PitCrew host's Docker socket. Its worker contains buildctl and reaches one
operator-owned BuildKit daemon through the profile's isolated service network.
This lane is for Dockerfile builds and image publication. It does not support Docker Compose, Testcontainers, service containers, or integration tests whose bind mounts must resolve on the runner's Docker daemon.
Architecture¶
GitHub job
-> disposable image-builder worker
-> mTLS BuildKit API on pitcrew-image-builder network
-> isolated build daemon
-> OCI registry
The BuildKit daemon is never the PitCrew orchestration daemon. Workers receive no Docker socket and no generic Docker API.
BuildKit commonly requires elevated daemon-host capabilities. Run it on a dedicated builder host or virtual machine. If the BuildKit container shares a Docker daemon with PitCrew, dedicate that entire host to the image-builder trust boundary.
Provision the service boundary¶
Create the profile network:
Configure BuildKit 0.32.2 with a TCP listener protected by mutual TLS:
root = "/var/lib/buildkit"
[grpc]
address = [ "tcp://0.0.0.0:1234" ]
[grpc.tls]
cert = "/certs/server-cert.pem"
key = "/certs/server-key.pem"
ca = "/certs/ca.pem"
[history]
maxAge = 60
maxEntries = 1
[worker.oci]
enabled = true
gc = true
max-parallelism = 1
Attach the daemon or an operator-owned TLS passthrough proxy to
pitcrew-image-builder with network alias buildkitd. Do not publish port 1234 to
the host or attach the service to a manager Compose network.
Keep the CA private key and server private key outside PitCrew. Give workflows only a client CA certificate, client certificate, and client key through GitHub secrets.
Install the profile¶
The profile:
- omits broad default labels;
- accepts
runs-on: [linux, x64, image-builder]; - permits one active worker across all configured repository targets;
- verifies BuildKit client version 0.32.2 by checksum; and
- requires the exact
pitcrew-image-builderservice network before replacing a manager.
Publish an image¶
Materialize the client certificate bundle into a job-private directory containing
ca.pem, cert.pem, and key.pem, then call:
export BUILDKIT_HOST=tcp://buildkitd:1234
export BUILDKIT_TLS_DIR="$RUNNER_TEMP/buildkit-tls"
immutable_ref="$(
pitcrew-build-image \
ghcr.io/example/project:candidate \
. \
.
)"
printf 'Published %s\n' "$immutable_ref"
pitcrew-build-image:
- rejects missing mTLS material;
- removes cache and unpinned history from a prior interrupted job;
- sends the local context to BuildKit;
- pushes the image;
- validates the returned
sha256digest; and - removes cache and history again on exit.
The profile's aggregate maximum is one because cache pruning is a profile-wide job boundary. Increasing concurrency requires independent BuildKit daemons and profiles; do not let concurrent jobs prune one shared daemon.
Registry authentication remains job-scoped. Configure $DOCKER_CONFIG inside the
worker and remove it before the job exits. Never put registry credentials in the
profile, image, BuildKit daemon configuration committed to this repository, or
observed state.
Validate isolation¶
Before production use, prove:
- a client without the expected certificate is rejected;
/var/run/docker.sockis absent inside the worker;- the published registry digest matches BuildKit metadata;
buildctl duis empty after the helper exits;- a cancelled build is cleaned by the next job's preflight; and
- no service administration endpoint is reachable from unrelated profiles.
Update and rollback¶
BuildKit client changes are normal worker-image changes. Review the version and checksums, then replay the complete profile command. Busy workers finish on the prior image.
Roll back by restoring the previous profile manifest or PitCrew release and replaying the complete setup command. BuildKit daemon upgrades remain operator-owned and should be rolled independently after saving their exact configuration and state policy.