[{"data":1,"prerenderedAt":422},["ShallowReactive",2],{"blog-best-practices/deployment":3},{"id":4,"title":5,"body":6,"category":397,"date":398,"dateModified":399,"description":400,"draft":401,"extension":402,"faq":403,"featured":401,"headerVariant":407,"image":408,"keywords":408,"meta":409,"navigation":410,"ogDescription":411,"ogTitle":408,"path":412,"readTime":413,"schemaOrg":414,"schemaType":415,"seo":416,"sitemap":417,"stem":418,"tags":419,"twitterCard":420,"__hash__":421},"blog/blog/best-practices/deployment.md","Secure Deployment Best Practices: CI/CD, Containers, and Infrastructure",{"type":7,"value":8,"toc":386},"minimark",[9,16,25,30,33,48,115,119,122,131,135,138,147,172,176,179,188,192,195,269,278,282,285,294,300,305,327,331,334,355,374],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 deployment security best practice is to treat your deployment pipeline with the same security rigor as production. Use OIDC for CI/CD authentication, scan dependencies and containers for vulnerabilities, run containers as non-root, implement security gates in pipelines, and have tested rollback procedures.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"Your deployment pipeline is a privileged path to production. A compromised CI/CD system can bypass every security control you've built.\"",[26,27,29],"h2",{"id":28},"best-practice-1-secure-cicd-authentication-3-min","Best Practice 1: Secure CI/CD Authentication 3 min",[13,31,32],{},"Your CI/CD pipeline needs production access. Secure it properly:",[34,35,37],"code-block",{"label":36},"GitHub Actions with OIDC (no long-lived secrets)",[38,39,44],"pre",{"className":40,"code":42,"language":43},[41],"language-text","# .github/workflows/deploy.yml\nname: Deploy\non:\n  push:\n    branches: [main]\n\npermissions:\n  id-token: write  # Required for OIDC\n  contents: read\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # Authenticate with OIDC - no secrets stored\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          role-to-assume: arn:aws:iam::123456789:role/GitHubActions\n          aws-region: us-east-1\n\n      - name: Deploy to ECS\n        run: |\n          aws ecs update-service --cluster prod --service api --force-new-deployment\n","text",[45,46,42],"code",{"__ignoreMap":47},"",[49,50,51,67],"table",{},[52,53,54],"thead",{},[55,56,57,61,64],"tr",{},[58,59,60],"th",{},"Authentication Method",[58,62,63],{},"Security",[58,65,66],{},"Recommendation",[68,69,70,82,93,104],"tbody",{},[55,71,72,76,79],{},[73,74,75],"td",{},"OIDC tokens",[73,77,78],{},"Excellent",[73,80,81],{},"Use when available",[55,83,84,87,90],{},[73,85,86],{},"Short-lived tokens",[73,88,89],{},"Good",[73,91,92],{},"Rotate frequently",[55,94,95,98,101],{},[73,96,97],{},"Stored secrets",[73,99,100],{},"Acceptable",[73,102,103],{},"Last resort, rotate often",[55,105,106,109,112],{},[73,107,108],{},"Personal access tokens",[73,110,111],{},"Poor",[73,113,114],{},"Avoid in CI/CD",[26,116,118],{"id":117},"best-practice-2-implement-security-gates-5-min","Best Practice 2: Implement Security Gates 5 min",[13,120,121],{},"Block deployments that fail security checks:",[34,123,125],{"label":124},"Security gates in CI/CD pipeline",[38,126,129],{"className":127,"code":128,"language":43},[41],"# .github/workflows/security.yml\nname: Security Gates\non: [pull_request, push]\n\njobs:\n  dependency-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run npm audit\n        run: npm audit --audit-level=high\n\n      - name: Run Snyk\n        uses: snyk/actions/node@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n        with:\n          args: --severity-threshold=high\n\n  sast-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run Semgrep\n        uses: returntocorp/semgrep-action@v1\n        with:\n          config: p/security-audit\n\n  secret-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Run Gitleaks\n        uses: gitleaks/gitleaks-action@v2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  container-scan:\n    runs-on: ubuntu-latest\n    needs: [build]\n    steps:\n      - name: Run Trivy\n        uses: aquasecurity/trivy-action@master\n        with:\n          image-ref: myapp:${{ github.sha }}\n          severity: 'CRITICAL,HIGH'\n          exit-code: '1'\n",[45,130,128],{"__ignoreMap":47},[26,132,134],{"id":133},"best-practice-3-secure-container-configuration-4-min","Best Practice 3: Secure Container Configuration 4 min",[13,136,137],{},"Containers need hardening before production:",[34,139,141],{"label":140},"Secure Dockerfile",[38,142,145],{"className":143,"code":144,"language":43},[41],"# Use specific version, not latest\nFROM node:20.11-alpine3.19\n\n# Run as non-root user\nRUN addgroup -g 1001 -S nodejs && \\\n    adduser -S nodejs -u 1001\n\n# Set working directory\nWORKDIR /app\n\n# Copy package files first (better caching)\nCOPY package*.json ./\nRUN npm ci --only=production\n\n# Copy application code\nCOPY --chown=nodejs:nodejs . .\n\n# Switch to non-root user\nUSER nodejs\n\n# Expose port (non-privileged)\nEXPOSE 3000\n\n# Health check\nHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\n  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1\n\n# Don't run as PID 1\nCMD [\"dumb-init\", \"node\", \"server.js\"]\n",[45,146,144],{"__ignoreMap":47},[148,149,150,154,157,160,166,169],"ul",{},[151,152,153],"li",{},"Use minimal base images (alpine, distroless)",[151,155,156],{},"Run as non-root user",[151,158,159],{},"Use multi-stage builds to exclude dev deps",[151,161,162,163],{},"Pin specific image versions, not ",[164,165],"latest",{},[151,167,168],{},"Scan images for vulnerabilities before deploy",[151,170,171],{},"Set read-only file system where possible",[26,173,175],{"id":174},"best-practice-4-infrastructure-as-code-security-3-min","Best Practice 4: Infrastructure as Code Security 3 min",[13,177,178],{},"Scan your infrastructure definitions:",[34,180,182],{"label":181},"Terraform security scanning",[38,183,186],{"className":184,"code":185,"language":43},[41],"# Run tfsec to find security issues\ntfsec .\n\n# Example issues it catches:\n# - S3 buckets without encryption\n# - Security groups with 0.0.0.0/0 access\n# - RDS without encryption at rest\n# - IAM policies with wildcard permissions\n\n# checkov for comprehensive scanning\ncheckov -d . --framework terraform\n\n# In CI/CD pipeline\n- name: Run tfsec\n  uses: aquasecurity/tfsec-action@v1.0.3\n  with:\n    soft_fail: false  # Fail the build\n",[45,187,185],{"__ignoreMap":47},[26,189,191],{"id":190},"best-practice-5-deployment-strategies-5-min","Best Practice 5: Deployment Strategies 5 min",[13,193,194],{},"Minimize risk with safe deployment patterns:",[49,196,197,213],{},[52,198,199],{},[55,200,201,204,207,210],{},[58,202,203],{},"Strategy",[58,205,206],{},"Risk Level",[58,208,209],{},"Rollback Speed",[58,211,212],{},"Best For",[68,214,215,229,242,255],{},[55,216,217,220,223,226],{},[73,218,219],{},"Blue-Green",[73,221,222],{},"Low",[73,224,225],{},"Instant",[73,227,228],{},"Critical services",[55,230,231,234,236,239],{},[73,232,233],{},"Canary",[73,235,222],{},[73,237,238],{},"Fast",[73,240,241],{},"High-traffic services",[55,243,244,247,250,252],{},[73,245,246],{},"Rolling",[73,248,249],{},"Medium",[73,251,249],{},[73,253,254],{},"Stateless services",[55,256,257,260,263,266],{},[73,258,259],{},"Recreate",[73,261,262],{},"High",[73,264,265],{},"Slow",[73,267,268],{},"Dev/staging only",[34,270,272],{"label":271},"Canary deployment example",[38,273,276],{"className":274,"code":275,"language":43},[41],"# Kubernetes canary with Argo Rollouts\napiVersion: argoproj.io/v1alpha1\nkind: Rollout\nmetadata:\n  name: api-rollout\nspec:\n  replicas: 10\n  strategy:\n    canary:\n      steps:\n        - setWeight: 10    # 10% of traffic\n        - pause: { duration: 5m }\n        - analysis:\n            templates:\n              - templateName: success-rate\n        - setWeight: 50    # 50% of traffic\n        - pause: { duration: 10m }\n        - analysis:\n            templates:\n              - templateName: success-rate\n\n---\napiVersion: argoproj.io/v1alpha1\nkind: AnalysisTemplate\nmetadata:\n  name: success-rate\nspec:\n  metrics:\n    - name: success-rate\n      successCondition: result[0] >= 0.99\n      provider:\n        prometheus:\n          query: |\n            sum(rate(http_requests_total{status=~\"2..\"}[5m]))\n            /\n            sum(rate(http_requests_total[5m]))\n",[45,277,275],{"__ignoreMap":47},[26,279,281],{"id":280},"best-practice-6-rollback-procedures-3-min","Best Practice 6: Rollback Procedures 3 min",[13,283,284],{},"Have tested rollback procedures ready:",[34,286,288],{"label":287},"Rollback procedures",[38,289,292],{"className":290,"code":291,"language":43},[41],"# Quick rollback commands\n# Kubernetes\nkubectl rollout undo deployment/api\nkubectl rollout status deployment/api\n\n# AWS ECS\naws ecs update-service --cluster prod --service api \\\n  --task-definition api:previous-version\n\n# Vercel\nvercel rollback\n\n# Database migrations (have down migrations ready)\nnpm run migrate:down\n\n# Feature flags for instant rollback\nconst newFeature = await featureFlags.isEnabled('new-checkout');\nif (newFeature) {\n  return newCheckoutFlow(req);\n} else {\n  return legacyCheckoutFlow(req);\n}\n",[45,293,291],{"__ignoreMap":47},[295,296,297],"info-box",{},[13,298,299],{},"Practice Rollbacks:\nRegularly test your rollback procedures in staging. A rollback you have never tested is a rollback that might fail when you need it most.",[295,301,302],{},[13,303,304],{},"External Resources:\nFor comprehensive deployment security guidance, see the\nOWASP CI/CD Security Cheat Sheet\n, the\nDocker Security Cheat Sheet\n, and the\nKubernetes Security Documentation\n. These resources provide industry-standard recommendations for securing your deployment pipeline.",[306,307,308,315,321],"faq-section",{},[309,310,312],"faq-item",{"question":311},"Should I auto-deploy to production?",[13,313,314],{},"For most teams, auto-deploy to staging and require manual approval for production. Mature teams with strong testing and observability can safely auto-deploy to production with canary releases.",[309,316,318],{"question":317},"How do I handle database migrations in deployment?",[13,319,320],{},"Run migrations before deploying new code (expand-contract pattern). Ensure migrations are backward compatible so the old code still works. Never run destructive migrations in the same deployment as code changes.",[309,322,324],{"question":323},"What should block a deployment?",[13,325,326],{},"High or critical security vulnerabilities, failing tests, secret detection, and compliance violations should block deployment. Have clear policies documented and enforced in CI/CD.",[26,328,330],{"id":329},"further-reading","Further Reading",[13,332,333],{},"Put these practices into action with our step-by-step guides.",[148,335,336,343,349],{},[151,337,338],{},[339,340,342],"a",{"href":341},"/blog/how-to/add-security-headers","Add security headers to your app",[151,344,345],{},[339,346,348],{"href":347},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[151,350,351],{},[339,352,354],{"href":353},"/blog/getting-started/first-scan","Run your first security scan",[356,357,358,364,369],"related-articles",{},[359,360],"related-card",{"description":361,"href":362,"title":363},"Handle secrets in CI/CD","/blog/best-practices/secrets","Secrets Management",[359,365],{"description":366,"href":367,"title":368},"Deployment observability","/blog/best-practices/monitoring","Monitoring",[359,370],{"description":371,"href":372,"title":373},"Serverless deployment","/blog/best-practices/vercel","Vercel Security",[375,376,379,383],"cta-box",{"href":377,"label":378},"/","Start Free Scan",[26,380,382],{"id":381},"audit-your-deployment-security","Audit Your Deployment Security",[13,384,385],{},"Check your CI/CD pipeline and deployment configuration.",{"title":47,"searchDepth":387,"depth":387,"links":388},2,[389,390,391,392,393,394,395,396],{"id":28,"depth":387,"text":29},{"id":117,"depth":387,"text":118},{"id":133,"depth":387,"text":134},{"id":174,"depth":387,"text":175},{"id":190,"depth":387,"text":191},{"id":280,"depth":387,"text":281},{"id":329,"depth":387,"text":330},{"id":381,"depth":387,"text":382},"best-practices","2026-01-22","2026-02-04","Deployment security best practices. Learn secure CI/CD pipelines, container security, infrastructure hardening, and safe rollback strategies.",false,"md",[404,405,406],{"question":311,"answer":314},{"question":317,"answer":320},{"question":323,"answer":326},"vibe-green",null,{},true,"Deploy applications securely with proper CI/CD and container practices.","/blog/best-practices/deployment","14 min read","[object Object]","Article",{"title":5,"description":400},{"loc":412},"blog/best-practices/deployment",[],"summary_large_image","Yd_-1avfUciE6XOG6i_XTcSU22ntbkIEBdgK9Wya4-g",1775843926251]