The Problem: Build Tools Don't Belong in Production
When you build a Go binary, a compiled Rust service, or a React app, you need compilers, build tools, and dev dependencies. But once the build is done, none of that is needed at runtime. Without multi-stage builds, all of it ends up baked into your production image — inflating its size by hundreds of megabytes.
Multi-stage builds solve this cleanly: use one image to build your artifact, then copy only the artifact into a minimal final image.
How Multi-Stage Builds Work
A multi-stage Dockerfile contains multiple FROM instructions. Each FROM starts a new stage. You can COPY --from= to pull files from previous stages. Only the last stage becomes your final image.
Example 1: Go Service
A Go application with dependencies can produce a completely static binary:
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .
# Stage 2: Runtime
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["/server"]Using FROM scratch means the final image contains only the binary and TLS certificates. A typical Go service image drops from ~300 MB to under 10 MB.
Example 2: React Frontend
# Stage 1: Build React app
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:1.25-alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]The final image is a lean nginx image with your static files — Node.js and all dev dependencies are gone. Image size typically drops from ~900 MB to ~25 MB.
Example 3: Node.js with Compiled Native Modules
# Stage 1: Build (includes compilers for native modules)
FROM node:20-alpine AS builder
RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production runtime
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]Build tools (python3, make, g++) are only in the builder stage.
Naming and Targeting Stages
Name stages with AS for readability and to enable partial builds:
# Build only up to the 'builder' stage (useful for CI caching)
docker build --target builder -t my-app:builder .
# Build the full image
docker build -t my-app:latest .This is useful in CI pipelines where you want to cache the dependency installation stage separately.
Caching in CI with BuildKit
Enable BuildKit for better layer caching:
export DOCKER_BUILDKIT=1
docker build --cache-from my-app:builder -t my-app:latest .Or with docker buildx:
docker buildx build --cache-from type=registry,ref=my-app:cache --cache-to type=registry,ref=my-app:cache,mode=max -t my-app:latest .Impact on Deploy Speed
Smaller images mean:
- Faster pushes to registries
- Faster pulls on deploy targets
- Lower storage costs
- Reduced attack surface (fewer packages = fewer CVEs)
A 10 MB image deploys in seconds. A 900 MB image can take minutes — and those minutes multiply across every deploy and every horizontal scale-out event.
Deploying Multi-Stage Builds on PandaStack
PandaStack's container deployment picks up your Dockerfile directly from GitHub and builds it in the platform — multi-stage builds are fully supported with no extra configuration.
npm install -g @pandastack/cli
panda deploy --type container --repo your-org/your-repoPandaStack handles the build, registry push, and deployment. Visit [dashboard.pandastack.io](https://dashboard.pandastack.io) to monitor your deployments or check [docs.pandastack.io](https://docs.pandastack.io) for the full container deployment guide.