UNPKG

opencode-agent-kit

Version:

Multi-stack OpenCode agent toolkit — 33+ specialized AI agents, 200+ skills, 46 commands, 8 MCP servers (Nuxt, React, Node.js, Laravel, CI3, Android, Flutter, DevOps, SEO, SonarQube, and more)

988 lines (801 loc) 26.6 kB
--- name: devops-cicd-pipeline description: Complete CI/CD pipeline patterns for DevOps Engineer GitHub Actions, Docker multi-stage build, Docker Compose, Vercel (Nuxt & Next.js), Cloudflare Pages/Workers, environment/secret management, health check & monitoring, rollback strategy. Gunakan skill ini untuk semua tugas deployment, pipeline, dan infrastruktur frontend/backend. license: MIT metadata: author: Nous Research DevOps Team version: "1.0.0" --- # DevOps CI/CD Pipeline Skill referensi untuk **@devops** DevOps Engineer. Stack: Docker, GitHub Actions, Vercel, Cloudflare, monitoring. --- ## 1. GitHub Actions Workflow Patterns Pipeline standar terdiri dari 6 stage: **Lint Test Build Security Scan Deploy Verify**. ### 1.1 Workflow — Test ```yaml name: Test on: push: branches: [main, develop] pull_request: branches: [main] jobs: lint-and-typecheck: 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 run typecheck test: needs: lint-and-typecheck runs-on: ubuntu-latest strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - run: npm run test -- --coverage - uses: actions/upload-artifact@v4 with: name: coverage-report path: coverage/ ``` ### 1.2 Workflow — Build ```yaml name: Build on: push: branches: [main, develop] jobs: build: 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 build - uses: actions/upload-artifact@v4 with: name: build-output path: | .output/ .next/ dist/ - name: Build Docker Image run: docker build -t ${{ secrets.REGISTRY }}/app:${{ github.sha }} . ``` ### 1.3 Workflow — Deploy (Environment Promotion) ```yaml name: Deploy on: workflow_run: workflows: ["Build"] types: [completed] branches: [main, develop] jobs: deploy-staging: if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: staging steps: - uses: actions/checkout@v4 - run: echo "Deploying to staging..." - name: Smoke Test run: curl --fail ${{ vars.STAGING_URL }}/api/health deploy-production: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - run: echo "Deploying to production..." - name: Health Check run: curl --fail ${{ vars.PRODUCTION_URL }}/api/health ``` **Environment Variables in CI:** - `vars.*` non-sensitive env vars (diatur via GitHub UI: Settings Environments `environment` name) - `secrets.*` sensitive values (API keys, tokens, passwords) - Gunakan `vars.` (bukan `secrets.`) untuk non-sensitive per-environment config ### 1.4 Branch Strategy & Environment Mapping | Branch | Environment | Job Trigger | Approval | |--------|-------------|-------------|----------| | `feature/*` | Development | Push & PR | Auto | | `develop` | Staging | Push | Auto | | `main` | Production | Push | Manual approval gate (environment protection rule) | | `hotfix/*` | Production (expedited) | Push | Manual approval | --- ## 2. Docker Multi-Stage Build ### 2.1 Multi-Stage Dockerfile (Nuxt / Next.js) ```dockerfile # === STAGE 1: Install dependencies === FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --only=production # === STAGE 2: Build === FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Nuxt 4 RUN npm run build # or for Next.js: RUN npm run build # === STAGE 3: Production === FROM node:20-alpine AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs COPY --from=builder --chown=nextjs:nodejs /app/.output ./.output USER nextjs EXPOSE 3000 ENV NODE_ENV=production \ PORT=3000 \ HOST=0.0.0.0 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 CMD ["node", ".output/server/index.mjs"] ``` ### 2.2 Backend / Express Dockerfile ```dockerfile FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 appuser COPY --from=builder --chown=appuser:nodejs /app/dist ./dist COPY --from=deps --chown=appuser:nodejs /app/node_modules ./node_modules USER appuser EXPOSE 3001 ENV NODE_ENV=production \ PORT=3001 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD node dist/health.js || exit 1 CMD ["node", "dist/index.js"] ``` ### 2.3 Docker Best Practices - **Gunakan alpine base image** minimal footprint, reduced attack surface - **Jangan pakai `latest` tag** pin versi eksplisit: `node:20-alpine`, `node:22-slim` - **Non-root user** selalu buat user non-root (`adduser`) di stage final - **COPY only what's needed** jangan copy `node_modules`, dev dependencies, atau source map di stage final - **Layer caching** urutkan COPY dari yang paling jarang berubah (package.json sebelum source code) - **`--chown`** set ownership ke non-root user saat COPY - **`HEALTHCHECK`** instruction wajib untuk production image - **Resource limits** set `--memory` dan `--cpus` saat run container --- ## 3. Docker Compose untuk Dev / Prod ### 3.1 docker-compose.yml (Development) ```yaml version: "3.9" services: frontend: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" volumes: - .:/app - /app/node_modules environment: - NODE_ENV=development - NUXT_PUBLIC_API_URL=http://localhost:3001 depends_on: - backend backend: build: context: ./backend dockerfile: Dockerfile.dev ports: - "3001:3001" volumes: - ./backend:/app - /app/node_modules environment: - NODE_ENV=development - DATABASE_URL=postgresql://user:pass@db:5432/app - PORT=3001 depends_on: db: condition: service_healthy db: image: postgres:16-alpine ports: - "5432:5432" environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=app volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d app"] interval: 5s timeout: 5s retries: 5 volumes: pgdata: ``` ### 3.2 docker-compose.prod.yml (Production) ```yaml version: "3.9" services: app: image: ${REGISTRY}/app:${IMAGE_TAG:-latest} restart: unless-stopped ports: - "${PORT:-3000}:3000" env_file: - .env.production healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 15s deploy: resources: limits: cpus: "1" memory: "512M" reservations: cpus: "0.5" memory: "256M" logging: driver: "json-file" options: max-size: "10m" max-file: "3" nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro - certbot-data:/var/www/certbot depends_on: - app ``` ### 3.3 Run Commands ```bash # Development (hot-reload) docker compose up -d docker compose logs -f # Production docker compose -f docker-compose.prod.yml --env-file .env.production up -d # Rebuild & restart service docker compose up -d --build --no-deps frontend # Rollback to previous image tag docker compose -f docker-compose.prod.yml up -d app # Then manually: docker tag previous-image app:latest ``` --- ## 4. Vercel Deployment (Nuxt & Next.js) ### 4.1 Nuxt 4 / Vue — Vercel ```json // vercel.json Nuxt 4 { "framework": "nuxtjs", "buildCommand": "nuxt build", "outputDirectory": ".output", "installCommand": "npm install", "regions": ["sin1", "iad1"], "headers": [ { "source": "/_nuxt/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ] } ``` **Env vars (Vercel Dashboard Project Settings Environment Variables):** - `NUXT_PUBLIC_API_URL` public, client-side accessible - `NUXT_PUBLIC_SITE_URL` canonical URL - `DATABASE_URL` secret, server-only - `NITRO_PRESET` `vercel` (default, otomatis) **Nuxt 4 Specific:** - SSR: `nuxt build` `.output/` Vercel serverless functions - Static: `nuxi generate` `dist/` Vercel static hosting - `nuxt.config.ts`: pastikan `ssr: true` atau atur per-route dengan `routeRules` - Server routes di `server/api/` otomatis jadi serverless functions ### 4.2 Next.js 15 / React — Vercel ```json // vercel.json Next.js 15 { "framework": "nextjs", "buildCommand": "next build", "outputDirectory": ".next", "installCommand": "npm install", "regions": ["sin1", "iad1"], "crons": [ { "path": "/api/cron/cleanup", "schedule": "0 0 * * *" } ] } ``` **Env vars:** - `NEXT_PUBLIC_*` client-side accessible - Non-prefixed vars server-only - `NEXT_RUNTIME` `nodejs` (default) or `edge` **Next.js 15 Specific:** - App Router: `next build` server components + client bundle - Middleware: `middleware.ts` di root edge runtime - ISR: `revalidate` option di fetch / `generateStaticParams` - PPR (Partial Prerendering): set `experimental.ppr = true` di `next.config.ts` ### 4.3 GitHub Actions → Vercel Deploy ```yaml name: Deploy to Vercel on: push: branches: [main] jobs: deploy: 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 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' ``` Atau gunakan Vercel CLI: ```yaml - name: Install Vercel CLI & Deploy run: | npm i -g vercel vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} ``` ### 4.4 Preview Deployments (Pull Requests) Vercel otomatis buat preview deployment untuk setiap PR. Konfigurasi di GitHub: ``` Settings Environments Preview Deployment branches: * ``` GitHub Action untuk PR preview: ```yaml name: Vercel Preview on: [pull_request] jobs: preview: 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 }} - name: Comment Preview URL uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body: `🚀 Preview: ${process.env.VERCEL_URL}` }) ``` --- ## 5. Cloudflare Pages / Workers ### 5.1 Cloudflare Pages (Nuxt / Next.js) ```toml # wrangler.toml — Cloudflare Pages name = "my-app" compatibility_date = "2025-01-01" pages_build_output_dir = ".output" # Nuxt # pages_build_output_dir = ".next" # Next.js [env.production] routes = [ { pattern = "*.example.com", zone_id = "<zone-id>" } ] [env.preview] routes = [ { pattern = "*.preview.example.com", zone_id = "<zone-id>" } ] ``` **Nuxt di Cloudflare Pages:** - Build command: `npm run build` (dengan preset `cloudflare-pages` di `nuxt.config.ts`) - `nuxt.config.ts`: `nitro: { preset: 'cloudflare-pages', esbuild: { options: { target: 'es2022' } } }` - Output: `.output/public/` diupload sebagai static assets - Server functions: `.output/server/` Cloudflare Functions **Next.js di Cloudflare Pages:** - Gunakan `@cloudflare/next-on-pages` - Build command: `npx @cloudflare/next-on-pages` - Output dir: `.vercel/output/static` - Limitations: tidak semua Next.js API didukung (App Router sebagian, ISR terbatas) ### 5.2 Cloudflare Workers ```toml # wrangler.toml — Worker name = "my-worker" main = "src/worker.ts" compatibility_date = "2025-01-01" [env.production] vars = { API_URL = "https://api.example.com" } [[d1_databases]] binding = "DB" database_name = "my-db" database_id = "<id>" [[kv_namespaces]] binding = "CACHE" id = "<id>" ``` ```typescript // src/worker.ts export default { async fetch(request: Request, env: Env): Promise<Response> { const url = new URL(request.url); // CORS headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } // Health check if (url.pathname === '/health') { return new Response(JSON.stringify({ status: 'ok', timestamp: Date.now() }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } // API proxy if (url.pathname.startsWith('/api/')) { const apiUrl = `${env.API_URL}${url.pathname}${url.search}`; const response = await fetch(apiUrl, { headers: request.headers }); return new Response(response.body, { status: response.status, headers: corsHeaders, }); } return new Response('Not Found', { status: 404 }); }, }; ``` ### 5.3 GitHub Actions → Cloudflare Deploy ```yaml name: Deploy to Cloudflare on: push: branches: [main] jobs: deploy: 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 build - name: Deploy to Cloudflare Pages uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy .output/public --project-name=my-app ``` ### 5.4 Deploy Worker ```yaml - name: Deploy Worker uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} command: deploy src/worker.ts --name my-worker --env production ``` --- ## 6. Environment Variables & Secret Management ### 6.1 Environment Variable Hierarchy (Priority) 1. **Runtime** (process.env / platform env vars dashboard) highest priority 2. **`.env.production`** production-specific 3. **`.env.staging`** staging-specific 4. **`.env.development`** development-specific 5. **`.env.local`** local overrides (`.gitignore`) 6. **`.env`** defaults ### 6.2 Template File ```bash # .env.example — committed, contains placeholder values NUXT_PUBLIC_API_URL=http://localhost:3001 NUXT_PUBLIC_SITE_URL=http://localhost:3000 DATABASE_URL=postgresql://user:pass@localhost:5432/app JWT_SECRET=change-me-in-production REDIS_URL=redis://localhost:6379 SENTRY_DSN= ``` ### 6.3 Platform-Specific Secret Storage | Platform | Secret Store | How | |----------|-------------|-----| | GitHub Actions | Repository Secrets | Settings Secrets and variables Actions | | GitHub Actions | Environment Secrets | Settings Environments pilih env Secrets | | Vercel | Environment Variables | Project Settings Environment Variables | | Cloudflare | Secrets via Wrangler | `wrangler secret put <KEY>` | | Cloudflare | Environment Variables | Dashboard Worker Settings Variables | | Docker Compose | `.env` file | `docker compose --env-file .env.production up` | | Production | Secrets Manager | AWS Secrets Manager / HashiCorp Vault / Doppler / 1Password | ### 6.4 Validasi Env Vars di Startup ```typescript // validate-env.ts const REQUIRED_ENV_VARS = [ 'DATABASE_URL', 'JWT_SECRET', 'NUXT_PUBLIC_API_URL', ]; function validateEnv() { const missing: string[] = []; for (const key of REQUIRED_ENV_VARS) { if (!process.env[key]) { missing.push(key); } } if (missing.length > 0) { console.error(`❌ Missing required environment variables: ${missing.join(', ')}`); process.exit(1); } console.log(' All environment variables validated'); } validateEnv(); ``` ### 6.5 Secret Rotation Schedule | Secret Type | Recommended Rotation | |-------------|---------------------| | JWT Secret | Every 90 days | | Database Password | Every 180 days | | API Keys (third-party) | Per vendor policy, min annually | | Cloudflare API Token | Every 90 days | | Vercel Token | Every 180 days | | SSH Keys | Every 365 days | --- ## 7. Health Check & Monitoring ### 7.1 Health Check Endpoints Semua service WAJIB memiliki endpoints berikut: ```typescript // api/health.ts Generic health check endpoint import { Request, Response } from 'express'; export async function healthCheck(req: Request, res: Response) { const healthData = { status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), version: process.env.npm_package_version || 'unknown', }; res.status(200).json(healthData); } // api/health/ready.ts Readiness check (dependencies available) export async function readinessCheck(req: Request, res: Response) { try { // Check database await db.$queryRaw`SELECT 1`; // Check Redis await redis.ping(); // Check external service const extRes = await fetch(process.env.EXTERNAL_HEALTH_URL!); if (!extRes.ok) throw new Error('External service unhealthy'); res.status(200).json({ status: 'ready', checks: { db: 'ok', redis: 'ok', external: 'ok' } }); } catch (error) { res.status(503).json({ status: 'not ready', error: error instanceof Error ? error.message : 'unknown', }); } } // api/health/live.ts Liveness check (service not deadlocked) export async function livenessCheck(req: Request, res: Response) { res.status(200).json({ status: 'alive' }); } ``` ### 7.2 HTTP Health Check Response Format ```json // 200 OK { "status": "ok", "timestamp": "2026-06-10T10:00:00.000Z", "uptime": 86400, "version": "1.2.3" } // 503 Service Unavailable (readiness failed) { "status": "not ready", "error": "Database connection failed", "timestamp": "2026-06-10T10:00:00.000Z" } ``` ### 7.3 Monitoring Stack Recommendations | Layer | Tool | Purpose | |-------|------|---------| | Uptime Monitoring | Better Uptime / Upptime / Checkly | External health check every 1-5 min | | Error Tracking | Sentry | Capture JS errors, source maps, stack traces | | Performance (RUM) | Vercel Analytics / Cloudflare Web Analytics | Real user monitoring, LCP, CLS, INP | | Application Metrics | OpenTelemetry Datadog / Grafana / SigNoz | Traces, spans, request duration, error rate | | Server Metrics | Prometheus + Grafana / CloudWatch | CPU, memory, disk, network | | Logging | Axiom / Better Stack / ELK / Loki | Structured JSON logs aggregation | | Alerting | Slack webhook / PagerDuty / OpsGenie | Critical alerts routed to on-call | ### 7.4 GitHub Actions — Smoke Test After Deploy ```yaml - name: Smoke Test run: | sleep 10 STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ vars.DEPLOY_URL }}/api/health) if [ "$STATUS" -ne 200 ]; then echo "❌ Health check failed with status $STATUS" exit 1 fi echo "✅ Health check passed" ``` ### 7.5 Alert Configuration (example: BetterStack) | Metric | Threshold | Severity | Action | |--------|-----------|----------|--------| | Uptime | < 99.9% in 5 min | Critical | PagerDuty call | | Error Rate | > 1% in 5 min | Critical | Slack alert | | Response Time (p95) | > 2000ms | Warning | Slack notification | | SSL Expiry | < 14 days | Warning | Email reminder | | Build Failure | Any | High | GitHub notification | --- ## 8. Rollback Strategy ### 8.1 Rollback Methods | Method | Speed | Risk | Complexity | Best For | |--------|-------|------|------------|----------| | **Git Revert** | Medium | Low | Low | Code-only issues | | **Docker Image Rollback** | Fast | Low | Medium | Container deployment | | **Vercel Instant Rollback** | Instant | Low | None | Vercel deployment | | **Cloudflare Pages Rollback** | Instant | Low | Low | Cloudflare Pages | | **Database Migration Rollback** | Slow | High | High | Schema migration issues | ### 8.2 Vercel Rollback ```bash # Via Vercel Dashboard Deployments Pilih deployment sebelumnya "•••" "Promote to Production" # Via Vercel CLI vercel rollback --yes --token=$VERCEL_TOKEN # Via GitHub Actions (automated rollback on health check failure) ``` ### 8.3 Automated Rollback in GitHub Actions ```yaml name: Deploy with Rollback on: push: branches: [main] jobs: deploy-with-rollback: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - name: Build & Push Docker Image run: | docker build -t ${{ secrets.REGISTRY }}/app:${{ github.sha }} . docker push ${{ secrets.REGISTRY }}/app:${{ github.sha }} - name: Deploy to Production run: | # Save current version before deployment echo "PREVIOUS_TAG=$(curl -s $DEPLOY_URL/api/health | jq -r '.version')" >> $GITHUB_ENV docker compose -f docker-compose.prod.yml up -d app - name: Health Check id: health run: | for i in {1..6}; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" $DEPLOY_URL/api/health) if [ "$STATUS" -eq 200 ]; then echo "✅ Deployment successful" exit 0 fi echo "Waiting... ($i/6)" sleep 10 done echo "❌ Health check failed, initiating rollback" exit 1 - name: Rollback on Failure if: failure() && steps.health.outcome == 'failure' run: | echo "⏪ Rolling back to $PREVIOUS_TAG" docker pull ${{ secrets.REGISTRY }}/app:$PREVIOUS_TAG docker compose -f docker-compose.prod.yml up -d app echo "✅ Rollback complete" ``` ### 8.4 Docker Container Rollback ```bash # Step 1: Tag current as backup docker tag app:latest app:rollback-$(date +%Y%m%d-%H%M%S) # Step 2: Pull previous stable image docker pull registry.example.com/app:v1.2.1 # Step 3: Re-tag and restart docker tag registry.example.com/app:v1.2.1 app:latest docker compose up -d app # Step 4: Verify curl --fail http://localhost:3000/api/health && echo "Rollback successful" ``` ### 8.5 Database Rollback ```bash # Prisma — rollback migration npx prisma migrate resolve --applied "<migration-name>" # Manual SQL restore psql $DATABASE_URL < backups/schema-2026-06-09.sql # Best practice: selalu backup database SEBELUM deploy migration # Di CI: # - name: Backup Database # run: pg_dump $DATABASE_URL > backups/pre-deploy-$(date +%Y%m%d).sql ``` ### 8.6 Rollback Decision Matrix | Scenario | Action | Timeline | |----------|--------|----------| | Minor bug (non-breaking) | Hotfix deploy | Within 1 hour | | Major bug (broken feature) | Rollback to previous version | Within 15 minutes | | Security vulnerability | Immediate rollback + hotfix | Within 5 minutes | | Database corruption | Restore from backup | 30-60 minutes | | Performance degradation | Scale up first, rollback if persists | 10 minutes | ### 8.7 Rollback Checklist - [ ] Notify team via Slack (#deploy channel) - [ ] Identify the last known good version (commit SHA / Docker tag / deployment timestamp) - [ ] Execute rollback (Git revert / Docker re-tag / Vercel promote) - [ ] Verify health check endpoint returns 200 - [ ] Run smoke tests - [ ] Monitor error rate for 15 minutes post-rollback - [ ] Post-mortem: document root cause and preventive measures --- ## Pipeline Configuration Cheatsheet ### GitHub Actions Secrets Required | Secret Name | Purpose | Source | |-------------|---------|--------| | `DOCKER_REGISTRY` | Container registry URL | Docker Hub / GHCR / ECR | | `DOCKER_USERNAME` | Registry auth | Docker Hub account | | `DOCKER_PASSWORD` | Registry auth token | Docker Hub / GHCR PAT | | `VERCEL_TOKEN` | Vercel API auth | Vercel Account Tokens | | `VERCEL_ORG_ID` | Vercel team ID | Vercel Project Settings | | `VERCEL_PROJECT_ID` | Vercel project ID | Vercel Project Settings | | `CLOUDFLARE_API_TOKEN` | Cloudflare API auth | Cloudflare Dashboard API Tokens | | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account | Cloudflare Dashboard | | `SENTRY_AUTH_TOKEN` | Sentry source maps | Sentry Settings Auth Tokens | ### Quick Reference: Deployment Commands ```bash # Nuxt 4 nuxt build # SSR build → .output/ nuxi generate # Static export → dist/ # Next.js 15 next build # Standard build → .next/ # Docker docker build -t app:latest . # Build image docker compose up -d # Start services # Vercel CLI vercel deploy --prod # Deploy to production vercel rollback # Rollback to previous # Cloudflare Wrangler wrangler pages deploy .output/public --project-name=my-app wrangler deploy src/worker.ts --name my-worker ```