Set up a professional CI/CD pipeline: automated testing, Docker multi-stage builds, container registry, and rolling deployments on a VPS.

The Goal

Every git push to main should automatically: run tests โ†’ build a Docker image โ†’ push to registry โ†’ deploy to VPS with zero downtime. Total pipeline time: under 3 minutes.

1. Multi-Stage Dockerfile

Multi-stage builds keep the final image small (<200 MB):

dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
CMD ["node", "server.js"]

Enable output: 'standalone' in next.config.ts to generate the minimal server bundle.

2. GitHub Actions Workflow

Three jobs: test โ†’ build โ†’ deploy, each gated on the previous:

  • test: npm test + ESLint + TypeScript check
  • build: docker build, tag with github.sha, push to GHCR
  • deploy: SSH into VPS, pull new image, run rolling restart via Docker Compose
  • 3. Zero Downtime with Docker Compose + Health Checks

    Add a health check so Docker waits for the app to be ready before taking the old container down:

    yaml
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

    Use --detach --wait with docker compose up so the deploy step blocks until the new container passes health checks before removing the old one.

    4. Secret Management

    Store secrets in GitHub Actions Secrets (SSH key, registry token, env vars). Inject at deploy time โ€” never bake secrets into the Docker image.

    5. Nginx Reverse Proxy

    Sit Nginx in front of the Next.js container for TLS termination, HTTP/2, static asset caching, and as a load balancer if you scale to multiple replicas.

    Key Takeaways

    1. Multi-stage Docker builds dramatically reduce image size

    2. Always tag images with a unique identifier (commit SHA)

    3. Health checks are non-negotiable for zero-downtime deploys

    4. Keep secrets out of images โ€” inject at runtime

    5. Nginx + Certbot = free, automatic HTTPS