MediumConcept

How do you write a good Dockerfile? What are multi-stage builds?

Technology:
Docker
Experience:
Junior,
Mid-level

Quick answer

Use a small, pinned base image, order instructions so dependencies are cached before source code, use multi-stage builds to leave build tools out of the final image, run as a non-root user and keep secrets out of the image.

Detailed explanation

Layer caching drives build speed. Copy the dependency manifest and install dependencies first, then copy the source code. Changing a source file then reuses the cached dependency layer instead of reinstalling everything.

A multi-stage build uses one stage with compilers and dev dependencies to build the app, then copies only the output into a slim runtime stage. The final image is smaller, starts faster and has a smaller attack surface.

Other habits: pin base image versions, add a .dockerignore (exclude node_modules, .git, local env files), run as a non-root user, use CMD in exec form so signals reach your process, and pass secrets at runtime or with build secrets rather than ENV or COPY.

Example

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

# runtime stage
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Key points

  • Copy dependency files before source code for caching
  • Multi-stage builds keep build tools out of production images
  • Pin versions, use .dockerignore, run as non-root
  • Never bake secrets into image layers

Common mistakes

  • COPY . . before installing dependencies, which breaks caching.
  • Using the latest tag, so builds change without warning.
  • Deleting a secret in a later layer and assuming it is gone. It still exists in the earlier layer.

Follow-up questions

  • What is the difference between CMD and ENTRYPOINT?
  • DockerEasy

    What is the difference between a Docker container and a virtual machine?

    A virtual machine runs a full guest operating system on virtualised hardware, while a container shares the host鈥檚 kernel and isolates only the process, its filesystem and resources, which makes containers much lighter and faster to start.

    Intern 路 Junior 路 DevOps
  • DockerEasy

    What is the difference between a Docker image and a container?

    An image is a read-only template made of layers that contains the application and its dependencies; a container is a running (or stopped) instance of an image with its own writable layer on top.

    Intern 路 Junior 路 DevOps
  • DockerEasy

    What are Docker volumes and why do you need them?

    Volumes are storage managed by Docker that lives outside a container鈥檚 writable layer, so data such as database files survives when the container is removed or replaced.

    Junior 路 Mid-level 路 DevOps
  • DockerMedium

    What is Docker Compose used for?

    Docker Compose defines a multi-container application (services, networks and volumes) in one YAML file so you can start, stop and rebuild the whole stack with a single command, typically for local development and testing.

    Mid-level 路 Senior 路 DevOps
  • ReactEasy

    What is the virtual DOM in React and how does it work?

    The virtual DOM is a lightweight JavaScript copy of the UI that React compares with the previous version after each update, so it only changes the parts of the real DOM that actually differ.

    Intern 路 Junior 路 Performance
  • Node.jsEasy

    What is the difference between package.json and package-lock.json?

    package.json lists your project's dependencies with allowed version ranges, while package-lock.json records the exact versions that were installed so every machine gets an identical dependency tree.

    Intern 路 Junior 路 Programming Fundamentals