Stop 07 · Containers & DockerTopics · 2/9

Dockerfile Best Practices

Part of the DevOps Roadmap

In short

A sloppy Dockerfile gives you 1 GB images, 10-minute builds and a container running as root. A tidy one is small, fast to rebuild and safer, with the same app inside.

The instructions for building a Docker image — writing an efficient, secure Dockerfile involves minimizing layers, using specific base image versions, and not running as root by default.

The mascot measures spoonfuls of spice into a big pot with a recipe card propped up beside it.

Think of it like this

The caterer's recipe card

A good wedding caterer doesn't write 'add some masala'. The card says which brand, how many spoons, in what order. Anyone in the kitchen gets the same biryani. A Dockerfile is that card: exact versions, exact steps, same image every time.

Watch it happen

Same app, two Dockerfiles

Sloppy

  • FROM node:latest, full Debian image
  • COPY . . before npm install
  • Runs as root, ships dev tools

Tidy

  • FROM node:22-alpine, pinned and small
  • Copy package files first, cache npm ci
  • USER node, production deps only

Same code inside, but the tidy image is smaller, rebuilds in seconds after a code change and gives an attacker far less to work with.

The big ideas

What you need to know

Pin a small base

Use a specific tag like node:22-alpine or a slim image. Smaller bases mean faster pulls and fewer packages with known holes.

Order for the cache

Put things that rarely change (installing dependencies) above things that change often (your source code).

Use .dockerignore

Exclude node_modules, .git, .env and build output so they never enter the build context or the image.

Don't run as root

Switch to a normal user with USER. If the app is hacked, the attacker gets far fewer powers inside the container.

See it in code

Walk through the code

dockerfile
1FROM node:22-alpine2WORKDIR /app3 4# Dependencies first, so this layer is cached5COPY package.json package-lock.json ./6RUN npm ci --omit=dev7 8# App code changes often, so copy it last9COPY . .10 11# Don't run as root12USER node13EXPOSE 300014CMD ["node", "server.js"]

1/4A pinned, small base image and a fixed working directory for every later step.

Pair this with a .dockerignore containing node_modules, .git and .env. The official node images already include a non-root user called node.

Save yourself the pain

Common mistakes

Oops

Baking secrets into the image with COPY .env or ENV API_KEY=....

Do this instead

Pass secrets at runtime through environment variables or a secrets manager. Anyone who pulls the image can read its layers.

Oops

Writing CMD npm start in shell form and wondering why the container takes 10 seconds to stop.

Do this instead

Use exec form, CMD ["node", "server.js"], so your app is PID 1 and gets the stop signal directly.

Your mission

Shrink an image

~30 min

Quick check

Why copy package.json before the rest of the source code?

Go deeper

  1. 1Write a Dockerfile for a simple application and build an image from it.
  2. 2Learn why pinning a specific base image version (not `latest`) matters for reproducibility.
  3. 3Practice reducing image size by cleaning up unnecessary files in the same layer they were created.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge