Guide
Multi-stage Dockerfiles that build fast and ship small
A Dockerfile that works is easy to write; one that rebuilds in seconds, produces a small image and does not run as root takes a few patterns most people learn the hard way. This generator writes multi-stage Dockerfiles that follow those patterns for six common stacks, along with a .dockerignore, and checks Dockerfiles you already have.
Layers and caching
Docker builds an image as a stack of layers, one per instruction, and reuses a cached layer as long as the instruction and its inputs have not changed. Order matters: if COPY . . comes before installing dependencies, every code change invalidates the install layer and the next build downloads everything again. The templates copy only the lockfile first, install, and copy the rest of the source afterwards.
Multi-stage builds
Each template has at least two FROM lines. The first stages install build tools and compile or bundle the app. The final stage starts again from a small runtime image and copies in only what the app needs to run. For Go, that runtime is distroless static, which contains no shell at all; for Java the build uses a JDK and the runtime a JRE; for a static site the build uses Node and the runtime is Nginx serving the dist folder.
The stack templates
The Node template supports npm, pnpm and Yarn through corepack and installs production dependencies again in the runtime stage. Python builds a virtual environment in one stage and copies it into a clean image, setting PYTHONUNBUFFERED so logs appear immediately. Rust compiles a dummy main.rs first so dependencies are cached separately from your code. Each template sets the port, the start command in JSON form and an optional HEALTHCHECK.
Base images and users
Slim Debian images are the default because they use glibc, which prebuilt native packages expect. The Alpine option gives smaller images using musl, which can break some binary wheels and Node modules, so test before switching. With “Run as a non-root user” on, the template uses the image’s existing node user, creates an unprivileged user, or picks a nonroot variant, so a compromised process cannot modify the image’s system files.
A worked example
With the Node defaults, the file starts with a deps stage FROM node:22-slim AS deps, installs with npm ci, builds in a second stage and creates a runtime stage that runs npm ci --omit=dev, copies dist, switches to USER node, exposes 3000 and starts with CMD ["node","dist/server.js"]. Switching to Go gives a final stage FROM gcr.io/distroless/static-debian12:nonroot. Pasting FROM node, COPY . ., RUN npm install and CMD npm start into the checker returns four suggestions: pin a tag, install dependencies before copying the source, use the JSON form of CMD and add a non-root user.
The checker
The checker reads an existing Dockerfile line by line and reports common problems: unpinned or latest images, apt-get install without --no-install-recommends or without cleaning the package lists, apt-get update in its own layer, ADD used for local files, shell-form CMD, sudo, RUN cd, secrets set with ENV, and the lack of any USER instruction. It covers frequent hadolint rules, not all of them.
Assumptions
Templates assume the lockfile sits at the project root and the build writes to dist or target. Monorepos and unusual layouts need the paths adjusted. Always build and run the image once locally before relying on it.
Related tools
- docker run to Compose turns run commands into a Compose service.
- .gitignore Generator keeps the same clutter out of Git.
- Kubernetes YAML Generator deploys the image to a cluster.
- .env Converter prepares runtime variables instead of baking them in.
Checked against the current version of the Dockerfile Generator on .

