Add development standards documentation #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Deployment Pipeline | |
| # Deploys to AWS App Runner (backend) and S3/CloudFront (frontend) | |
| name: Deploy | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: 'Environment to deploy to' | |
| required: true | |
| default: 'preprod' | |
| type: choice | |
| options: | |
| - preprod | |
| - prod | |
| env: | |
| AWS_REGION: us-east-1 | |
| jobs: | |
| test: | |
| name: Run Tests | |
| uses: ./.github/workflows/ci.yml | |
| deploy-preprod: | |
| name: Deploy to Pre-Production | |
| runs-on: ubuntu-latest | |
| needs: test | |
| if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'preprod') | |
| environment: | |
| name: preprod | |
| url: https://app.preprod.yourdomain.com | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PREPROD }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PREPROD }} | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| id: login-ecr | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build and push backend image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| push: true | |
| platforms: linux/amd64 | |
| tags: | | |
| ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_PREPROD }}:latest | |
| ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_PREPROD }}:${{ github.sha }} | |
| build-args: | | |
| GIT_SHA=${{ github.sha }} | |
| BUILD_TIMESTAMP=${{ github.event.head_commit.timestamp || github.event.repository.updated_at }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Build frontend | |
| run: | | |
| cd frontend | |
| npm ci | |
| VITE_API_URL=https://api.preprod.yourdomain.com \ | |
| VITE_COGNITO_USER_POOL_ID=${{ secrets.COGNITO_USER_POOL_ID_PREPROD }} \ | |
| VITE_COGNITO_CLIENT_ID=${{ secrets.COGNITO_CLIENT_ID_PREPROD }} \ | |
| VITE_COGNITO_DOMAIN=${{ secrets.COGNITO_DOMAIN_PREPROD }} \ | |
| VITE_COGNITO_REGION=us-east-1 \ | |
| npm run build | |
| - name: Deploy frontend to S3 | |
| run: | | |
| aws s3 sync frontend/dist s3://${{ secrets.S3_BUCKET_PREPROD }} \ | |
| --delete \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --exclude "index.html" \ | |
| --exclude "*.json" | |
| aws s3 cp frontend/dist/index.html s3://${{ secrets.S3_BUCKET_PREPROD }}/index.html \ | |
| --cache-control "no-cache, no-store, must-revalidate" | |
| - name: Invalidate CloudFront cache | |
| run: | | |
| aws cloudfront create-invalidation \ | |
| --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_PREPROD }} \ | |
| --paths "/*" | |
| - name: Trigger App Runner deployment | |
| run: | | |
| aws apprunner start-deployment --service-arn ${{ secrets.APPRUNNER_SERVICE_ARN_PREPROD }} || true | |
| - name: Wait for deployment and verify | |
| run: | | |
| echo "Waiting for deployment..." | |
| EXPECTED_SHA="${{ github.sha }}" | |
| for i in {1..60}; do | |
| HEALTH=$(curl -sf https://api.preprod.yourdomain.com/api/health 2>/dev/null || echo '{}') | |
| DEPLOYED_SHA=$(echo $HEALTH | jq -r '.git_sha // "unknown"') | |
| echo "Attempt $i/60: Deployed=$DEPLOYED_SHA, Expected=$EXPECTED_SHA" | |
| if [ "$DEPLOYED_SHA" = "$EXPECTED_SHA" ]; then | |
| echo "Deployment verified!" | |
| exit 0 | |
| fi | |
| sleep 10 | |
| done | |
| echo "Deployment verification failed" | |
| exit 1 | |
| deploy-prod: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: test | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' | |
| environment: | |
| name: production | |
| url: https://app.yourdomain.com | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }} | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| id: login-ecr | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build and push backend image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| push: true | |
| platforms: linux/amd64 | |
| tags: | | |
| ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_PROD }}:latest | |
| ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_PROD }}:${{ github.sha }} | |
| build-args: | | |
| GIT_SHA=${{ github.sha }} | |
| BUILD_TIMESTAMP=${{ github.event.head_commit.timestamp || github.event.repository.updated_at }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Build frontend | |
| run: | | |
| cd frontend | |
| npm ci | |
| VITE_API_URL=https://api.yourdomain.com \ | |
| VITE_COGNITO_USER_POOL_ID=${{ secrets.COGNITO_USER_POOL_ID_PROD }} \ | |
| VITE_COGNITO_CLIENT_ID=${{ secrets.COGNITO_CLIENT_ID_PROD }} \ | |
| VITE_COGNITO_DOMAIN=${{ secrets.COGNITO_DOMAIN_PROD }} \ | |
| VITE_COGNITO_REGION=us-east-1 \ | |
| npm run build | |
| - name: Deploy frontend to S3 | |
| run: | | |
| aws s3 sync frontend/dist s3://${{ secrets.S3_BUCKET_PROD }} \ | |
| --delete \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --exclude "index.html" \ | |
| --exclude "*.json" | |
| aws s3 cp frontend/dist/index.html s3://${{ secrets.S3_BUCKET_PROD }}/index.html \ | |
| --cache-control "no-cache, no-store, must-revalidate" | |
| - name: Invalidate CloudFront cache | |
| run: | | |
| aws cloudfront create-invalidation \ | |
| --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_PROD }} \ | |
| --paths "/*" | |
| - name: Trigger App Runner deployment | |
| run: | | |
| aws apprunner start-deployment --service-arn ${{ secrets.APPRUNNER_SERVICE_ARN_PROD }} || true | |
| - name: Verify deployment | |
| run: | | |
| echo "Waiting for production deployment..." | |
| EXPECTED_SHA="${{ github.sha }}" | |
| for i in {1..60}; do | |
| HEALTH=$(curl -sf https://api.yourdomain.com/api/health 2>/dev/null || echo '{}') | |
| DEPLOYED_SHA=$(echo $HEALTH | jq -r '.git_sha // "unknown"') | |
| if [ "$DEPLOYED_SHA" = "$EXPECTED_SHA" ]; then | |
| echo "Production deployment verified!" | |
| exit 0 | |
| fi | |
| sleep 10 | |
| done | |
| exit 1 |