← Back to blog
·2 min read·CI/CD

Docker Multi-Stage Builds in CI/CD Pipelines

Shrink images, speed up pipelines, and improve security with multi-stage Dockerfiles in GitLab CI.

Fat container images slow pulls, widen attack surface, and waste registry storage. Multi-stage builds separate build dependencies from runtime artifacts.

Build Pipeline Flow

Multi-Stage Dockerfile

# Stage 1: install dependencies FROM node:20-alpine AS deps WORKDIR /app COPY package-lock.json package.json ./ RUN npm ci # Stage 2: build application FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build # Stage 3: minimal runtime FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ USER nextjs EXPOSE 3000 CMD ["node", "server.js"]

BuildKit cache

Enable BuildKit and cache mounts in CI to avoid re-downloading dependencies on every pipeline run.

GitLab CI with BuildKit

variables: DOCKER_BUILDKIT: "1" build: stage: build image: docker:24 services: - docker:24-dind script: - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker push $CI_REGISTRY_IMAGE:latest

Image Size Comparison

Security Benefits

  1. No compiler or dev tools in production image
  2. Smaller surface for CVE scanners to flag
  3. Non-root user in final stage
  4. Distroless or Alpine for minimal OS packages

Conclusion

Multi-stage builds belong in every serious CI/CD pipeline. The upfront Dockerfile complexity pays off in faster deploys and safer production containers.

Related Articles