Use the same Dockerfile for both local development and production with multi-stage builds
This is a follow-up to my earlier post on using Docker for local development. One problem we kept running into at Anyfin was maintaining two separate Dockerfiles - one for local development and one for production. Whenever we updated a dependency, we had to update both files, risking inconsistencies between environments.
Multi-stage builds solve this cleanly.
The problem
The typical setup involved separate files:
api/
├── index.js
├── package.json
├── Dockerfile ← production
└── Dockerfile.development ← local dev
With a docker-compose for local development pointing at Dockerfile.development and CI pointing at Dockerfile. Two files to maintain, easy to drift out of sync.
The solution: multi-stage builds
Multi-stage Dockerfiles let you define multiple images from a single file using the target parameter. The idea: put shared dependencies in a base stage, and layer production-only steps into a prod stage.
Single Dockerfile:
FROM node:16-alpine as base
RUN apk add --update graphicsmagick
FROM base as prod
WORKDIR /home/node/app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "index.js"]
docker-compose.yml for local development:
api:
build:
context: "./api"
target: "base"
command: sh -c "yarn install && yarn start"
volumes:
- ./api:/home/node/app:cached
Production build:
docker build . -t api:latest
What changed
- Local development uses the
basestage via thetargetparameter - gets the system dependencies, mounts source via volumes - Production builds the full
prodstage - copies and installs only production dependencies - Single source of truth: bump Node version in one place and both environments update
Key benefits
- No manual synchronization between Dockerfiles
docker-compose up apiworks for local developmentdocker build . -t api:latestbuilds the production image- The base system (OS packages, global tools) is identical in both environments
This pattern has worked well for us across multiple services at Anyfin and has saved us from several "works locally but breaks in prod" incidents that were caused by environment drift.