ericrisco/rsc-harness/docker
- community
Checking that the SKILL.md file is still in the repository.
Use when authoring or auditing a Dockerfile, shrinking a bloated image, hardening a container that runs as root, picking a base image, or wiring a Compose dev loop with hot reload. NOT CI builds or deploy-to-host (that is `deployment`), NOT k8s autoscaling (that is `scaling`), NOT app-level injection or secrets-in-code (that is `secure-coding`).
Install
npx vskill@latest install ericrisco/rsc-harness/dockerSKILL.md
docker — container-craft
You own one question, deeply: how do I turn this app into a small, secure, fast-building image — and run a frictionless local stack with Compose? Your artifacts are the Dockerfile, the .dockerignore, and the compose.yaml: their layers, their attack surface, their build cache, their dev loop. You do not touch what happens after the image is built.
The hand-off is sharp: the moment you have a green, scanned image and a working `compose.yaml`, you are done. Everything downstream is ../deployment/SKILL.md — it already references Dockerfiles and leans on you for the image internals.
When NOT to use — and who owns it instead
| You're asked for… | Owner | Why it's not you | |
|---|---|---|---|
| CI build, registry push, deploy to host, rollback | ../deployment/SKILL.md | You build + harden the image; it moves the image through CI to prod | |
| k8s manifests, HPA autoscaling, ingress | scaling | Orchestration above a single image | |
| App SQL injection, secrets in source, dep CVE triage | ../secure-coding/SKILL.md | You cover only image/container hardening | |
| Install/operate a self-hosted PaaS | ../coolify/SKILL.md | Host operation, not the image | |
| Containerless PaaS deploy mechanics | ../railway/SKILL.md, ../fly-io/SKILL.md | Platform deploy, not the Dockerfile |
Pick a base image (2026 reality)
| Base | Size | CVE / patch velocity | Debuggable? | Wins when | |
|---|---|---|---|---|---|
*-slim (e.g. bookworm-slim) | ~30–80 MB | moderate, glibc | yes (shell, apt) | pragmatic default, native deps / Python wheels | |
distroless (gcr.io/distroless/*) | ~2–25 MB | convenient but patches slower | no shell | static/compiled runtimes, want minimal surface | |
| Chainguard / Wolfi | tiny, glibc | lowest live CVE count, SLSA L3 attestations | minimal | security-first; real scans found high-sev CVEs where the Chainguard equivalent had zero | |
alpine | ~5 MB, musl | small surface | yes (apk) | tiny static services — but musl breaks many Python wheels / native deps | |
scratch | 0 | nothing to patch | no | a fully static binary (Go), nothing else |
Rule of thumb: glibc app with native deps → *-slim or Wolfi. Static Go binary → scratch or distroless/static. Security mandate → Chainguard. Reach for alpine only when you've confirmed your wheels/native libs build against musl. Per-language tag maps, multi-stage templates for the stacks not shown below (Rust, JVM jlink, static SPA → nginx) and multi-arch buildx live in references/base-images-and-stages.md.
Multi-stage skeletons
Node — `npm ci` with a cache mount → distroless nonroot. Bad: single-stage node:20 ≈ 1.1 GB. Good: this ≈ 180 MB. Tags track Node 24 (the active LTS as of mid-2026); Node 22 is in Maintenance LTS, so swap 24→22 only when you deliberately want the conservative maintenance line.
Python — `uv` into a venv → slim, non-root. Bad: python:3.12 + pip ≈ 1 GB. Good: ≈ 130 MB.
Go — static binary → `scratch`. Bad: golang:1.26 ≈ 900 MB. Good: ≈ 12 MB. Pin a supported toolchain: Go maintains only the latest two minors (1.26/1.25 as of mid-2026), so a golang:1.23 build image is two minors past EOL and no longer gets security patches.
The cache-mount target differs per ecosystem: /root/.npm, /root/.cache/uv (or /root/.cache/pip), /go/pkg/mod. The cache persists across builds without bloating any layer.
.dockerignore — load-bearing, not optional
Without it the entire context (including .git, node_modules, .env) ships to the daemon: it slows the build and can bake secrets into a layer via COPY ..
Mirror .gitignore, then add build outputs and the Dockerfile itself.
Harden the runtime
Non-root in the image (already shown above): create a user, COPY --chown, USER. Then lock the runtime down where the container actually runs:
--read-only rootfs + --tmpfs /tmp for scratch space; no-new-privileges blocks setuid escalation; --cap-drop=ALL then add back only what you truly need (--cap-add=NET_BIND_SERVICE to bind port < 1024).
Build secrets — never `ARG`/`ENV`. An ARG TOKEN lands in the image history and any ENV SECRET=... persists in a layer. Use a BuildKit secret mount; it is never written to a layer:
Compose for dev
Canonical filename is compose.yaml; omit the obsolete `version:` key; use docker compose (v2 subcommand), never the standalone docker-compose v1. One concern per container — the app, the database and the seed job are separate services. develop.watch is GA since Compose 2.22.0 and gives you a tight dev loop.
Run the dev loop with docker compose watch (or docker compose up --watch). Fuller patterns — watch-per-stack, multi-service healthcheck graphs, override files, env_file vs secrets, and when to graduate to deployment/scaling — are in references/compose-recipes.md.
Verify the image
These are exactly what scripts/verify.sh automates (it skips a tool gracefully if absent):
hadolint Dockerfile— static lint of the Dockerfile (no error-level findings).docker compose config -q— thecompose.yamlparses and resolves.trivy image myimage:1.4.0— CVE + misconfig + secret scan of the built image.trivy config .— scan the Dockerfile/compose for misconfiguration without a build.dockle myimage:1.4.0— CIS-style image hygiene.- size check — assert the final image is under your threshold (
docker image inspect -f '{{.Size}}').
Anti-patterns
| Bad | Good | Why | |
|---|---|---|---|
FROM node:latest | FROM node:24-bookworm-slim, or pin a digest | :latest drifts under you; builds + scans become non-reproducible | |
runs as root (no USER) | USER nonroot / USER 10001 | PID 1 defaults to root (UID 0), so a container escape == host compromise | |
ARG TOKEN / ENV SECRET= | RUN --mount=type=secret,... | ARG/ENV persist in image history & layers | |
COPY . . then RUN npm ci | copy lockfile → install → copy source | source edits bust the dep cache layer every build | |
single FROM, ship the SDK | multi-stage, copy only the artifact | runtime carries compilers & dev CVE surface | |
apt-get install x | apt-get install --no-install-recommends x && rm -rf /var/lib/apt/lists/* | recommends + apt lists bloat the layer | |
no .dockerignore | mirror .gitignore + build outputs | whole context ships; .env/.git can leak | |
version: "3.8" / docker-compose v1 | omit version:; docker compose v2 | the key is obsolete; v1 is EOL | |
CMD npm start (shell form) | CMD ["node", "server.js"] (exec) | shell form wraps the process in /bin/sh -c, which swallows SIGTERM — no graceful shutdown | |
no HEALTHCHECK | HEALTHCHECK CMD [...] | nothing for Compose/orchestrators to gate readiness on (condition: service_healthy) |
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/docker/SKILL.md
Popularity
- Repository stars
- 141
- Repository forks
- 11
- 7-day trend score
- 30
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/ericrisco/rsc-harness/docker)