gha-container-proof is the GitHub Actions job container and Docker action compatibility oracle for offline CI. It turns jobs.<job_id>.container definitions, steps[*].uses: docker://... references, and local action manifests with runs.using: docker into deterministic receipts with stable check IDs.
It is part of Wildmason's offline GitHub Actions proof-tool lane:
gha-workflow-proofchecks workflow structure.gha-eventsmithcreates event/context fixtures.gha-expression-proofevaluates Actions expressions.gha-cache-proofmodelsactions/cache.gha-artifact-proofmodels artifact upload/download.gha-service-proofmodels service containers (jobs.<id>.services) and readiness.gha-container-proofmodels the primary job container and Docker action containers.gha-github-service-proofmodels the GitHub API/service surface.gha-runner-image-proofvalidates runner-image manifests and the toolcache.gha-command-proofchecks runtime command and environment-file side effects.
gha-container-proof does not duplicate gha-service-proof. If a workflow has both job containers and services, check-workflow classifies the job-container side and emits container.services.delegated pointing at the sibling tool.
cargo install gha-container-proof --lockedScan workflow YAML for job containers and Docker action surfaces.
gha-container-proof check-workflow `
--repo . `
--workspace . `
--workflow .github/workflows/ci.yml `
--format json `
--output target/container-proof.jsonWhen --workflow is omitted, the command scans .github/workflows/*.yml and .github/workflows/*.yaml. The scan detects:
jobs.<job_id>.containeras a bare image string or full object (image,credentials,env,ports,volumes,options).steps[*].uses: docker://image.steps[*].uses: ./local-actionwhoseaction.ymldeclaresruns.using: dockerwithimage: Dockerfile,image: docker://..., or a relative Dockerfile path.- Remote action refs (
owner/repo@ref) are classified as simulated — external action manifest unavailable unless mirrored or provided.
Classify a concrete rendered job-container request.
gha-container-proof plan-job \
--job-id build \
--runner-os linux \
--runs-on ubuntu-22.04 \
--container node:22-bookworm \
--env NODE_ENV=test \
--port 3000 \
--volume /host/cache:/cache \
--options "--cpus 2" \
--format json \
--output receipt.jsonClassify a concrete Docker action request.
gha-container-proof plan-action \
--action-ref ./actions/build-image \
--action-path ./actions/build-image \
--using docker \
--image Dockerfile \
--entrypoint /entrypoint.sh \
--pre-entrypoint /pre.sh \
--post-entrypoint /post.sh \
--args "build" \
--env INPUT_TARGET=release \
--format json \
--output receipt.jsonAlso supports docker:// action references without a local path:
gha-container-proof plan-action --action-ref docker://alpine:3.20 --image docker://alpine:3.20 --format jsonOptionally use the Docker CLI to verify runtime availability and image behavior. Offline by default — does not pull images unless --allow-pull is set.
gha-container-proof probe \
--image node:22-bookworm \
--runner-os linux \
--tool node --tool bash \
--command "node --version" \
--format json \
--output probe.jsondocker image inspect <image>records local image availability.docker run --rm <image> <tool> --versionanddocker run --rm <image> sh -c "<command>"record probe evidence.- Captures command kind, image, exit code, stdout/stderr excerpts, and elapsed time.
- Skips cleanly when the Docker CLI is not on PATH (
probe.docker_cli_not_found) and fails clearly when the daemon is unreachable (probe.docker_daemon_unreachable). - Override the Docker binary with
--docker-bin <PATH>or theGHA_CONTAINER_PROOF_DOCKERenvironment variable (used by tests to run against a fake docker).
- Container jobs require a Linux Docker runner; Windows and macOS hosted runners are unsupported.
- When a job runs in a container, shell steps execute inside the job container; networking is Docker-internal.
container.credentialsis recognized and classified as requiring secret-safe handling; credential values are redacted in receipts.container.optionsare parsed enough to flag known unsupported or dangerous options.--network/--netare unsupported under ci-forge-managed networking and emit a failure.--privileged,--pid=host,--ipc=host,--security-opt, and Docker socket mounts are flagged as risky.- Absolute Windows host paths (
C:\...,D:\...) mounted into Linux containers warn. --user,--workdir/-w,--entrypoint,--env/-e,--volume/-v,--cpus,--memory/-mare classified explicitly.- Unknown Docker options are classified as
simulated/unknownrather than silently accepted.
runs.using: dockeractions run in a new container for the action.runs.image: Dockerfile(or a relative*.Dockerfilepath) requires build support.runs.image: docker://...requires image availability.runs.pre-entrypoint,runs.entrypoint, andruns.post-entrypointare recognized.runs.argsis preserved.- Action
INPUT_*env can be passed through for receipts; the executor (ci-forge) owns post-state (STATE_*) handoff. - Dockerfile-backed actions detect missing Dockerfile, unsupported context, and build requirement.
- Job container on non-Linux runner → fail.
- Docker unavailable on probe → skip (
probe.docker_cli_not_found) or fail (probe.docker_daemon_unreachable). - Image missing locally while offline → fail unless
--allow-pullis set. --network/--netin options when ci-forge owns networks → fail.--privileged→ warn (or fail under--strict).- Host Docker socket mount → warn (high-risk; explicit compatibility).
- Absolute Windows host mount into Linux container → warn.
container.credentialsvalues → redacted; absence checked.- Unknown Docker options → warn (
container.options.unknown). - Remote Docker action manifest unavailable → simulated (
action.manifest.unavailable).
Every command emits the same top-level receipt:
{
"schema_version": 1,
"tool": { "name": "gha-container-proof", "version": "1.0.0" },
"checked_at": "2026-...",
"mode": "check-workflow",
"compatibility": "compatible",
"summary": { "passed": 0, "warnings": 0, "failed": 0, "skipped": 0 },
"subjects": [ ... ],
"checks": [ ... ]
}Each subject describes one job container, Docker action, or Docker probe:
{
"kind": "job-container",
"job_id": "build",
"image": "node:22-bookworm",
"runner_os": "linux",
"classification": "compatible",
"network_model": "ci-forge-managed",
"requires_docker": true,
"requires_build": false,
"requires_pull": false,
"checks": [
{ "id": "container.image.declared", "status": "pass", "message": "...", "details": {} }
]
}See docs/spec.md for the protocol surface and docs/RULES.md for stable check IDs.
- uses: wildmason/gha-container-proof@v1
with:
command: check-workflow
repo: .
workflow: .github/workflows/ci.yml
format: json
output: target/container-proof.jsonThe action installs the crate with Cargo unless gha-container-proof is already on PATH.
The CLI exits 0 when there are no failed checks. With --strict, warnings also fail the run.
gha-container-proof is a compatibility oracle, not a runner. It does not start long-lived job containers as the source of truth for execution — ci-forge owns the Docker lifecycle and attaches these receipts beside gha-service-proof, gha-cache-proof, gha-artifact-proof, gha-github-service-proof, and the rest.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.