## 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.DevOpsCloud
Chia sẻ