Skip to main content

Deployment Workflow

The ci-deploy.yml is a reusable workflow that handles Docker build, push, and Cloud Run deployment.

Inputs

on:
workflow_call:
inputs:
environment:
required: true
type: string
version:
required: true
type: string
terraform-outputs-artifact-id:
required: true
type: string
terraform-outputs-file-name:
required: true
type: string
build-artifact-name:
required: true
type: string

Deployment Steps

1. Download Artifacts

- uses: actions/download-artifact@v4
with:
run-id: ${{ inputs.terraform-outputs-artifact-id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/download-artifact@v4
with:
name: ${{ inputs.build-artifact-name }}

2. Parse Terraform Outputs

Extracts deployment configuration from Terraform:

- name: Setup Terraform Outputs Variables
run: |
OUTPUTS=$(cat ${{ inputs.terraform-outputs-file-name }})
echo "BACKSTAGE_CLOUD_SQL_CONNECTION_NAME=$(echo $OUTPUTS | jq -r '.backstage_cloud_sql_instance_connection_name.value')" >> $GITHUB_ENV
echo "BACKSTAGE_DB_NAME=$(echo $OUTPUTS | jq -r '.backstage_db_name.value')" >> $GITHUB_ENV
# ... additional outputs

Extracted Values:

  • Cloud SQL connection name
  • Database name and user
  • DB password secret ID
  • IAP audience
  • Service account email
  • VPC connector ID
  • Vault CA cert secret ID
  • Vault internal URL
  • TechDocs bucket

3. Authenticate to GCP

- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}

Uses Workload Identity Federation for secure, keyless authentication.

4. Retrieve Secrets

- name: Get Backstage DB Password
run: |
gcloud secrets versions access latest \
--secret="${{ env.BACKSTAGE_DB_PASSWORD }}" | tr -d '\n' | \
{ read -r pw; echo "::add-mask::$pw"; echo "BACKSTAGE_DB_PASSWORD_VALUE=$pw" >> $GITHUB_ENV; }

- name: Get Vault CA Certificate
run: |
gcloud secrets versions access latest \
--secret="${{ env.VAULT_CA_CERT_SECRET_ID }}" > /tmp/vault-ca.crt

5. Generate Configuration

Templates the production configuration file:

- name: Generate Backstage Config
working-directory: backstage
env:
# All environment variables from Terraform outputs and secrets
run: |
envsubst < app-config.production.yaml.tpl > app-config.production.yaml

Environment Variables Substituted:

  • Database connection details
  • OAuth credentials (GitHub, Google)
  • Vault configuration
  • JWT key paths
  • Base URLs

6. Build and Push Docker Image

- uses: docker/build-push-action@v6
with:
context: backstage
file: backstage/packages/backend/Dockerfile
push: true
tags: ${{ env.GAR_URL }}
cache-from: type=gha
cache-to: type=gha,mode=max

Image is pushed to Google Artifact Registry.

7. Deploy to Cloud Run

- name: Deploy to Cloud Run
run: |
gcloud run deploy backstage \
--image="${{ env.GAR_URL }}" \
--project="${{ secrets.GCP_PROJECT_ID }}" \
--region="${{ env.REGION }}" \
--port=7007 \
--service-account="${{ env.BACKSTAGE_SERVICE_ACCOUNT_EMAIL }}" \
--add-cloudsql-instances="${{ env.BACKSTAGE_CLOUD_SQL_CONNECTION_NAME }}" \
--vpc-connector="${{ env.BACKSTAGE_VPC_CONNECTOR_ID }}" \
--set-secrets="/etc/ssl/certs/vault-ca.crt=${{ env.VAULT_CA_CERT_SECRET_ID }}:latest" \
--set-secrets="/etc/backstage/jwt-private/key=backstage-jwt-private-key:latest" \
--set-secrets="/etc/backstage/jwt-public/key=backstage-jwt-public-key:latest" \
--memory=4Gi \
--cpu=4000m \
--timeout=300 \
--concurrency=80 \
--min-instances=1 \
--max-instances=1 \
--allow-unauthenticated

Cloud Run Configuration

SettingValuePurpose
Memory4GiNode.js + TypeScript compilation
CPU4000m4 cores for parallel processing
Timeout300sLong-running API calls
Concurrency80Requests per instance
Min Instances1Avoid cold starts
Max Instances1Cost control
AuthUnauthenticatedUses Backstage's own auth

Mounted Secrets

PathSecretPurpose
/etc/ssl/certs/vault-ca.crtvault-ca-certVault TLS verification
/etc/backstage/jwt-private/keybackstage-jwt-private-keyToken signing
/etc/backstage/jwt-public/keybackstage-jwt-public-keyToken verification

Permissions

permissions:
contents: read
id-token: write

Minimal permissions - only needs to read repo and use OIDC.