prod-deploy.yml10.2 KBView on GitHub name: Deploy AWS Prod
on:
push:
branches:
- main
workflow_dispatch:
inputs:
deploy_mode:
description: Deployment mode
required: true
default: auto
type: choice
options:
- auto
- full
permissions:
contents: read
id-token=[redacted]
concurrency:
group: aws-prod-deploy
cancel-in-progress: false
jobs:
plan:
runs-on: ubuntu-latest
permissions:
contents: read
# Reading which commit each deploy job last succeeded on.
actions: read
outputs:
should_deploy: ${{ steps.plan.outputs.should_deploy }}
deploy_stack: ${{ steps.plan.outputs.deploy_stack }}
deploy_frontend: ${{ steps.plan.outputs.deploy_frontend }}
publish_api: ${{ steps.plan.outputs.publish_api }}
publish_chat: ${{ steps.plan.outputs.publish_chat }}
publish_worker: ${{ steps.plan.outputs.publish_worker }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine deploy scope
id: plan
env:
CEDAR_COMPARE_FROM_SHA: ${{ github.event.before }}
CEDAR_COMPARE_TO_SHA: ${{ github.sha }}
INPUT_DEPLOY_MODE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_mode || 'auto' }}
# Scope each surface off the commit it was last deployed from rather than
# off the previous push, so a cancelled or failed run doesn't strand its
# changes. See aws/scripts/last-deployed-sha.sh.
CEDAR_DEPLOY_BASELINE_WORKFLOW: prod-deploy.yml
CEDAR_DEPLOY_BASELINE_REF: main
GH_TOKEN: ${{ github.token }}
run: bash ./aws/scripts/plan-surface-scope.sh
deploy-backend:
needs: plan
if: needs.plan.outputs.deploy_stack == 'true' || needs.plan.outputs.publish_api == 'true' || needs.plan.outputs.publish_chat == 'true' || needs.plan.outputs.publish_worker == 'true'
runs-on: ubuntu-latest
environment: production
env:
AWS_REGION: us-east-1
AWS_DEFAULT_REGION: us-east-1
CEDAR_AWS_ACCOUNT_ID: '619071350399'
CEDAR_AWS_ENV: prod
CEDAR_AWS_REGION: us-east-1
AWS_PROD_DEPLOY_ROLE_ARN: ${{ vars.AWS_PROD_DEPLOY_ROLE_ARN }}
CEDAR_AWS_PROD_APP_DOMAIN: ${{ vars.CEDAR_AWS_PROD_APP_DOMAIN }}
CEDAR_AWS_PROD_API_DOMAIN: ${{ vars.CEDAR_AWS_PROD_API_DOMAIN }}
CEDAR_AWS_PROD_CERTIFICATE_ARN: ${{ vars.CEDAR_AWS_PROD_CERTIFICATE_ARN }}
CEDAR_AWS_PROD_MARKETING_DOMAINS: ${{ vars.CEDAR_AWS_PROD_MARKETING_DOMAINS || 'cedarcopilot.com,www.cedarcopilot.com' }}
# Unset until the Manufact domain cutover (docs/design — Cedar MCP via Manufact proxy);
# buildEnvironmentConfig only sets CEDAR_MCP_PUBLIC_URL in the ECS task when this resolves
# to a non-empty value, so an unset GitHub variable here is a safe, inert default.
CEDAR_AWS_PROD_MCP_PUBLIC_URL: ${{ vars.CEDAR_AWS_PROD_MCP_PUBLIC_URL }}
CEDAR_AWS_SHARED_FILES_BUCKET_NAME: ${{ vars.CEDAR_AWS_SHARED_FILES_BUCKET_NAME }}
CEDAR_AWS_VPC_ID: ${{ vars.CEDAR_AWS_VPC_ID }}
CEDAR_AWS_DEPLOY_STACK: ${{ needs.plan.outputs.deploy_stack }}
CEDAR_AWS_DEPLOY_FRONTEND: 'false'
CEDAR_AWS_PUBLISH_API: ${{ needs.plan.outputs.publish_api }}
CEDAR_AWS_PUBLISH_CHAT: ${{ needs.plan.outputs.publish_chat }}
CEDAR_AWS_PUBLISH_WORKER: ${{ needs.plan.outputs.publish_worker }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Cache node_modules
id: cache-deps
uses: actions/cache@v4
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: ${{ runner.os }}-deploy-deps-${{ hashFiles('pnpm-lock.yaml') }}
- name: Cache AWS CDK dependencies
id: cache-aws
uses: actions/cache@v4
with:
path: aws/node_modules
key: ${{ runner.os }}-deploy-aws-${{ hashFiles('aws/package.json', 'aws/pnpm-lock.yaml') }}
- name: Install workspace dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Install AWS deployment dependencies
if: steps.cache-aws.outputs.cache-hit != 'true'
run: pnpm --ignore-workspace --dir aws install --no-frozen-lockfile
- name: Validate prod deploy configuration
run: |
set -euo pipefail
required_vars=(
AWS_PROD_DEPLOY_ROLE_ARN
CEDAR_AWS_VPC_ID
)
for var_name in "${required_vars[@]}"; do
if [[ -z "${!var_name:-}" ]]; then
echo "Missing required GitHub production environment variable: ${var_name}" >&2
exit 1
fi
done
if [[ "$AWS_PROD_DEPLOY_ROLE_ARN" != "arn:aws:iam::${CEDAR_AWS_ACCOUNT_ID}:role/"* ]]; then
echo "AWS_PROD_DEPLOY_ROLE_ARN must point at Cedar account ${CEDAR_AWS_ACCOUNT_ID}" >&2
exit 1
fi
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{ env.AWS_PROD_DEPLOY_ROLE_ARN }}
- name: Verify Cedar AWS account
run: |
set -euo pipefail
account_id="$(aws sts get-caller-identity --query 'Account' --output text)"
if [[ "$account_id" != "$CEDAR_AWS_ACCOUNT_ID" ]]; then
echo "Expected Cedar AWS account ${CEDAR_AWS_ACCOUNT_ID}, got ${account_id}" >&2
exit 1
fi
- name: Validate Secrets Manager has all declared fields
run: bash ./aws/scripts/validate-secrets.sh prod
- name: Preflight CDK validation
if: needs.plan.outputs.deploy_stack == 'true'
run: |
set -euo pipefail
echo "Typechecking CDK and synthesizing template in parallel..."
pnpm --ignore-workspace --dir aws exec tsc -p tsconfig.json --noEmit &
PID_TC=$!
pnpm --ignore-workspace --dir aws exec cdk synth CedarAwsProdEnvironmentStack --quiet >/dev/null &
PID_SYNTH=$!
wait $PID_TC
wait $PID_SYNTH
echo "CDK preflight passed"
- name: Deploy backend
env:
NODE_OPTIONS: --max-old-space-size=8192
DOCKER_BUILDKIT: '1'
CEDAR_AWS_RUN_TYPECHECK: 'false'
run: bash ./aws/scripts/deploy-surface.sh prod
deploy-frontend:
needs: [plan, deploy-backend]
if: always() && needs.plan.outputs.deploy_frontend == 'true' && (needs.deploy-backend.result == 'success' || needs.deploy-backend.result == 'skipped')
runs-on: ubuntu-latest
environment: production
env:
AWS_REGION: us-east-1
AWS_DEFAULT_REGION: us-east-1
CEDAR_AWS_ACCOUNT_ID: '619071350399'
CEDAR_AWS_ENV: prod
CEDAR_AWS_REGION: us-east-1
AWS_PROD_DEPLOY_ROLE_ARN: ${{ vars.AWS_PROD_DEPLOY_ROLE_ARN }}
CEDAR_AWS_PROD_APP_DOMAIN: ${{ vars.CEDAR_AWS_PROD_APP_DOMAIN }}
CEDAR_AWS_PROD_API_DOMAIN: ${{ vars.CEDAR_AWS_PROD_API_DOMAIN }}
CEDAR_AWS_PROD_CERTIFICATE_ARN: ${{ vars.CEDAR_AWS_PROD_CERTIFICATE_ARN }}
CEDAR_AWS_VPC_ID: ${{ vars.CEDAR_AWS_VPC_ID }}
VITE_PUBLIC_POSTHOG_KEY: ${{ vars.VITE_PUBLIC_POSTHOG_KEY }}
# VITE_PUBLIC_POSTHOG_HOST is intentionally unset: publish-frontend-assets.sh derives it
# as <app-origin>/ingest (the same-origin reverse proxy on the CloudFront distribution),
# so analytics isn't dropped by browser tracker blockers.
VITE_PUBLIC_SENTRY_DSN: ${{ vars.VITE_PUBLIC_SENTRY_DSN }}
VITE_PUBLIC_SENTRY_ENVIRONMENT: production
VITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: '0.1'
VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: '0'
VITE_PUBLIC_SENTRY_REPLAYS_ERROR_SAMPLE_RATE: '1'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: cedar-2k
SENTRY_PROJECT: scm-platform-features
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Cache node_modules
id: cache-deps
uses: actions/cache@v4
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: ${{ runner.os }}-deploy-deps-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install workspace dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{ env.AWS_PROD_DEPLOY_ROLE_ARN }}
- name: Deploy frontend
env:
NODE_OPTIONS: --max-old-space-size=8192
run: bash ./aws/scripts/publish-frontend-assets.sh prod
smoke-test:
needs: [deploy-backend, deploy-frontend]
if: always() && (needs.deploy-backend.result == 'success' || needs.deploy-backend.result == 'skipped') && (needs.deploy-frontend.result == 'success' || needs.deploy-frontend.result == 'skipped') && !(needs.deploy-backend.result == 'skipped' && needs.deploy-frontend.result == 'skipped')
runs-on: ubuntu-latest
environment: production
env:
AWS_REGION: us-east-1
AWS_DEFAULT_REGION: us-east-1
CEDAR_AWS_ENV: prod
CEDAR_AWS_REGION: us-east-1
AWS_PROD_DEPLOY_ROLE_ARN: ${{ vars.AWS_PROD_DEPLOY_ROLE_ARN }}
CEDAR_AWS_PROD_APP_DOMAIN: ${{ vars.CEDAR_AWS_PROD_APP_DOMAIN }}
CEDAR_AWS_PROD_API_DOMAIN: ${{ vars.CEDAR_AWS_PROD_API_DOMAIN }}
CEDAR_AWS_PROD_CERTIFICATE_ARN: ${{ vars.CEDAR_AWS_PROD_CERTIFICATE_ARN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{ env.AWS_PROD_DEPLOY_ROLE_ARN }}
- name: Smoke test prod deployment
run: bash ./aws/scripts/smoke-test.sh prod