ROHITH.

DevOps7 Min Read

Implementing CI/CD for a Next.js App
Using GitHub Actions

Deploy manually? SSH in, pull changes, restart, hope nothing breaks. I used to do that too — until I automated it with GitHub Actions, Docker, and a VPS.

CI/CD Pipeline Illustration
Background

What is CI/CD?

CI (Continuous Integration) automatically checks your code when you push it.

CD (Continuous Deployment) automatically deploys your code to production.

Instead of manually deploying, the system handles everything.

Architecture

My Deployment Setup

  1. I push code to GitHub
  2. GitHub Actions runs lint, tests, and build
  3. If everything passes, a Docker image is built
  4. The image is deployed to my VPS
  5. The application restarts automatically

No manual SSH. No risky deployment steps.

Step 01

CI — Automatic Code Validation

Every Pull Request to main runs:

  • Install dependencies (pnpm)
  • Run ESLint
  • Run tests
  • Build the Next.js app

If any step fails, the PR cannot be merged.

name: CI

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v2
        with:
          version: 8

      - uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build
Why this matters:
  • Lint catches code issues early
  • Tests prevent breaking features
  • Build ensures production compilation works
Step 02

Dockerizing the App

Instead of running Node directly on the server, I used Docker.

Benefits:

  • Same environment everywhere
  • Easy deployment
  • Clean restarts
  • No "works on my machine" problems
# Build Stage
FROM node:18-alpine AS builder

WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm
RUN pnpm install

COPY . .
RUN pnpm build

# Production Stage
FROM node:18-alpine

WORKDIR /app
COPY --from=builder /app ./

EXPOSE 3000
CMD ["pnpm", "start"]
Step 03

CD — Automatic Deployment

When code is merged into main, deployment starts automatically.

  1. Build Docker image
  2. Push image
  3. SSH into VPS
  4. Pull latest image
  5. Restart container
name: Deploy

on:
push:
    branches: [main]

jobs:
deploy:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Build Docker Image
        run: docker build -t myapp:latest .

    - name: Deploy to Server
        uses: appleboy/ssh-action@v1.0.3
        with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USER }}
        key: ${{ secrets.SERVER_SSH_KEY }}
        script: |
            docker pull myapp:latest
            docker stop myapp || true
            docker rm myapp || true
            docker run -d --name myapp -p 3000:3000 myapp:latest
Security

Managing Secrets

I stored sensitive values inside GitHub Secrets:

  • Server IP
  • SSH private key
  • Docker credentials
  • Environment variables
Never commit secrets to Git. Even one leaked SSH key can compromise your server.
Lessons

Mistakes I Faced

  • CI was slow due to missing dependency caching
  • SSH key permission issues
  • Large Docker image size initially
  • Environment variable misconfiguration

Each issue helped improve the pipeline.

Result

Final Result

  • No manual deployments
  • Consistent builds
  • Faster releases
  • More confidence in production

CI/CD is not optional for production applications. Automation reduces risk and improves reliability.


RJ

Rohith Jayaraj

Full-Stack & DevOps Engineer

Work With Me