feat(isolation): add generic devenv sandbox image built and published to GHCR - #395
Conversation
… to GHCR Generalized from login.gov Team Data's production template (per #394): Nix (single-user) + devenv + direnv on a parametrized BASE_IMAGE, with shell and opencode variants. No baked proxy CA — runtime trust stays a kit's job; an EXTRA_CA_CERT build arg keeps behind-proxy local rebuilds possible. Minimal apt payload (xz-utils, ca-certificates): with Nix baked, kits and devenv.nix deliver tools at create/run time. CI builds a multi-arch manifest (amd64 + arm64 on native runners, push-by-digest + merge, registry layer cache) and pushes to GHCR on path-filtered pushes to main plus manual dispatch. Consumed on both backends via ACQ_IMAGE=ghcr.io/gsa-tts/agentic-coding-patterns/ devenv-{shell,opencode}:<tag>. Closes #394
Path-filtered pull_request trigger runs the same 2x2 matrix without publishing: no GHCR login, no digest push, no cache write (fork PRs lack packages:write, and PR runs must not write the shared layer cache). Each job loads its single-arch image and smoke-tests devenv/direnv/nix as the agent user, so amd64 and the arm runner path are exercised before merge; the merge job stays main-only.
This reverts commit a7ddf0a.
…PATH Self-review findings: put the Nix profile on ENV PATH so the toolchain is visible to non-interactive execs (bash -c never reads .bashrc); sha256-pin the Nix installer script (release assets are mutable); narrow the publish trigger to Dockerfile/VERSION so docs-only changes can't repoint published tags, and make the merge job refuse to reuse an already-published version tag; gate publishing on the main ref so a branch workflow_dispatch builds without touching production tags.
wz-gsa
left a comment
There was a problem hiding this comment.
Adversarial review — LGTM, supply chain independently verified end-to-end
This is exactly the kind of PR worth checking every pin on, not just reading the Dockerfile for shape. I verified every integrity claim live rather than trusting the comments.
Supply-chain pins — all verified against the real sources:
- Nix installer SHA-256: fetched the actual release asset (
nix-installer.shv0.27.0) and hashed it — matchesNIX_INSTALLER_SHA256exactly. - nixpkgs rev (
ac62194c...): resolves to a real commit on therelease-25.05backport branch, not a dangling/garbage SHA. - All 6 pinned GitHub Actions (
checkout,setup-buildx-action,login-action,build-push-action,upload-artifact,download-artifact): dereferenced every tag through the GitHub API — every full-length commit SHA in the workflow matches its stated tag exactly. - zizmor: 0 medium/high findings; the 2
--pedantic-only low findings (missing comments adjacent topackages: write) are style, not substance — the actual security reasoning for that scope is present elsewhere in the workflow.
Reviewed the two things that looked riskiest on first read and both check out:
apt-get update || true— the fail-open is correctly scoped to just theupdatestep. The&&-chain still propagates a genuineapt-get installfailure (e.g. if the failed update left no cachedxz-utils), so a real installability problem still aborts the build loudly; only the proxy-blocked-update case is absorbed, matching the comment's justification.- Multi-user installer + same-layer
chown -R agent:agent /nix— this is the standard container workaround for Nix's installer (no clean single-user-from-scratch path in this installer generation), and doing the chown in the sameRUNas the install (not a separate layer) correctly avoids doubling the image size with a redundant/nixlayer.
Version-immutability guard is sound. docker buildx imagetools inspect before push, refusing to republish an existing version tag; concurrency: group: devenv-image-${{ github.ref }} with cancel-in-progress: false serializes runs on the same ref rather than racing them, so a concurrent second push/dispatch queues instead of creating a TOCTOU window between the inspect-check and the manifest push.
Scope discipline matches the README's own framing — confirmed this is deliberately NOT registered in kits.yaml (it's a base image, not a kit), and the Dockerfile bakes only the base-agnostic toolchain (Nix/devenv/direnv), correctly pushing runtime CA trust, team config, and store sizing to kits that layer on top — consistent with the "delegated to kits" design Basilio described.
Nice design detail worth calling out: EXTRA_CA_CERT defaults empty for CI builds (a public generic image should never bake a proxy CA) and is documented as a local-rebuild-only escape hatch — the right default for something published to a public registry.
All 8 CI checks green. Approve.
AI-assisted (OpenCode).
Summary
Adds a generic devenv sandbox base image under
integrations/isolation/images/devenv/— Nix (single-user) + devenv + direnv on a parametrizedBASE_IMAGE— plus a CI workflow that builds a multi-arch (amd64 + arm64) manifest for two variants (devenv-shell,devenv-opencode) and publishes them to GHCR. Teams consume it withACQ_IMAGE=ghcr.io/gsa-tts/agentic-coding-patterns/devenv-{shell,opencode}:<tag>on either backend, replacing the load-a-4.7-GB-tar-per-machine flow.Eligibility & Accountability
@GSA-TTS/agentic-coding-teamcode-owner approval; eligibility is attested here, not authenticated). Anyone may open issues without this.AI Assistance Disclosure
Type of Change
integrations/)Motivation
Repos using devenv need Nix + devenv + direnv baked into the sandbox image — create-time installs are impractical (multi-GB Nix closure; egress policies rightly block package mirrors). login.gov Team Data has run this template in production since June, but distribution was per-machine tar loads. Nothing in the image is team-specific, so it belongs here as a published, generic artifact kits layer on. Design per #394: no baked proxy CA (runtime trust stays the
zscaler-ca-certificatekit's job;EXTRA_CA_CERTbuild arg keeps behind-proxy local rebuilds possible), minimal apt payload (with Nix baked, kits anddevenv.nixdeliver tools), team config stays in kits.Testing
make validatelocally (all checks pass)devenv-shellvariant locally (native arm64, behind Zscaler viaEXTRA_CA_CERT); smoke test as theagentuser resolvesdevenv 1.11.2,direnv 2.36.0,nix 2.24.9from the baked.bashrcwiring.Safety Checklist
Validation Results
Related Issues
Closes #394
Additional Context
main, plusworkflow_dispatch), so the first GHCR publish happens after merge; the new packages will need visibility flipped to public once created.