AGENT SKILL

dockerfile-review

Dockerfile best practices — layer optimization, caching, security, image size. Use when writing, reviewing, or optimizing Dockerfiles, investigating bloated images, or fixing cache-busting build orders.

License 许可
MIT
最近更新
2026-11-11
Stars
★ 0
Forks
0

01 前言 Front Matter

name
dockerfile-review
metadata
{ "processkit": { "id": "SKILL-dockerfile-review", "created": 1775433600000, "version": "1.1.0", "category": "devops", "apiVersion": "processkit.projectious.work/v2" } }
description
Dockerfile best practices — layer optimization, caching, security, image size. Use when writing, reviewing, or optimizing Dockerfiles, investigating bloated images, or fixing cache-busting build orders.

02 资源文件 1

03 SKILL.md 正文

Dockerfile Review

Intro

A good Dockerfile is small, cache-friendly, reproducible, and does not run as root. Most problems come from three mistakes: ordering layers wrong (busting the cache), forgetting to clean up package manager state, and using ADD when COPY is what you meant.

Overview

Layer optimization

Every instruction creates a layer; layers are cached in order. Order instructions from least to most frequently changing so the cache stays warm. Combine related RUN commands with && to collapse layers. Keep a .dockerignore file so build context does not balloon with node_modules, .git, and local caches.

Caching dependencies

Copy dependency manifests and install before copying source code. If the manifests haven't changed, the install layer is reused:

COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

Pin dependency versions so builds are reproducible. Unpinned dependencies produce "works in CI, fails in prod" surprises.

Security

  • Don't run as root. Create a non-root user and USER into it after setup.
  • Never COPY secrets into an image. Use BuildKit build secrets or inject via runtime environment variables.
  • Pin base images by digest for supply-chain integrity: FROM python:3.12-slim@sha256:....
  • Remove package manager caches after installing (rm -rf /var/lib/apt/lists/*).

Size reduction

  • Start from slim or alpine base images where the ecosystem supports it.
  • Use multi-stage builds for compiled languages — the final stage should not contain the compiler.
  • Remove build tools after compilation.
  • Use --no-install-recommends with apt-get.

Correctness

  • Prefer COPY over ADD — ADD has magic behavior (URL fetch, auto-extraction) that usually surprises you.
  • Set WORKDIR instead of cd in RUN commands.
  • Use the exec form of CMD and ENTRYPOINT: ["executable", "arg"], not the shell form.

Gotchas

Agent-specific failure modes — provider-neutral pause-and-self-check items:

  • COPY . . before installing dependencies. Copying the full source tree before the dependency install step means any source file change invalidates the install cache, forcing a full reinstall on every build. Copy dependency manifests first (COPY requirements.txt .), install, then copy source — this way the install layer is reused unless the manifest changes.
  • Running the final image as root. A container running as root inside a compromised container has host-level privileges if the container runtime is misconfigured or if a vulnerability allows container escape. Always create a dedicated non-root user and USER into it before CMD/ENTRYPOINT. This is a single RUN useradd and USER instruction.
  • Using ADD with a local path instead of COPY. ADD with a local file path behaves like COPY but also auto-extracts tarballs and fetches URLs — surprising behavior that causes non-obvious build results. Use COPY for all local file copies; ADD is only appropriate for URL fetching or deliberate tarball extraction.
  • Unpinned base image tags (FROM python:latest). latest resolves to a different image every time the build runs on a new host or after an upstream push, producing non-reproducible builds. Pin to a specific version tag (python:3.12-slim) and ideally to a digest (@sha256:...) for full supply-chain integrity.
  • Shell-form CMD or ENTRYPOINT. CMD python app.py (shell form) runs the command inside /bin/sh -c, which means signals from the container runtime (SIGTERM on docker stop) are sent to the shell, not to the Python process. The process does not shut down gracefully. Use exec form: CMD ["python", "app.py"].
  • apt-get install and rm -rf /var/lib/apt/lists/* in separate RUN instructions. Docker layers are immutable snapshots. Removing the apt cache in a later RUN does not reclaim the space from the earlier layer — the files exist in the earlier layer's snapshot permanently. Combine apt-get update, apt-get install, and the cleanup into a single RUN command.
  • Secrets passed via ARG or ENV. Build arguments and environment variables are stored in the image's layer history and visible with docker history or docker inspect. Never pass API keys, passwords, or tokens via ARG or ENV at build time — use BuildKit's --mount=type=secret for build-time secrets, and runtime environment variables for run-time secrets.

Full reference

Canonical Python Dockerfile

# syntax=docker/dockerfile:1.7
FROM python:3.12-slim@sha256:... AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.12-slim@sha256:...
RUN useradd --create-home --uid 1000 app
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=app:app . .
USER app
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
ENTRYPOINT ["python", "-m", "myapp"]

Notable choices: multi-stage build so pip's build deps are not in the final image, explicit non-root user, COPY --chown so we don't need a later chown, exec-form entrypoint, environment variables set via ENV rather than inside the command.

Canonical apt cleanup

RUN apt-get update \
 && apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
 && rm -rf /var/lib/apt/lists/*

Always combine update, install, and cleanup into one RUN. A separate RUN rm -rf /var/lib/apt/lists/* does nothing for image size because the previous layer already committed those files.

Multi-stage build for Go

FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

Anti-patterns

  • COPY . . before installing dependencies — every source change busts the install cache
  • Running as root in production — add a user and USER into it
  • ADD with a local path — use COPY; reserve ADD for URL fetches and tar auto-extraction
  • latest base image tags — pin to a specific version, ideally by digest
  • Shell-form CMD / ENTRYPOINT — breaks signal handling; use the exec form
  • apt-get install without --no-install-recommends — pulls in tons of suggested packages
  • Secrets via ARG or ENV — they persist in image history; use BuildKit secrets (--mount=type=secret) instead
  • Missing .dockerignore — sends .git, node_modules, and everything else to the daemon as build context

Review checklist

  • Is the base image pinned (ideally by digest)?
  • Are dependencies installed before source is copied?
  • Is .dockerignore present and sensible?
  • Does the final stage run as non-root?
  • Are package manager caches removed in the same RUN?
  • Is ENTRYPOINT / CMD in exec form?
  • Are compile-time deps excluded from the final stage?
  • Is there any latest, any bare ADD, any secret in ARG/ENV?

04 同仓库更多 Skills