Quay lại blog
devops9 phút đọc

Docker và CI/CD Pipeline cho ứng dụng Next.js

Hướng dẫn đầy đủ containerise ứng dụng Next.js với Docker multi-stage build và thiết lập CI/CD pipeline tự động với GitHub Actions.

V
Bởi Ventra Rocket
·Đăng ngày 20 tháng 2, 2026
#Docker#CI/CD#GitHub Actions#Next.js#DevOps

Containerisation và CI/CD là hai trụ cột của DevOps hiện đại. Kết hợp Docker với GitHub Actions giúp tự động hoá hoàn toàn từ code commit đến production deployment, giảm thiểu lỗi human error và rút ngắn release cycle.

1. Dockerfile tối ưu cho Next.js

Multi-stage build giúp giảm image size từ ~1GB xuống còn ~150MB.

# Dockerfile
# Stage 1: Dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --only=production

# Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Tắt telemetry
ENV NEXT_TELEMETRY_DISABLED=1

RUN npm run build

# Stage 3: Runner (production image)
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy only necessary files
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

Bật standalone output trong next.config.ts:

const nextConfig = {
  output: 'standalone',
};
export default nextConfig;

2. Docker Compose cho môi trường local

# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: runner
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

volumes:
  postgres_data:

3. GitHub Actions CI/CD Pipeline

# .github/workflows/deploy.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Type check
        run: npm run typecheck

      - name: Lint
        run: npm run lint

      - name: Build
        run: npm run build
        env:
          NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to VPS via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            cd /opt/app
            docker compose pull
            docker compose up -d --remove-orphans
            docker image prune -f
            echo "Deploy completed at $(date)"

4. Secrets Management

Không bao giờ hardcode secrets vào Dockerfile hay docker-compose. Dùng GitHub Secrets cho CI/CD và .env files cho local (đã có trong .gitignore).

# Thêm secrets vào GitHub repo
gh secret set DATABASE_URL --body "postgresql://..."
gh secret set VPS_SSH_KEY < ~/.ssh/id_rsa
gh secret set NEXTAUTH_SECRET --body "$(openssl rand -base64 32)"

5. Health Check và Rollback

# Thêm health check vào docker-compose.yml
services:
  app:
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
// app/api/health/route.ts
export async function GET() {
  return Response.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version,
  });
}

Script deploy với auto-rollback:

#!/bin/bash
# deploy.sh
set -e

PREVIOUS_IMAGE=$(docker inspect app --format='{{.Image}}' 2>/dev/null || echo "")

docker compose pull
docker compose up -d --remove-orphans

# Kiểm tra health sau 30 giây
sleep 30
if ! curl -sf http://localhost:3000/api/health; then
  echo "Health check failed, rolling back..."
  docker compose down
  docker tag $PREVIOUS_IMAGE ghcr.io/org/app:latest
  docker compose up -d
  exit 1
fi

echo "Deploy successful"

Kết luận

Pipeline CI/CD với Docker và GitHub Actions giúp team tự tin deploy nhiều lần mỗi ngày. Với cấu hình trên, mỗi pull request tự động chạy tests, build Docker image và deploy lên production trong vòng 3-5 phút. Ventra Rocket áp dụng pipeline này cho tất cả dự án production của khách hàng.

Bài viết liên quan

Docker và CI/CD Pipeline cho ứng dụng Next.js | Ventra Rocket