/docker-workflow
internalWhat it is
Multi-stage Dockerfile patterns, Docker Compose for local development, and image security scanning with vulnerability triage.
Called by
/gaia-dev-story(when Docker is in the stack)- TypeScript, Angular, Java, Python, and mobile stack dev agents
What it does
Provides multi-stage Dockerfile patterns for different stacks (Node.js, Java, Python) with build and runtime separation.
Documents Docker Compose patterns for local development environments.
Covers image security scanning and vulnerability triage workflows.
When you will see it surface
- When a story involves Docker changes, the dev agent loads these patterns for guidance.
Technical notes
- Separate build dependencies from runtime -- smaller images, reduced attack surface.
- Run containers as non-root users.
- Use .dockerignore to exclude unnecessary files.
The bundled gaia-tools deterministic-tools image
GAIA ships a single OCI image (ghcr.io/gaiastudio-ai/gaia-tools) that bundles every Tier-2 brownfield scanner with pinned versions and a freshly-warmed Grype vuln-DB. When brownfield.tools.runner: docker is set in .gaia/config/project-config.yaml, the brownfield Phase-3 adapters dispatch through this image instead of resolving binaries from $PATH — so operators on a stock machine need only Docker, not brew + pip + npm + Go SDK + Java SDK + .NET Sarif.Multitool.
Bundled tools (v0.1.4 / 2026-06-02)
| Tool | Tier | Version | Purpose |
|---|---|---|---|
grype | 2 | 0.79.5 | CVE scanner (any stack) |
syft | 2 | 1.4.1 | SBOM producer (any stack) |
osv-scanner | 1 | 1.7.4 | OSV CVE scan |
spotbugs | 2 | 4.8.4 | JVM dead-code + bug finder |
vulture | 1 | 2.13 | Python dead-code |
pip-audit | 1 | 2.7.3 | Python CVE |
cyclonedx-py | 1 | 4.4.3 | Python SBOM |
cdxgen | 1 | 10.11.0 | Node SBOM |
yamllint | 1 | 1.35.1 | YAML workflow lint |
yq (Mike Farah) | — | 4.44.1 | YAML query |
Sarif.Multitool | — | 5.0.2 | Phase-7 SARIF merge (package-name + input-path mapping ensure merged output contains real findings) |
Enabling the docker runner
Set the runner switch via the canonical config editor:
/gaia-config-brownfield set tools.runner docker
/gaia-config-brownfield set tools.image ghcr.io/gaiastudio-ai/gaia-tools:0.1.4-2026-06-02
Or hand-edit .gaia/config/project-config.yaml:
brownfield:
deterministic_tools: true
tools:
runner: docker # default: native
image: ghcr.io/gaiastudio-ai/gaia-tools:0.1.4-2026-06-02
Image override env var: for one-off scans against a non-default image without touching config, export GAIA_TOOLS_IMAGE before invoking the brownfield run. The env-var beats the config key. Useful for testing a not-yet-published image tag.
Pulling / building the image
CI publishes the image on push to main and on a monthly cron for vuln-DB freshness — see .github/workflows/gaia-tools-image.yml. The canonical pull path:
/gaia-doctor --install --docker
If docker pull fails with denied (registry visibility issue) or the published image lags the in-tree Dockerfile (the monthly cron has not yet republished), self-build locally:
docker build -t ghcr.io/gaiastudio-ai/gaia-tools:latest plugins/gaia/tools/gaia-tools/
That tag matches the default docker-runner.sh resolves when brownfield.tools.image isn't set, so the brownfield adapters route through the local image without further config.
Build prerequisites: the Dockerfile itself installs everything it needs; the HOST building the image needs ONLY Docker (with buildkit if you want multi-arch via docker buildx). The image's runtime stage bundles the .NET 8 SDK (for Microsoft.Sarif.Multitool) and the native runtime dependency libicu72 — both are installed inside the image, not on the host. Multi-arch builds (linux/amd64 + linux/arm64) work from any Docker host with QEMU emulation enabled (Docker Desktop ships this on by default; on a bare Linux host run docker run --privileged --rm tonistiigi/binfmt --install all once).
History of build-blocker classes: earlier image revisions resolved several Dockerfile blockers and baked in GRYPE_DB_VALIDATE_AGE=false. Adding the .NET SDK layer for Sarif.Multitool briefly introduced three new blockers — a missing libicu72 runtime dependency; a wrong NuGet package name (Microsoft.Sarif.Multitool, where the actual tool package is Sarif.Multitool); and an unset DOTNET_ROOT — all since fixed. A fresh build from the current branch succeeds end-to-end, and the sarif --version probe at the end of the .NET layer verifies the install before the build accepts the image.
What runs through the runner
Every Tier-2 and applicable Tier-1 brownfield step routes through the docker runner when tools.runner=docker:
- grype — CVE scan (via
scripts/adapters/grype/adapter.sh). - syft — SBOM producer (Phase-3 SBOM step in
plugins/gaia/skills/gaia-brownfield/SKILL.md). - python-vulture — Python dead-code (
scripts/adapters/dead-code/python-vulture/adapter.sh). - spotbugs — JVM dead-code (
scripts/adapters/dead-code/jvm-spotbugs/adapter.sh). - Sarif.Multitool — Phase-7 SARIF merge (
scripts/adapters/brownfield/sarif-merge.sh). - go-deadcode — probes the runner first; bundled image does not yet ship the binary (Go toolchain off-policy), so it falls through to host-PATH with a clear INFO log when neither is present.
Mount + network contract
The runner (plugins/gaia/scripts/lib/docker-runner.sh) dispatches with:
docker run --rm --network=none \
-v "$PROJECT_ROOT:/workspace:ro" \
-v "$ADAPTER_OUT_DIR:/out" \
-w /workspace \
$TOOLS_IMAGE $SUBCMD $ARGS
Inside the container, the project source is read-only at /workspace, SARIF + JSON output lands at /out, and --network=none prevents accidental phone-home. The bundled grype's bypass of the vuln-DB age check is what lets it scan without network — pre-warmed DB is good for the lifetime of the image tag.