agentleFS
VelimirMuellerVelimirMueller/ai-development-skills/skills/infra/containerize-service/SKILL.md

containerize-service

Skill7 starsChanged 16 days agoMITView on GitHub

Use when a backend or full-stack service needs a production container image — multi-stage Dockerfile per track (Node/pnpm, Go, Python/uv), non-root, digest-pinned bases, health check, SBOM and provenance, image scan.

  • Reads credentials

3 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All checks pass and trivy is clean → say "already in place" and stop.

    line 25

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Containerize Service

One image per service, built once, run as a non-root user, healthy only when the service says so. The same image goes to dev, stg and prd (environments.md).

1. Audit current state (change nothing)

cat .claude/stack-profile.md 2>/dev/null                       # languages, package_manager, backend.track, hosting
ls Dockerfile* .dockerignore compose*.y*ml deploy/ 2>/dev/null
ls pnpm-lock.yaml go.mod pyproject.toml uv.lock 2>/dev/null     # which track(s)
grep -rn "healthz\|/health" --include=*.ts --include=*.go --include=*.py . 2>/dev/null | grep -v node_modules | head -3
docker --version && docker buildx version

Read from the profile: languages, package_manager (pnpm, go, uv), backend.track (hono, nextjs, go, fastapi), hosting. If there is no profile, detect from the lockfiles above. Ask one question only when two tracks match (a monorepo) and the choice changes the output: which service to containerize. Suggest set-up-stack-profile if the profile is missing.

2. Decide

  • No Dockerfile → full (steps 3 to 7).
  • Dockerfile exists → delta: check each line of the checklist below, fix only what fails.
  • All checks pass and trivy is clean → say "already in place" and stop.

Checklist: multi-stage; every FROM has @sha256:; final stage non-root; .dockerignore excludes .git, node_modules, .env*; HEALTHCHECK present; no secret in ENV, ARG or COPY; --frozen-lockfile, --locked or go mod download before source copy.

3. Detect the track

EvidenceTrackTemplate in dockerfile-patterns.md
package.json + pnpm-lock.yaml (Hono, Fastify, Nest)Node/pnpm"Node (pnpm)"
package.json with nextNode/pnpm, Next.js"Node (pnpm)" plus the standalone note
go.modGo static"Go"
pyproject.toml + uv.lock (FastAPI)Python/uv"Python (uv)"
npm/yarn/bun lockfilesame shape; swap the install linekeep the stage layout

Hosted on Vercel (hosting: [vercel]) and no worker or long-running process: no image needed. Stop and say so; use deploy-to-vercel.

4. Install only what is missing

Nothing on the host except Docker with BuildKit (docker buildx version). Do not install language toolchains for the image; the build stage carries them. Create .dockerignore first:

.git
node_modules
dist
.venv
__pycache__
bin
*.test
.env*
!.env.example
Dockerfile
.dockerignore

5. Generate the seams

  1. Health seam. The service exposes GET /healthz returning 200 when it can serve. The image probes it with the service's own binary, because distroless has no curl or shell. Code for each track is in dockerfile-patterns.md.
  2. Dockerfile from the track template. Set the three ARG … IMAGE= lines to the digests in stack-versions.md; re-read the digest with docker buildx imagetools inspect <image:tag> --format '{{.Manifest.Digest}}'.
  3. Graceful shutdown. The process handles SIGTERM and finishes in-flight requests within 10 s. Compose and Kubernetes send SIGTERM first.

6. Wire

  • Build locally with attestations and check them:
    docker buildx build --sbom=true --provenance=mode=max -t localhost:5005/app:test --push .
    Attestations need --push, or the containerd image store, or a docker-container builder. In CI the pipeline builds with provenance: false and signs once with actions/attest; see set-up-delivery-pipeline. Do not add a second build path here.
  • Scan with Trivy from a pinned image, not the GitHub Action (its tags were rewritten in March 2026):
    docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v trivy-cache:/root/.cache \
      ghcr.io/aquasecurity/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa \
      image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:test
    Grype (anchore/grype) is an equal choice if you already use Syft SBOMs.
  • Probes. Compose uses the image HEALTHCHECK. Kubernetes ignores it; define readinessProbe and livenessProbe against /healthz.
  • Compose and the host: deploy-to-hetzner, deploy-to-ionos.

7. Verify

docker build -t app:test . && docker image inspect app:test --format '{{.Config.User}}'

Expect a non-root user (65532, nonroot or 10001:10001), never empty or 0.

docker run -d --name app-test -p 3000:3000 app:test && sleep 12 && curl -fsS localhost:3000/healthz
docker inspect app-test --format '{{.State.Health.Status}}'     # healthy
docker rm -f app-test

Expect ok and healthy. Reproducibility check (same digest twice):

for i in 1 2; do docker buildx build --no-cache --provenance=false --sbom=false \
  --build-arg SOURCE_DATE_EPOCH=1760000000 --output type=oci,dest=/tmp/r$i.tar,rewrite-timestamp=true . ; done
for i in 1 2; do tar -xOf /tmp/r$i.tar index.json | grep -o 'sha256:[a-f0-9]*' | head -1; done

Expect two identical digests. Trivy exits 0.

References

  • container-patterns.md: the rules and why (base image, user, pinning, health, SBOM, scanning, secrets).
  • dockerfile-patterns.md: the three templates, built and run during authoring.
  • ../_shared/environments.md: build once, promote the digest.
  • ../../core/_shared/security-baseline.md, ../../core/_shared/engineering-principles.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.