--- name: deployment-cicd description: Deploy applications with confidence using CI/CD pipelines, containerization, and infrastructure as code. Covers GitHub Actions, Docker, Vercel, and cloud deployment patterns. Triggers on deployment, CI/CD, Docker, GitHub Actions, or DevOps requests. license: MIT --- # Deployment & CI/CD Ship code reliably and automatically. ## Deployment Philosophy ### The Deployment Pipeline ``` Code → Build → Test → Stage → Production │ │ │ │ │ └───────┴───────┴───────┴────────┘ Automated, Repeatable ``` ### Principles 1. **Automate everything** - No manual steps 2. **Fail fast** - Catch issues early 3. **Rollback ready** - Always have an escape 4. **Environment parity** - Dev ≈ Staging ≈ Prod 5. **Observability** - Know what's happening --- ## GitHub Actions ### Basic Workflow ```yaml # .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: build-and-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: Run linter run: npm run lint - name: Run tests run: npm test - name: Build run: npm run build ``` ### Matrix Builds ```yaml jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [18, 20, 22] os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test ``` ### Deploy on Push to Main ```yaml name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm run build - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' ``` ### Secrets Management ```yaml env: DATABASE_URL: ${{ secrets.DATABASE_URL }} API_KEY: ${{ secrets.API_KEY }} # In GitHub: Settings → Secrets and variables → Actions ``` ### Caching ```yaml - name: Cache node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- ``` --- ## Docker ### Node.js Dockerfile ```dockerfile # Build stage FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY --from=builder /app/package*.json ./ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public EXPOSE 3000 CMD ["npm", "start"] ``` ### Docker Compose ```yaml # docker-compose.yml version: '3.8' services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgresql://postgres:password@db:5432/mydb depends_on: - db db: image: postgres:15-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" volumes: postgres_data: ``` ### .dockerignore ``` node_modules .git .gitignore README.md .env .env.* Dockerfile docker-compose.yml .next coverage ``` --- ## Platform Deployments ### Vercel (Next.js) ```json // vercel.json { "framework": "nextjs", "buildCommand": "npm run build", "outputDirectory": ".next", "env": { "DATABASE_URL": "@database-url" }, "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Cache-Control", "value": "no-store" } ] } ] } ``` ### Railway ```toml # railway.toml [build] builder = "nixpacks" buildCommand = "npm run build" [deploy] startCommand = "npm start" healthcheckPath = "/api/health" healthcheckTimeout = 100 restartPolicyType = "on_failure" restartPolicyMaxRetries = 3 ``` ### Fly.io ```toml # fly.toml app = "my-app" primary_region = "ord" [build] dockerfile = "Dockerfile" [http_service] internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 [[services]] internal_port = 3000 protocol = "tcp" [[services.ports]] port = 80 handlers = ["http"] [[services.ports]] port = 443 handlers = ["tls", "http"] ``` --- ## Environment Management ### Environment Files ``` .env # Shared defaults (committed) .env.local # Local overrides (gitignored) .env.development # Dev-specific .env.production # Prod-specific .env.test # Test-specific ``` ### Environment Variables Pattern ```typescript // lib/env.ts import { z } from 'zod'; const envSchema = z.object({ DATABASE_URL: z.string().url(), API_KEY: z.string().min(1), NODE_ENV: z.enum(['development', 'production', 'test']), }); export const env = envSchema.parse(process.env); ``` --- ## Database Migrations ### Prisma Migrations ```bash # Create migration npx prisma migrate dev --name init # Apply migrations (CI/CD) npx prisma migrate deploy # Generate client npx prisma generate ``` ### Migration in CI/CD ```yaml - name: Run database migrations run: npx prisma migrate deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} ``` --- ## Rollback Strategies ### Blue-Green Deployment ``` ┌─────────┐ Traffic → │ Blue │ ← Current └─────────┘ ┌─────────┐ │ Green │ ← New version (testing) └─────────┘ After verification: Switch traffic to Green ``` ### Canary Deployment ``` ┌─────────┐ 90% ────→ │ Current │ └─────────┘ ┌─────────┐ 10% ────→ │ New │ ← Monitor for issues └─────────┘ Gradually increase new version traffic ``` ### Instant Rollback (Vercel) ```bash # Rollback to previous deployment vercel rollback # Or via dashboard: Deployments → ... → Promote to Production ``` --- ## Health Checks ### Health Endpoint ```typescript // app/api/health/route.ts import { db } from '@/lib/db'; export async function GET() { try { // Check database await db.$queryRaw`SELECT 1`; return Response.json({ status: 'healthy', timestamp: new Date().toISOString(), version: process.env.APP_VERSION || 'unknown', }); } catch (error) { return Response.json( { status: 'unhealthy', error: 'Database connection failed' }, { status: 503 } ); } } ``` --- ## Complete CI/CD Pipeline ```yaml name: CI/CD on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '20' jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npm run lint test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npm test -- --coverage - uses: codecov/codecov-action@v3 build: needs: [lint, test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npm run build - uses: actions/upload-artifact@v3 with: name: build path: .next deploy-preview: if: github.event_name == 'pull_request' needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} deploy-production: if: github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' ``` --- ## References - `references/github-actions-recipes.md` - Common workflow patterns - `references/docker-patterns.md` - Docker best practices - `references/monitoring-setup.md` - Observability configuration