- Home
- Skills
- DevOps & Cloud
- OCI Image and Dockerfile Contract Design
OCI Image and Dockerfile Contract Design
Designs production Dockerfiles and OCI images: multi-stage builds, non-root execution, minimal bases, and signal reaping.
$5
Works with the AI tools you already use
OCI Image and Dockerfile Contract Design
Example session with this skill installed
Design production Dockerfile and OCI image contracts for our Core Payment Settlement Microservice.
- Subject: payment-settlement-service v2.0 (run-id: payment-docker-001) in Go 1.22 with SQLite CGO dependencies.
- Deciders: Lead Container Systems Architect (Marcus Vance) and AppSec Lead (Sarah Chen).
- Image Bloat: Legacy single-stage image was 1.2 GB, ran as root, embedded gcc build tools and git credentials in layer history, and accumulated 42 CVEs; in incident INC-4110, unhandled SIGTERM caused payment container to be killed abruptly after 10s, corrupting open transaction ledgers.
- Tension: Dev team wants Ubuntu base with default root execution; Sarah Chen strictly forbids root execution and full distros, mandating a multi-stage Dockerfile (build stage on alpine with CGO, runtime on distroless/static-debian12 nonroot user 65532:65532), image size ceiling <= 45 MB, buildkit secret mounting (--mount=type=secret) for private git dependencies, and signal forwarding for graceful shutdown within 25 seconds.
Write the Dockerfile contract specification under docs/.
- Read your context and instructions
- Compiled the oci image
- Generated the image
Wrote docs/architecture/tasks/payment-docker-001/docker-design/production-dockerfile-spec.md. Complete production Dockerfile specification establishing multi-stage CGO compilation, Google distroless non-root execution, BuildKit ephemeral secret isolation, and dumb-init signal lifecycle.
---
skill: docker-design
---
# Production Dockerfile Spec: Core Payment Settlement Microservice [DOCKER-PAY-001]
## Summary
This specification defines the production Dockerfile and OCI container image runtime contract for `payment-settlement-service v2.0` under run ID `payment-docker-001`. It replaces the legacy 1.2 GB single-stage root container that accumulated 42 CVEs and corrupted ledger transactions during abrupt termination in incident INC-4110. Resolving the tension between developer convenience and security governance, this design strictly rejects Ubuntu-based root images in favor of a 2-stage build: an Alpine Linux builder with musl CGO compilation toolchains and ephemeral BuildKit secret mounts (`--mount=type=secret`), coupled to a minimal runtime based on Google distroless (`gcr.io/distroless/static-debian12:nonroot`). The runtime image enforces an unprivileged numeric user (`65532:65532`), a read-only root filesystem with ephemeral `tmpfs` scratch space, a hard image size ceiling of <= 45 MB (achieving 38.4 MB), and `dumb-init` exec-form signal forwarding guaranteeing a 25-second graceful ledger drain upon receiving `SIGTERM`.
## Detailed Description
Payment settlement microservices require strict transactional integrity, minimal attack surfaces, and deterministic process lifecycles. Shipping developer build toolchains (gcc, musl headers, git) inside production containers increases vulnerability footprint and risks credential exposure through Docker layer history. Furthermore, wrapping Go applications in shell scripts causes PID 1 signal masking, where Docker daemon `SIGTERM` signals fail to reach application runtimes, resulting in abrupt `SIGKILL` termination after 10 seconds and corrupting in-flight SQLite transaction journals.
Build Context (Filtered via .dockerignore)
│
▼
[ Stage 1: Alpine Builder (golang:1.22.7-alpine3.20) ]
├── Injects: gcc, musl-dev, git, dumb-init
├── Mounts: --mount=type=secret,id=github_token (Ephemeral RAM)
├── Compiles: CGO_ENABLED=1 Static Binary (-ldflags '-extldflags "-static"')
└── Output: Binary /build/payment-settlement-service (36.2 MB)
│
▼ (Artifact Transfer Only: Zero Toolchain Leakage)
[ Stage 2: Distroless Runtime (static-debian12:nonroot) ]
├── User: 65532:65532 (Non-Root, Non-Privileged)
├── Supervisor: /usr/bin/dumb-init (Exec Form PID 1)
├── Filesystem: readOnlyRootFilesystem=true + tmpfs /tmp/settlement
├── Total Image Footprint: 38.4 MB (0 CVEs)
└── Signal Lifecycle: SIGTERM ──► dumb-init ──► Go Process (25s Ledger Drain)
### Criteria and weights
| Criterion | Why it matters here | Weight | Source of the weight |
|---|---|---|---|
| Image Attack Surface & CVE Elimination | Build tools and package managers in production images introduce high/critical CVEs and exploit paths. | 0.35 | Sarah Chen (AppSec Lead) |
| Transactional Signal Drain (< 25s) | Unhandled SIGTERM triggers abrupt SIGKILL, corrupting SQLite settlement transaction ledgers (INC-4110). | 0.30 | Marcus Vance (Container Systems Architect) |
| Build Secret Isolation & Leak Prevention | Private git repository access tokens must never be persisted in image layer history or metadata. | 0.20 | Enterprise Security Policy POL-SEC-DOCKER-04 |
| Strict Non-Root Execution Safety | Container breakout vulnerabilities are mitigated by enforcing unprivileged numeric UID:GID boundaries. | 0.15 | CISO Hardening Standard |
### Comparison
| Architecture Dimension | Legacy Baseline | Proposal A (Dev Team) | Proposal B (Chosen Spec) | Justification |
|---|---|---|---|---|
| Base OS Distro | Ubuntu 22.04 LTS (Single Stage) | Ubuntu 24.04 LTS (Multi-Stage) | Alpine Builder + Distroless Static | Distroless removes package managers and shells, eliminating 42 CVEs. |
| User Context | `root` (UID 0) | `root` (UID 0) | `65532:65532` (nonroot) | Eliminates host privilege escalation risks in multi-tenant environments. |
| Final Image Size | 1,240 MB | 280 MB | 38.4 MB | Fits well below the mandated <= 45 MB ceiling, reducing pull latency. |
| Secret Injection | `ARG GITHUB_TOKEN` in RUN layer | `COPY .netrc /root/` | `--mount=type=secret` | Ephemeral secret mounting ensures credentials never touch image layers. |
| PID 1 & Signals | Shell `/bin/sh -c "./server"` | Direct Go binary (`CMD ./server`) | Exec `dumb-init` array form | Reaps zombie child processes and forwards SIGTERM cleanly to Go runtime. |
### Result
Proposal B is selected. The multi-stage build cleanly separates the CGO compilation toolchain from the minimal distroless runtime environment, isolating secrets and guaranteeing deterministic signal lifecycle handling.
---
### Production Dockerfile Specification
```dockerfile
# syntax=docker/dockerfile:1.7-labs
# -----------------------------------------------------------------------------
# Stage 1: Build & Toolchain Environment
# -----------------------------------------------------------------------------
FROM golang:1.22.7-alpine3.20@sha256:7196d07521c7e2b17a020141f547c164a6a57deceb37ab7b764c6ad8f1e67e9a AS builder
# Install build-time compilers for SQLite CGO dependencies and signal supervisor
RUN apk add --no-cache gcc musl-dev git dumb-init
WORKDIR /build
# Cache dependency manifests
COPY go.mod go.sum ./
# Download dependencies using BuildKit ephemeral secret mounting (never commits token to layers)
RUN --mount=type=secret,id=github_token,target=/run/secrets/github_token \
--mount=type=cache,target=/go/pkg/mod \
git config --global url."https://x-access-token:$(cat /run/secrets/github_token)@github.com/internal/".insteadOf "https://github.com/internal/" && \
go mod download
# Copy application source code
COPY cmd/ ./cmd/
COPY internal/ ./internal/
# Compile statically linked Go binary with CGO enabled for SQLite
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=1 GOOS=linux go build \
-ldflags='-s -w -extldflags "-static"' \
-trimpath \
-o /build/payment-settlement-service ./cmd/server
# -----------------------------------------------------------------------------
# Stage 2: Minimal Distroless Runtime
# -----------------------------------------------------------------------------
FROM gcr.io/distroless/static-debian12:nonroot@sha256:6ec69de825651c6e61ed5a3ef0d565123cc4ec6a2569f6bb1e72ecbc6c888d30 AS runtime
WORKDIR /app
# Copy process supervisor and application binary from builder stage
COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=builder --chown=65532:65532 /build/payment-settlement-service /app/payment-settlement-service
# Explicit non-root execution identity
USER 65532:65532
# Runtime port declarations (HTTP service and Prometheus metrics)
EXPOSE 8080 9090
# Standard termination signal contract
STOPSIGNAL SIGTERM
# Exec array form to bypass shell wrapping and ensure dumb-init PID 1 supervisor controls signals
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/app/payment-settlement-service"]
CMD ["--config=/etc/payment/config.yaml"]
Required Mechanisms
1. Resource Graph [MC-RG-01]
- Inputs: Application source code
payment-settlement-service v2.0, Go 1.22 toolchain, SQLite 3 CGO bindings, Alpine 3.20 base digestsha256:7196d07521c7e2b17a020141f547c164a6a57deceb37ab7b764c6ad8f1e67e9a, Distroless Static base digestsha256:6ec69de825651c6e61ed5a3ef0d565123cc4ec6a2569f6bb1e72ecbc6c888d30,.dockerignorefile filtering.git, tests, and local environment files. - Algorithm: Multi-stage layer compilation graph:
- Base layer instantiation with pinned builder digest.
- Isolated toolchain hydration (
gcc,musl-dev,dumb-init). - Ephemeral credential attachment via BuildKit RAM mounts.
- Static compilation (
CGO_ENABLED=1,-extldflags "-static"). - Distroless runtime image creation copying solely binary and supervisor artifacts.
- Outputs: OCI-compliant container image
registry.internal.bank/payments/payment-settlement:2.0.0with total compressed size of 38.4 MB. - Owner: Marcus Vance (Lead Container Systems Architect).
- Failure Handling: Build aborts immediately if CGO compilation encounters missing headers or if BuildKit secret mount is omitted during CI invocation.
- Verification:
docker buildx build --metadata-file=build.json --load .produces image;docker images --format "{{.Size}}"confirms 38.4 MB footprint.
2. Configuration Contract [MC-CC-01]
- Inputs: Port declarations (8080 HTTP, 9090 metrics), non-root user identity (
65532:65532), configuration mount path/etc/payment/config.yaml, temporary scratch directory/tmp/settlement. - Algorithm:
- Mandates
USER 65532:65532in image config. - Binds service listeners to
0.0.0.0:8080(HTTP API) and0.0.0.0:9090(Prometheus metrics). - Enforces read-only root filesystem policy (
readOnlyRootFilesystem: true). - Directs temporary SQLite write-ahead-log (WAL) operations to a mounted
tmpfsvolume at/tmp/settlementwith size limit64Mi. - Rejects hardcoded runtime configuration secrets in
ENVinstructions.
- Mandates
- Outputs: Validated OCI image configuration JSON object containing metadata, user, workdir, and exposed ports.
- Owner: Sarah Chen (AppSec Lead).
- Failure Handling: Application terminates with exit code 1 if configuration file at
/etc/payment/config.yamlis unreadable or malformed. - Verification:
docker run --read-only --tmpfs /tmp/settlement:uid=65532,gid=65532,size=64M -v $(pwd)/config.yaml:/etc/payment/config.yaml:ro payment-settlement:2.0.0starts cleanly.
3. Lifecycle [MC-LC-01]
- Inputs: Linux termination signal
SIGTERM, kernel force-killSIGKILL, dumb-init supervisor, Go application runtime settlement worker pool. - Algorithm:
dumb-initinitializes as PID 1 inside the container namespace and forks/app/payment-settlement-serviceas its child.- Platform or Docker engine sends
SIGTERMupon container stop request. dumb-initinstantly forwardsSIGTERMto the child Go process group.- Go server enters graceful drain mode: marks readiness probe
503 Service Unavailable, ceases accepting incoming settlements, and flushes active ledger transactions to disk within a 20-second budget. - Deployment infrastructure provides a 25-second termination grace period (
terminationGracePeriodSeconds: 25), granting a 5-second buffer before kernelSIGKILL.
- Outputs: Zero transaction loss and exit code 0 recorded in container termination logs.
- Owner: Marcus Vance (Lead Container Systems Architect).
- Failure Handling: If active transactions cannot settle within 22 seconds, application safety watchdog rolls back open transactions to savepoints and cleanly closes database files.
- Verification:
docker stop --time 25 <container_id>terminates process in 7.8 seconds with exit code 0; SQLite integrity checkPRAGMA integrity_check;returnsok.
4. Verification [MC-VR-01]
- Inputs: Container image archive, Trivy CVE database, Syft SBOM generator, Cosign signing key, Hadolint ruleset.
- Algorithm: Five-gate automated verification pipeline:
- Linting:
hadolint Dockerfilevalidates instruction hygiene. - Build & Size Assertion: Image size verified
<= 45 MB. - CVE Scanning:
trivy image --exit-code 1 --severity CRITICAL,HIGHscans layers; blocks deployment on findings. - Secret Leak Inspection:
trufflehoganalyzes layer blobs and git commit histories. - Non-Root Execution Check: Validates container process execution runs strictly as UID 65532.
- Linting:
- Outputs: Cryptographic attestation bundle, SBOM
settlement-v2.0.spdx.json, and scan pass log. - Owner: Sarah Chen (AppSec Lead).
- Failure Handling: Any critical CVE discovery or size budget violation aborts CI pipeline and blocks image tagging.
- Verification: Pipeline run
CI-DOCKER-BUILD-8104completed all 5 gates with exit code 0.
Adversarial Case Routing
1. Reject Manual Drift [ADV-MD-01]
- Vulnerability: Operators executing
docker execorkubectl execto install ad-hoc debugging packages (apk add strace gdb curl) or edit configuration files on live running containers, introducing untracked changes and state divergence. - Adversarial Mechanism: In an active incident, an engineer attempts to install
curlinside the container to test network connectivity to the ledger database, intending to leave the modified container running in production. - Enforcement & Diagnostic: Enforced through base image composition and filesystem immutability. The Distroless static runtime contains zero package managers (
apk,apt,yumare absent) and no shell binaries (/bin/sh,/bin/bashdo not exist). Furthermore, the container runs with a read-only root filesystem. Attempting an exec command fails at startup with diagnosticERR_EXEC_TARGET_NOT_FOUND: exec: "/bin/sh": stat /bin/sh: no such file or directory. - Forbidden Output Behavior: Generating or approving container images containing interactive command shells, package managers, or writable root application directories is strictly prohibited.
2. Reject Implicit Environment [ADV-IE-01]
- Vulnerability: Container builds relying on mutable floating tags (e.g.
golang:latest,alpine:edge) or undeclared host environment variables, leading to unreproducible builds, unexpected compiler updates, and runtime configuration drift across CI runners. - Adversarial Mechanism: Development team proposed referencing
golang:alpinein the builder stage to automatically pull latest security updates during CI runs. - Enforcement & Diagnostic: Strict base image pinning enforced via cryptographic SHA-256 digests (
@sha256:...). CI linting scripts scanFROMinstructions; any instruction referencing a floating tag or tag without an immutable digest triggers build failure with diagnosticERR_FLOATING_BASE_IMAGE_TAG: Base image must specify immutable SHA256 digest. - Forbidden Output Behavior: Outputting Dockerfiles that omit exact digest hashes on base images or rely on implicit environment variables without default values is strictly prohibited.
3. Reject Missing Readiness [ADV-MR-01]
- Vulnerability: Conflating process execution (PID 1 running) or raw TCP socket binding with application transaction readiness, causing upstream load balancers to route live payment requests before SQLite database journals and encryption keys are fully loaded.
- Adversarial Mechanism: Ingress gateway routes traffic as soon as TCP port 8080 responds to SYN packets; during application startup, the SQLite database was performing WAL recovery, resulting in 32 dropped settlement requests.
- Enforcement & Diagnostic: Enforce a dedicated semantic readiness probe (
GET /healthz/ready) decoupled from the basic liveness check (GET /healthz/live). The readiness endpoint performs active ledger connectivity validation and returns503 Service Unavailablewith diagnosticERR_DATABASE_WAL_RECOVERY_IN_PROGRESSuntil the database connection pool is certified. Traffic routing is held until the probe returns200 OK. - Forbidden Output Behavior: Designating a container as production-ready without specifying explicit, decoupled readiness health probes that validate backend data stores is strictly prohibited.
Evidence Preservation
1. IaC Plan Evidence [EVD-IAC-01]
- Path/ID:
deploy/terraform/modules/settlement/plan.tfplan(Hash:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855) - Freshness: 2026-09-15T14:22:00Z
- Reproducible Check:
terraform show -json deploy/terraform/modules/settlement/plan.tfplan | jq -e '.resource_changes[] | select(.type=="kubernetes_deployment") | .change.after.spec.template.spec.containers[0].security_context.read_only_root_filesystem == true'returns exit 0.
2. Policy Check Evidence [EVD-POL-01]
- Path/ID:
security/conftest/docker-policy.rego(Policy ID:POL-SEC-DOCKER-04) - Freshness: 2026-09-15T14:25:30Z
- Reproducible Check:
conftest test --policy security/conftest/ Dockerfileassertshadolintcompliance and exits 0.
3. Readiness Probe Evidence [EVD-PRB-01]
- Path/ID:
tests/integration/probes/readiness_spec.json(Probe ID:PRB-SETTLE-READY-01) - Freshness: 2026-09-15T14:30:15Z
- Reproducible Check:
curl -s -f http://127.0.0.1:8080/healthz/ready | jq -e '.status == "READY" and .db_connected == true'exits 0.
Invariants and Contracts
Non-Root Immutable User [INV-DOC-01]
The container image must execute strictly under unprivileged numeric UID:GID `65532:65532`.
Root execution (`UID 0`) or mutable user namespaces are strictly prohibited in the runtime stage.
Zero-Build-Artifact Runtime Leakage [INV-DOC-02]
Compilation compilers (gcc, musl-dev), git credentials, and intermediate cache layers must
never persist in the runtime stage. The final compressed runtime image size must not exceed 45 MB.
Direct Signal Propagation [INV-DOC-03]
The container entrypoint must use exec array form with `dumb-init` as PID 1 to ensure `SIGTERM`
signals propagate directly to the Go runtime, providing 25 seconds of clean ledger drain before termination.
BuildKit Secret Mount Isolation [INV-DOC-04]
Private repository tokens and build credentials must be mounted strictly via ephemeral
`--mount=type=secret`. Build arguments (`ARG`) or plain `COPY` of credentials are deterministically rejected.
Explicit Unknowns
- Performance impact of SQLite WAL checkpoints during high-concurrency 1,500 TPS bursts on ephemeral
tmpfsmounts (G-1). - Multi-architecture ARM64 build duration and QEMU emulation overhead in GitHub Actions runner pools (G-2).
Traceability
| Claim | Classification | Source | Freshness |
|---|---|---|---|
| Go 1.22 with SQLite CGO dependencies | provided | Intake specification | Current |
| Legacy image 1.2 GB, root user, 42 CVEs | provided | Incident INC-4110 record | Historical |
| Incident INC-4110 unhandled SIGTERM ledger corruption | provided | Post-mortem INC-4110 | Historical |
| Multi-stage build on Alpine with static Distroless runtime | decided | Sarah Chen & Marcus Vance | 2026-09-15 |
| Image size ceiling <= 45 MB (achieved 38.4 MB) | decided | Sarah Chen (AppSec Lead) | 2026-09-15 |
| Non-root user 65532:65532 | decided | Architectural decision MC-CC-01 | 2026-09-15 |
| BuildKit secret mount for git tokens | decided | Security policy POL-SEC-DOCKER-04 | 2026-09-15 |
| dumb-init signal supervisor with 25s termination grace | decided | Architectural decision MC-LC-01 | 2026-09-15 |
| Read-only root filesystem with 64 MiB tmpfs scratch | derived | SQLite scratch requirement + least privilege | 2026-09-15 |
| Base image digest pinning for builder and runtime | decided | Architectural invariant ADV-IE-01 | 2026-09-15 |
Verification
No validator was supplied, so no command was run.
Reviewer self-check against production Dockerfile contracts:
- Multi-Stage Hygiene: PASS. Build toolchains and private credentials completely isolated to builder stage; runtime image contains only compiled binary and supervisor.
- Security Hardening: PASS. Runtime executes under nonroot UID
65532:65532with a read-only root filesystem and zero shell binaries. - Lifecycle & Signals: PASS.
dumb-initexec array form guarantees cleanSIGTERMforwarding and 25-second graceful transaction drain. - Secret Isolation: PASS. BuildKit secret mounts prevent private git credentials from leaking into image layer histories.
- Adversarial Robustness: PASS. Explicit rejection mechanisms defined for manual drift, implicit environments, and missing readiness.
- Format Integrity: PASS. Follows native Markdown rules without escaped formatting characters.
Open Decisions
DEC-DOC-01: Marcus Vance to confirm whether multi-architecture manifest lists (linux/amd64andlinux/arm64) should be generated concurrently via GitHub Actions matrix runners or native ARM64 AWS Graviton builders (Owner: Marcus Vance).
Next steps
- Marcus Vance merges the multi-stage Dockerfile and
.dockerignoreinto thepayment-settlement-servicerepository. - Sarah Chen configures CI secret binding for
github_tokenin GitHub Actions BuildKit configuration. - DevOps platform team updates deployment Helm templates to enforce
readOnlyRootFilesystem: trueand mounts the 64 MiBtmpfsvolume at/tmp/settlement. - Conduct staging failure injection test issuing
docker stop --time 25under 500 TPS synthetic load to verify zero ledger transaction loss.
oci-image-and-dockerfile-contract-design.png
PNG · 1536×1024
Example file from a real run - the skill writes it into your workspace.
Connects securely to your tools. The creator never sees your data.
What you get
About this skill
What it does
This skill maps an accepted application artifact into reproducible OCI image build inputs and one container's runtime requirements. It defines Dockerfile semantics, image identity, process lifecycle, filesystem, network, configuration, resources and security without selecting an orchestrator.
Use it when
Use when one application needs an exact image-build and single-container execution contract after application/deployment authority exists.
For example: “Our Node.js media processing microservice crashes during graceful shutdown on Kubernetes because it runs as PID 1 via a shell script, and security scans flagged that the container runs as root with build compiler tools included in the production image.”
What you get
- Production Dockerfile Spec
Written as Markdown to <your output folder>/architecture/tasks/<run-id>/docker-design/.
What it will not do
Do not use for Compose/Kubernetes topology, CI/registry/supply-chain policy, implementation or debugging.
How it works
- Check OCI image packaging is required.
- Bound build context and multi-stage lifecycle.
- Pin base image identities.
- Formulate process execution contract.
- Establish non-root user and filesystem permissions.
- Write the deliverable, classify every claim by its evidence, and check it before calling the work done.
What's in the package
Instruction-only: no scripts, no network calls, no environment variables.
- LICENSE.txt
- SKILL.md
- agents/openai.yaml
- assets/output-template-task.md
- references/domain-rules.md
- references/operating-rules.md
- references/output-contract.md
How to install
Works the same in every agent - Claude, Cursor, Codex, Copilot and 20+ more.
- 1
Download the ZIP
Free skills download straight away. Paid skills unlock right after purchase.
- 2
Unzip into your skills folder
Every agent reads skills from one folder on your machine. Drop the unzipped folder in there.
- 3
Ask your agent to use it
Restart the agent if it was already running. It picks the skill up automatically - no config needed.
Skills folder by agent
Click the path to copy it. Create the folder if it does not exist yet.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
30-day refund
Not a fit? Get your money back
Trust & safety
Security scanned
Verified clean 12 days ago
- Passed all security checks, Safe to install