Trang chủ / Blog / Từ Zero đến Production: Deploy Next.js với Docker & CI/CD

Từ Zero đến Production: Deploy Next.js với Docker & CI/CD

Từ Zero đến Production: Deploy Next.js với Docker & CI/CD
## Tại sao Docker + CI/CD là bắt buộc?

Trong thời đại cloud-native, việc deploy ứng dụng Next.js lên production không chỉ là \`git push\` và cầu nguyện. Bạn cần một quy trình chuẩn hóa, tự động hóa và kiểm soát được.

> "Works on my machine" không còn là lý do hợp lệ trong 2026.

## Buớc 1: Containerize với Docker

### Tạo Dockerfile cho Next.js

\`\`\`dockerfile
# Multi-stage build for Next.js
FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Dependencies
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Builder
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable pnpm && pnpm build

# Runner
FROM base AS runner
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

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

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

### Giải thích từng layer

- **Multi-stage build**: Giảm kích thước image từ 1.2GB xuống 150MB
- **Alpine Linux**: Base image nhẹ , bảo mật hơn Ubuntu
- **Non-root user**: Chạy app với quyền hạn chế, tăng bảo mật
- **Standalone output**: Chỉ copy những file cần thiết

## Buớc 2: Docker Compose cho local development

\`\`\`yaml
version: '3.9'
services:
  app:
    build:
      context: .
      target: runner
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:
\`\`\`

## Buớc 3: GitHub Actions CI/CD

### Workflow file: \`.github/workflows/deploy.yml\`

\`\`\`yaml
name: Deploy to Production

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

jobs:
  test:
    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: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build

  build-and-push:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: \${{ github.actor }}
          password: \${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/\${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: \${{ secrets.SERVER_HOST }}
          username: \${{ secrets.SERVER_USER }}
          key: \${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            docker pull ghcr.io/\${{ github.repository }}:latest
            docker-compose up -d --no-deps --build app
            docker system prune -f
\`\`\`

## Buớc 4: Deploy lên VPS / Cloud

### Option 1: VPS (DigitalOcean, Vultr, Linode)

1. **Chuẩn bị server**:

\`\`\`bash
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-\$(uname -s)-\$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
\`\`\`

2. **Setup Nginx reverse proxy**:

\`\`\`nginx
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host \$host;
        proxy_cache_bypass \$http_upgrade;
    }
}
\`\`\`

3. **SSL với Certbot**:

\`\`\`bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
\`\`\`

### Option 2: Kubernetes (GKE, EKS, AKS)

\`\`\`yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextjs-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nextjs
  template:
    metadata:
      labels:
        app: nextjs
    spec:
      containers:
      - name: app
        image: ghcr.io/yourorg/app:latest
        ports:
        - containerPort: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url
---
apiVersion: v1
kind: Service
metadata:
  name: nextjs-service
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 3000
  selector:
    app: nextjs
\`\`\`

## Monitoring & Observability

### Sentry cho error tracking

\`\`\`javascript
// sentry.client.config.js
import * as Sentry from "@sentry/nextjs";

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
});
\`\`\`

### Datadog cho metrics

Thêm Datadog Agent vào \`docker-compose.yml\`:

\`\`\`yaml
  datadog:
    image: gcr.io/datadoghq/agent:latest
    environment:
      - DD_API_KEY=\${DD_API_KEY}
      - DD_SITE=datadoghq.com
      - DD_LOGS_ENABLED=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /proc/:/host/proc/:ro
      - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
\`\`\`

## Kết luận

Với Docker + CI/CD, bạn đạt được:

- **Consistency**: Môi trường giống nhau từ local đến production
- **Automation**: Deploy tự động mỗi khi merge vào \`main\`
- **Rollback nhanh**: Chỉ cần rollback Docker tag
- **Scalability**: Dễ dàng scale horizontal với Kubernetes

Thời gian setup ban đầu 1-2 ngày, nhưng sau đó tiết kiệm hàng chục giờ mỗi tháng.
Phạm Duy Khánh

Phạm Duy Khánh

AI/ML Engineer · TechCorp

Chuyên gia machine learning và data engineering với background nghiên cứu NLP. PhD từ ĐH Bách Khoa Hà Nội, có 15+ publications về deep learning. Đã xây dựng recommendation systems phục vụ 5M+ users.

Bạn có dự án cần tư vấn?

Đội ngũ chuyên gia của chúng tôi sẵn sàng hỗ trợ bạn từ ý tưởng đến triển khai.