RMS Meta

RMS Meta

Code to cloud for people & nature

Blog · Dec 25, 2025

Dockerfile Syntax: Writing Lean, Secure Images the Right Way

A good Dockerfile is clear, repeatable, and produces a small, secure image. Here’s how to write one the right way.

Core syntax & instructions

  • FROM base: Choose a minimal, trusted base (e.g., alpine or an official distro). Example: FROM python:3.12-slim.
  • WORKDIR /app: Set the working directory for subsequent instructions and runtime.
  • COPY package*.json ./ : Copy only what you need; use .dockerignore to keep the context small.
  • RUN commands: Install deps, build assets. Chain related commands to reduce layers and clean caches (e.g., apk add --no-cache ...).
  • ENV VAR=value: Set environment variables for runtime defaults.
  • EXPOSE 3000: Document the port (doesn’t publish it).
  • USER appuser: Drop root; run as a non-root user.
  • CMD ["node","server.js"]: Default runtime command (only one CMD is used—the last one).
  • ENTRYPOINT ["myapp"]: The fixed executable; combine with CMD for default args.
  • HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1: Define a container health probe.

Order matters (cache-friendly layout)

  1. FROM (base image)
  2. WORKDIR
  3. Copy dependency manifests first (e.g., package*.json, requirements.txt)
  4. RUN install deps (leverages cache if manifests unchanged)
  5. COPY the rest of the source
  6. RUN build/compile steps
  7. ENV, EXPOSE, USER, HEALTHCHECK
  8. CMD/ENTRYPOINT

Multi-stage builds (preferred)

Use a builder stage to compile, then copy only the outputs to a slim runtime stage.

# Stage 1: build
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: runtime
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["node","dist/server.js"]

Benefits: smaller images, no build tools in production, faster pulls.

ENTRYPOINT vs CMD

  • ENTRYPOINT sets the binary; CMD sets default args.
  • Together: ENTRYPOINT ["myapp"] + CMD ["--port","8080"] → default command myapp --port 8080; users can override CMD args.

Security & hygiene

  • Use minimal trusted bases; pin versions.
  • Don’t bake secrets into images; use env/secret stores at runtime.
  • Run as non-root; set USER.
  • Keep .dockerignore updated to avoid shipping build artifacts and secrets.
  • Add HEALTHCHECK for better orchestration behavior.
  • Label images (LABEL org.opencontainers.image.source=…) for traceability.

Common pitfalls

  • Using ADD when COPY is enough (avoid unintended URL fetch/extract).
  • Reinstalling deps every build because COPY order changed—copy manifests first.
  • Leaving build tools in the runtime image—use multi-stage.
  • Running as root in production—set USER.

Quick checklist

  • Minimal base + multi-stage
  • Cached deps (copy manifests early)
  • Non-root USER, HEALTHCHECK
  • Clean build artifacts; small runtime image
  • .dockerignore in place; no secrets in image

Follow this structure and you’ll get lean, secure images that build fast and run predictably in CI/CD.