.github/workflows/deploy-ecs.yml
Zero-Trust Next.js & Prisma to AWS ECS
Production GitHub Actions pipeline with keyless AWS OIDC authentication, Semgrep SAST, Trivy container scanning, Prisma migrations, and rolling ECS service deployment.
Keyless AWS IAM role assumption via OIDC (no static long-lived secrets)
Parallel Semgrep SAST + npm audit gate prior to container build
Trivy CRITICAL/HIGH vulnerability gate on built ECR image
deploy-ecs.yml·107 lines·yaml
| 1 | name: Production ECS Delivery |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: [main] |
| 6 | pull_request: |
| 7 | branches: [main] |
| 8 | |
| 9 | permissions: |
| 10 | id-token: write |
| 11 | contents: read |
| 12 | security-events: write |
| 13 | |
| 14 | env: |
| 15 | AWS_REGION: us-east-1 |
| 16 | ECR_REPOSITORY: volition-web |
| 17 | ECS_CLUSTER: production-core |
| 18 | ECS_SERVICE: volition-web-svc |
| 19 | NODE_VERSION: "22" |
| 20 | |
| 21 | jobs: |
| 22 | quality-and-sast: |
| 23 | name: Static Analysis & Unit Suite |
| 24 | runs-on: ubuntu-latest |
| 25 | steps: |
| 26 | - name: Checkout source |
| 27 | uses: actions/checkout@v4 |
| 28 | |
| 29 | - name: Setup Node.js |
| 30 | uses: actions/setup-node@v4 |
| 31 | with: |
| 32 | node-version: ${{ env.NODE_VERSION }} |
| 33 | cache: npm |
| 34 | |
| 35 | - name: Install deterministic dependencies |
| 36 | run: npm ci |
| 37 | |
| 38 | - name: Validate Prisma schema & types |
| 39 | run: | |
| 40 | npx prisma validate |
| 41 | npm run lint |
| 42 | npx tsc --noEmit |
| 43 | |
| 44 | - name: Run Semgrep SAST scan |
| 45 | uses: returntocorp/semgrep-action@v1 |
| 46 | with: |
| 47 | config: p/typescript p/nextjs p/owasp-top-ten |
| 48 | |
| 49 | build-scan-deploy: |
| 50 | name: Build, Scan & Rollout ECS |
| 51 | needs: [quality-and-sast] |
| 52 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' |
| 53 | runs-on: ubuntu-latest |
| 54 | steps: |
| 55 | - name: Checkout source |
| 56 | uses: actions/checkout@v4 |
| 57 | |
| 58 | - name: Configure AWS credentials via OIDC |
| 59 | uses: aws-actions/configure-aws-credentials@v4 |
| 60 | with: |
| 61 | role-to-assume: ${{ secrets.AWS_OIDC_DEPLOY_ROLE_ARN }} |
| 62 | aws-region: ${{ env.AWS_REGION }} |
| 63 | |
| 64 | - name: Login to Amazon ECR |
| 65 | id: login-ecr |
| 66 | uses: aws-actions/amazon-ecr-login@v2 |
| 67 | |
| 68 | - name: Build release container image |
| 69 | env: |
| 70 | REGISTRY: ${{ steps.login-ecr.outputs.registry }} |
| 71 | IMAGE_TAG: ${{ github.sha }} |
| 72 | run: | |
| 73 | docker build \ |
| 74 | --build-arg GIT_SHA=${IMAGE_TAG} \ |
| 75 | -t ${REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} . |
| 76 | |
| 77 | - name: Scan image with Trivy |
| 78 | uses: aquasecurity/trivy-action@0.28.0 |
| 79 | with: |
| 80 | image-ref: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} |
| 81 | format: table |
| 82 | exit-code: "1" |
| 83 | ignore-unfixed: true |
| 84 | vuln-type: os,library |
| 85 | severity: CRITICAL,HIGH |
| 86 | |
| 87 | - name: Push verified image to ECR |
| 88 | env: |
| 89 | REGISTRY: ${{ steps.login-ecr.outputs.registry }} |
| 90 | run: docker push ${REGISTRY}/${ECR_REPOSITORY}:${{ github.sha }} |
| 91 | |
| 92 | - name: Run Prisma production migrations |
| 93 | env: |
| 94 | DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }} |
| 95 | run: | |
| 96 | npm ci --ignore-scripts |
| 97 | npx prisma migrate deploy |
| 98 | |
| 99 | - name: Update ECS task definition & await stability |
| 100 | run: | |
| 101 | aws ecs update-service \ |
| 102 | --cluster ${ECS_CLUSTER} \ |
| 103 | --service ${ECS_SERVICE} \ |
| 104 | --force-new-deployment |
| 105 | aws ecs wait services-stable \ |
| 106 | --cluster ${ECS_CLUSTER} \ |
| 107 | --services ${ECS_SERVICE} |