docker-conventions
Dockerfiles: base images, multi-stage builds, non-root USER, hadolint and Trivy.
Skill by leroyguillaume
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/leroyguillaume-docker-conventions
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: devops, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/docker-conventions
What's inside
- Skill name
docker-conventions- Description
- Dockerfiles: base images, multi-stage builds, non-root USER, hadolint and Trivy. TRIGGER when: editing or creating a `Dockerfile`, `Containerfile` or `.dockerignore`; user asks about base images, image size, container CVEs, non-root users or hadolint. SKIP when: no Dockerfile is touched and the user isn't asking about container builds.
- License
- MIT
- Instructions
- 186 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
# Docker conventions
**Always:**
- Create a `.dockerignore` file to exclude unnecessary files from the build
context (`.git/`, `node_modules/`, `*.log`, `README.md`, etc.).
- A Compose file is named per `docker-compose-conventions`.
- **Pick the smallest runtime base image the app can actually run on.** Every
package in the final image is a CVE waiting to be reported against you, and
the fastest way to fix a vulnerability is to not ship the package. Walk down
this ladder and stop at the first rung that works:
1. `scratch` — statically linked binaries (Rust with `musl`, Go with
`CGO_ENABLED=0`). Zero packages, zero CVEs, nothing to patch.
2. **Distroless** (`gcr.io/distroless/static`, `.../base`, `.../cc`) — when
you need libc, TLS roots, or `/etc/passwd` but no shell or package
manager. Prefer the `:nonroot` variants.
3. `alpine` — when you genuinely need a shell or a couple of apk packages.
4. `*-slim` (`debian:*-slim`, `python:*-slim`) — when glibc or a distro
runtime is unavoidable.
Full distro images (`debian`, `ubuntu`, `python:3.13`, `node:22`) are a
**builder-stage-only** thing; they never appear in the last `FROM`.
- **Always multi-stage.** Compilers, headers, package managers, and dev
dependencies stay in the builder; the runtime stage gets the artefact and
nothing else. If the final stage installs a toolchain, the split is wrong.
- **Distroless and `scratch` have no `groupadd`/`useradd`.** Two options, both
fine, and both land on the same UID 65532 as the rule below:
- use the upstream `:nonroot` tag and `USER nonroot:nonroot`;
- or create the user in the builder stage and copy `/etc/passwd` and
`/etc/group` over.
Either way the final stage still ends with an explicit non-root `USER`.
The copied entry names `/usr/sbin/nologin`, a binary the final image does
not contain — harmless, since an image with no shell and no `sshd` has no
login path to refuse in the first place.
- **Scan the built image in CI** (`trivy image`) and fail the build
on `HIGH`/`CRITICAL`, then **re-scan the published image daily** — a CVE
lands on an image nobody rebuilt (the scheduled re-scan is in
`ci-conventions`). A skinny base is what makes that gate cheap
enough to keep enabled.
- **Scan the `Dockerfile` itself with `trivy config`, and fix every finding.**
These are the `DS-xxxx` checks (`DS-0002` running as root, `DS-0026` missing
`HEALTHCHECK`, `DS-0001` `:latest` tag, `DS-0009` relative `WORKDIR`, …).
**Treat them as errors and fix at the source** — no self-authorised ignores
(`ci-conventions`). It is a `pre-commit` hook, so it also gates CI through
`quality`; a `repo: local`, `language: system` hook, never a Docker-backed
one (`pre-commit-conventions`):
```yaml
- repo: local
hooks:
- id: trivy-dockerfile
name: trivy config (Dockerfile)
entry: trivy config --exit-code 1 Dockerfile
language: system
pass_filenames: false
files: ^Dockerfile$
```
`trivy config` takes one target, so a repo with several `Dockerfile`s gets
one hook per file rather than passed filenames.
- **`trivy` does not replace `hadolint`. Run both.** They overlap on the
obvious stuff (absolute `WORKDIR`, `cd` in `RUN`, `--no-install-recommends`),
but each catches things the other is blind to:
- **Only `hadolint`**: apk package version pinning (`DL3018`) — the pinning
rule below has *no* Trivy equivalent, which by itself settles the
question; apt list cleanup (`DL3009`); JSON notation for
`ENTRYPOINT`/`CMD` (`DL3025`); and **ShellCheck on every `RUN` line**
(`SC2086` unquoted expansion, etc.), which Trivy does not do at all.
- **Only `trivy`**: missing `HEALTHCHECK` (`DS-0026`), plus severity levels
and the same report format as image/IaC scanning.
- **Keep the runtime lean beyond the base image too:** `--no-install-recommends`
for apt, `--no-cache` for apk, no `curl`/`wget`/`bash` "just for debugging",
and health checks that use the app itself rather than an extra binary.
- Follow Linux (FHS) path conventions, with `<appname>` the project name:
- **Build** in `/usr/local/src/<appname>` (the builder stage `WORKDIR`).
- **Binary** installed to `/usr/local/bin/` (on `PATH`).
- **Config**, if the app has any, under `/etc/<appname>/`.
- Any mutable state under `/var/lib/<appname>/`.
- Create a dedicated non-root user and group **named after the app** (use a
short, recognisable name if the project name is long or not a valid Linux
username), with **UID 65532 and GID 65532**, and end the `Dockerfile` with a
`USER` directive. The account's other properties — `nologin` shell, locked
password, no group it does not need — are `system-user-conventions`; the UID
is the one thing that differs here, and the next paragraph says why.
Why 65532 and not 1000: UID/GID must be **> 10000** (Trivy `KSV-0020` /
`KSV-0021`). Without user namespaces, a container UID *is* a host UID, and
1000 is the first human account on virtually every Linux box — so a
container escape, a `hostPath` mount, or a shared NFS export lands with
that user's identity. 65532 is the specific high UID upstream already
agreed on (`distroless:nonroot`, Chainguard), which keeps the number
identical whether you inherit the user or create it. Avoid 65534 — that's
`nobody`/`nogroup`, shared by every unmapped process and squashed-to by NFS.
Example (app `myapp`):
```dockerfile
WORKDIR /usr/local/src/myapp
# ... build, producing /usr/local/src/myapp/target/release/myapp ...
RUN groupadd --system --gid 65532 myapp \
&& useradd --system --uid 65532 --gid myapp \
--home /nonexistent --no-create-home --shell /usr/sbin/nologin myapp
COPY --from=builder \
/usr/local/src/myapp/target/release/myapp /usr/local/bin/myapp
USER myapp
ENTRYPOINT ["/usr/local/bin/myapp"]
```
- **`chown` only what the app writes — never the binary.** `COPY` without
`--chown` lands `root:root` `0755`, which is what an executable should be:
the process needs to *execute* it, not to own it, and a service that can
rewrite its own binary rewrites it once and is still there after every
restart. Give the app user only its state and cache paths, and prefer
`readOnlyRootFilesystem: true` at the orchestrator (see
`helm-conventions`) so the question stops arising.
- `--home` follows `system-user-conventions`: the state directory, or
`/nonexistent` with `--no-create-home`.
- Lint every `Dockerfile` with `hadolint`: the `hadolint` hook from the
`hadolint/hadolint` repo, never the Docker-backed `hadolint-docker`.
- **Fix every `hadolint` warning at the source.** A green run is the only
acceptable end state.
- **Pin `apk` package versions** (`apk add pkg=1.2.3-r4`) — this is DL3018, and
the answer is to pin, not to silence. Keeping the pins current is Renovate's
job, not a reason to skip them. Annotate each one so Renovate can see it:
```dockerfileFrom leroyguillaume/claude, MIT.
More from leroyguillaume/claude
- 0.624score
adr-conventions
leroyguillaume
Architecture Decision Records in `docs/adr/`.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
api-conventions
leroyguillaume
HTTP/RPC API payloads in any language: DTOs, JSON casing, pagination, OpenAPI docs.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
architecture-conventions
leroyguillaume
ARCHITECTURE.md: the system as it stands, in the present tense.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
argocd-conventions
leroyguillaume
Argo CD objects in any GitOps repository.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
ci-conventions
leroyguillaume
Platform-agnostic CI pipelines: quality gate, Trivy scans, path filters, caching, releases.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
contributing-conventions
leroyguillaume
CONTRIBUTING.md: dev setup, tests, pre-commit and CI.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
dependency-update-conventions
leroyguillaume
Dependabot and Renovate configuration and their PRs.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
diagram-conventions
leroyguillaume
Diagrams in committed files: format and diagram type.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
documentation-conventions
leroyguillaume
Docs a human can maintain: one home per fact, links not copies.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
frontend-conventions
leroyguillaume
Frontend code: TypeScript, React, Biome, mobile-first.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
github-actions-conventions
leroyguillaume
GitHub Actions mechanics: workflows, pinned actions, SARIF, release notes, Releases, Pages.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
github-issue-conventions
leroyguillaume
Where remaining work goes on a GitHub repo: an issue via `gh`.
SkillAFreshness: A★ 7Claude Code +9
Signals
- Stars
- 7
- Forks
- 3
- Open issues
- 0
- Last commit (this folder)
- Oct 2, 2026 (26 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/leroyguillaume/claude
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from leroyguillaume
- 0.624score
api-conventions
leroyguillaume
HTTP/RPC API payloads in any language: DTOs, JSON casing, pagination, OpenAPI docs.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
architecture-conventions
leroyguillaume
ARCHITECTURE.md: the system as it stands, in the present tense.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
argocd-conventions
leroyguillaume
Argo CD objects in any GitOps repository.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
ci-conventions
leroyguillaume
Platform-agnostic CI pipelines: quality gate, Trivy scans, path filters, caching, releases.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
contributing-conventions
leroyguillaume
CONTRIBUTING.md: dev setup, tests, pre-commit and CI.
SkillAFreshness: A★ 7Claude Code +9 - 0.624score
adr-conventions
leroyguillaume
Architecture Decision Records in `docs/adr/`.
SkillAFreshness: A★ 7Claude Code +9
Cite this page
docker-conventions skill. Agent Mart. https://agentmart.dev/skills/leroyguillaume-docker-conventions (last updated 2026-10-24)