- •Multi-stage Docker builds eliminate development dependencies from final production images, slashing image sizes from 1.2GB to <150MB.
- •Cache node_modules and Docker layers in GitHub Actions to reduce CI workflow execution times from 8 minutes to under 90 seconds.
- •Deploy updates using PM2 cluster reload to achieve zero-downtime rolling releases on production servers.
- •Implement automated health check steps that roll back deployments immediately if an endpoint returns a non-200 status code.
Deployment pipelines should operate predictably without manual intervention. Poorly structured configurations frequently fail due to unbudgeted memory limits during Next.js builds, slow un-cached Docker layer downloads, or environment inconsistencies between staging and production.
A reliable deployment pipeline combines multi-stage container optimization with automated GitHub Actions execution.
1. Multi-Stage Dockerfile for Next.js & Node.js
Separating dependency resolution, build compilation, and production runtime keeps final container images under 150MB:
# Stage 1: Dependency Installation
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production# Stage 2: Application Build FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/nodemodules ./nodemodules COPY . . RUN npm run build
# Stage 3: Minimal Production Runner (<150MB) FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000 CMD ["node", "server.js"] ```
2. Deterministic GitHub Actions Deployment Workflow
name: Production Deployment Pipelineon: push: branches: [ main ]
jobs: validate-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout Source Code uses: actions/checkout@v4
- name: Setup Node.js Environment uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm'
- name: Install Dependencies run: npm ci
- name: Run Type Check & Linter run: | npx tsc --noEmit npm run lint
- name: Deploy to Remote Server via SSH uses: appleboy/ssh-action@master with: host: ${{ secrets.EC2HOST }} username: ${{ secrets.EC2USER }} key: ${{ secrets.SSHPRIVATEKEY }} script: | cd /var/www/anuj-portfolio git pull origin main npm ci --only=production npm run build pm2 reload ecosystem.config.js --update-env curl -f http://localhost:3000/api/health || exit 1 ```
Using npm ci enforces exact package-lock.json checksums across environments, while PM2 cluster reloading seamlessly shifts active connections to updated worker threads without dropping incoming client requests.
Anuj Bansal
Anuj Bansal is a freelance full stack developer based in Indore, India specializing in scalable Next.js architectures, React web applications, Node.js backends, and high-performance server infrastructure. Looking to build a production-ready product? Hire Anuj for your next web application or SaaS platform.
Planning to Build a Production-Ready Web Application?
From system architecture and Next.js engineering to database optimization and technical SEO — let’s build a fast, scalable web product tailored to your business goals.