Quick reference for deployment — environment tiers, GitHub Actions and Dockerfile snippets, registry and Vercel commands, and a deployment strategy table.
CI/CDDockerVercelGitHub Actions
Environments
| Env |
Purpose |
Data |
| dev | Local, hot reload | Mock / seed |
| staging | Prod mirror for QA | Anonymised prod |
| prod | Live traffic | Real |
Build vs Runtime
| Build time | Runtime |
| Frozen into artifact | Read on process start |
NEXT_PUBLIC_* (public) | process.env secrets (server) |
Env Vars & Secrets
# .env.local — git-ignored, real values
DATABASE_URL=postgres://user:pass@host/db
STRIPE_SECRET_KEY=sk_live_xxx
NEXT_PUBLIC_API_URL=https://api.example.com
# .env.example — committed, keys only, no values
DATABASE_URL=
STRIPE_SECRET_KEY=
- Never commit secrets. Never put a secret behind
NEXT_PUBLIC_.
- Prod stores: AWS Secrets Manager, Vault, GitHub secrets, platform env vars.
- Rotate regularly, scope to least privilege.
CI/CD Pipeline
Stages (fail fast)
lint → test → build → deploy
(cheap, first) (only if all pass)
GitHub Actions
# .github/workflows/ci.yml
name: CI/CD
on:
push: { branches: [main] }
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Docker
Image vs Container
Image = immutable blueprint (built once). Container = running instance. Many containers from one image.
Multi-Stage Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
USER node
CMD ["npm", "start"]
Commands
docker build -t app:$GIT_SHA .
docker run -p 3000:3000 --env-file .env app:$GIT_SHA
docker compose up -d
docker ps # running containers
docker logs -f <container>
docker-compose
services:
app:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16-alpine
environment: { POSTGRES_PASSWORD: pass }
volumes: ["pgdata:/var/lib/postgresql/data"]
volumes: { pgdata: {} }
Registry
docker tag app:$GIT_SHA ghcr.io/acme/app:$GIT_SHA
docker push ghcr.io/acme/app:$GIT_SHA
docker pull ghcr.io/acme/app:$GIT_SHA
# tag by git SHA, not just :latest
Vercel & Cloud
Vercel CLI
vercel # preview deploy
vercel --prod # production
vercel env add KEY production
# Git push → auto preview per PR; main → prod; 1-click rollback
Cloud Models
| Model | You manage | Example |
| VM | OS + scaling | EC2 |
| Containers | Image + orchestration | ECS, EKS |
| Serverless | Just code | Lambda, Cloud Run |
Deployment Strategies
| Strategy | How | Trade-off |
| Rolling | Replace a few at a time | No downtime; two versions live |
| Blue-Green | Switch all traffic at once | Instant rollback; 2x infra |
| Canary | Small % first, ramp up | Small blast radius; needs metrics |
| Recreate | Stop old, start new | Simple; has downtime |
Rollback Rules
- Immutable, versioned artifacts → re-point, don't rebuild.
- Backward-compatible migrations: additive first, then code.
Monitoring & Health
Three Pillars
- Metrics — latency, error rate, throughput
- Logs — discrete events
- Traces — request path across services
Health Check
// app/api/health/route.ts
export async function GET() {
try {
await db.query("SELECT 1");
return Response.json({ status: "ok" }, { status: 200 });
} catch {
return Response.json({ status: "down" }, { status: 503 });
}
}
// liveness = process up | readiness = can serve
HTTPS & DNS
| Record | Maps to |
| A / AAAA | Domain → IPv4 / IPv6 |
| CNAME | Domain → hostname (custom domain) |
TLS: platforms auto-provision + renew (Let's Encrypt). Redirect HTTP → HTTPS, enable HSTS.