Skip to content

Deploy Production

Deploy Production #18

name: Deploy Production
on:
workflow_run:
workflows: ["Build and Push Docker Images"]
types: [completed]
branches: [main, "v*"]
push:
branches: [main]
paths:
- "deployment/cloud/gcp/infrastructure/**"
- "pixi.toml"
workflow_dispatch: # Allow manual triggers
concurrency:
group: deploy-production
cancel-in-progress: true
env:
GCP_PROJECT: biocirv-470318
GCP_REGION: us-west1
DEPLOY_ENV: production
# These values come from Pulumi outputs after bootstrapping the production stack.
# Run `DEPLOY_ENV=production pixi run -e deployment cloud-outputs` to verify.
# NOTE: Update after first production deploy (Phase 4 bootstrap).
WIF_PROVIDER: "projects/194468397458/locations/global/workloadIdentityPools/github-actions-production/providers/github-oidc-production"
DEPLOYER_SA: "biocirv-prod-gh-deploy@biocirv-470318.iam.gserviceaccount.com"
jobs:
# --- Preview (runs on pushes to main that touch infrastructure) ---
preview:
name: Pulumi Preview (Production)
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}
- name: Set up pixi
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.63.2
environments: deployment
- name: Preview infrastructure changes
run: pixi run -e deployment cloud-plan-direct
# --- Compute image tag from docker-build completion or workflow_dispatch ---
image-tag:
name: Compute Image Tag
if: >-
(github.event_name == 'workflow_dispatch') || (github.event_name ==
'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'release')
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
outputs:
image_tag: ${{ steps.vars.outputs.image_tag }}
steps:
- name: Derive image tag
id: vars
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ github.event_name }}" = "workflow_run" ]; then
# The docker-build was triggered by a release — use short SHA
SHA="${{ github.event.workflow_run.head_sha }}"
echo "image_tag=${SHA::7}" >> "$GITHUB_OUTPUT"
echo "Resolved image tag from docker-build: ${SHA::7}"
else
# workflow_dispatch: query the last successful docker-build run
# on main to get the SHA of the most recently built image.
SHA=$(gh api \
"repos/${{ github.repository }}/actions/workflows/docker-build.yml/runs?branch=main&status=success&per_page=1" \
--jq '.workflow_runs[0].head_sha')
if [ -z "$SHA" ] || [ "$SHA" = "null" ]; then
echo "::error::No successful docker-build run found"
exit 1
fi
echo "image_tag=${SHA::7}" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: ${SHA::7}"
fi
deploy-infrastructure:
name: Deploy Infrastructure (Pulumi)
needs: image-tag
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}
- name: Set up pixi
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.63.2
environments: deployment
- name: Deploy infrastructure
env:
IMAGE_TAG: ${{ needs.image-tag.outputs.image_tag }}
# When the production frontend URL is known, add:
# CORS_ORIGINS: '["https://PRODUCTION_FRONTEND_URL"]'
run: pixi run -e deployment cloud-deploy-direct
run-migrations:
name: Run Database Migrations
needs: [image-tag, deploy-infrastructure]
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}
- name: Set up pixi
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.63.2
environments: deployment
- name: Run Alembic migrations
env:
IMAGE_TAG: ${{ needs.image-tag.outputs.image_tag }}
run: pixi run -e deployment cloud-migrate-ci
update-services:
name: Force Cloud Run Revision Updates
needs: [image-tag, run-migrations]
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}
- name: Set up pixi
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.63.2
environments: deployment
- name: Force new Cloud Run revisions
env:
IMAGE_TAG: ${{ needs.image-tag.outputs.image_tag }}
run: pixi run -e deployment cloud-update-services
validate-deployment:
name: Validate Deployment Health
needs: [image-tag, update-services]
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Validate services are healthy
run: bash scripts/validate-deployment.sh
timeout-minutes: 10