Tips & tricks
Make your Dockerfile smaller and your builds faster
Four Dockerfile habits that shrink images and speed up deploys: multi-stage builds, layer order, a .dockerignore and a non-root user.
· 1 min read
In short
- Use a multi-stage build so build tools never reach the final image
- Copy package files first so dependency installs are cached
- Add a .dockerignore and run as a non-root user
Every service in a monorepo is built from its own Dockerfile, so small mistakes multiply by the number of services. Four habits fix most of it.
1. Multi-stage builds
Build in one stage, ship from another. The final image keeps only what runs.
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
2. Order layers by how often they change
Docker caches each step. Copy the files that change rarely (package*.json) before the ones that change constantly (your source), so installs are reused.
3. Add a .dockerignore
Keep junk out of the build context:
node_modules
.git
.env
dist
4. Run as a non-root user
Most official images ship a low-privilege user. Switch to it before the final command:
USER node
Before and after
| Typical first attempt | After these four | |
|---|---|---|
| Final image contains | build tools, dev deps, git history | only the runtime and built code |
| Dependency install | on every deploy | cached until package.json changes |
| Secrets in layers | possible | excluded by .dockerignore |
For a full example in context, see deploying a Next.js app and API from one repo.
Frequently asked questions
Why is my Docker image so large?
Usually because build tools, dev dependencies and the whole source tree end up in the final image. A multi-stage build and a .dockerignore remove most of that.
Do smaller images deploy faster?
Generally yes. Less to build, push and pull means quicker deploys and less memory pressure on the platform.