Skip to main content

1.3 Deploy services

Outcome​

The four runtime services — rcm-core, rcm-app, edi-gateway, edi-app — are running in Azure, configured to talk to master DB, Key Vault, and Service Bus.

Prerequisites​

Service map​

Deploys are driven from GitHub Actions workflows under .github/workflows/:

WorkflowTarget
deploy-dev-rcm-core.ymlApp Service slot for rcm-core + slot swap
deploy-dev-rcm-app.ymlStatic Web App for rcm-app + master DB migrate job
deploy-dev-edi-gateway.ymlApp Service slot for edi-gateway + slot swap
deploy-dev-edi-app.ymlStatic Web App for edi-app
deploy-dev-docs.ymlMatrix workflow that builds + deploys all four docs sites

Steps​

  1. Wire up the self-hosted GitHub Actions runner

    All deploy + infra-CI workflows run on the self-hosted runner inside the dev VNet (vm-runner-dev-eus2-01). The runner's managed identity holds the role assignments for App Service, Static Web Apps, Key Vault, and the master DB. Federated identity on the runner means no client-secret rotation is required.

    Repo-level Actions variables (not secrets — these are non-sensitive):

    VariableValue
    AZURE_SUBSCRIPTION_IDThe MedSuite DevTest subscription ID
    AZURE_TENANT_IDThe Azure AD tenant ID
    AZURE_RG_DEVrg-rcm-dev-eus2
    KEY_VAULT_NAME_DEVkv-rcm-dev-eus2

    Secrets are seeded out-of-band into Key Vault (Terraform no longer provisions them — see Secret rotation).

  2. Trigger the deploy workflows

    gh workflow run deploy-dev-rcm-core.yml
    gh workflow run deploy-dev-rcm-app.yml -f run_migrate_master=true -f run_seed_master=true
    gh workflow run deploy-dev-edi-gateway.yml
    gh workflow run deploy-dev-edi-app.yml
    gh workflow run deploy-dev-docs.yml
    gh run watch

    Each backend workflow:

    1. Builds the workspace with pnpm turbo build.
    2. Bundles rcm-core / edi-gateway with esbuild (the SFTP / ssh2 native bindings stay external — see commit bc65543).
    3. Deploys the bundle to the App Service staging slot, then runs the slot-swap CLI scoped to actual Swap operations.
    4. Health-checks /health + /api/health before marking the swap complete.

    The SPA workflows deploy directly to the Static Web App with the SPA-fallback config in staticwebapp.config.json so deep links resolve.

  3. Bind managed identities to Key Vault

    Each App Service has a system-assigned managed identity. Grant Key Vault Secrets User on the Vault so backends can read secrets at startup:

    for app in app-rcmcore-dev-eus2 app-edigateway-dev-eus2; do
    PRINCIPAL_ID=$(az webapp identity show \
    --resource-group rg-rcm-dev-eus2 \
    --name "$app" \
    --query principalId -o tsv)
    az role assignment create \
    --assignee "$PRINCIPAL_ID" \
    --role "Key Vault Secrets User" \
    --scope $(az keyvault show -n kv-rcm-dev-eus2 --query id -o tsv)
    done

    Static Web Apps also have a system-assigned managed identity used by the SWA linked-API pattern; Terraform grants it the appropriate roles in infra/terraform/modules/static-web-apps.

  4. Verify each service is up

    curl -fsSL https://api.rcm.medsuite.com/health
    curl -fsSL https://api.rcm.medsuite.com/api/health
    curl -fsSL https://edi.medsuite.com/health
    curl -fsSL https://edi.medsuite.com/api/health

    Each /health route returns {"status":"ok"}. The /api/health shim was added in commit d955d0f so SWA-routed /api/* traffic also resolves before slot swaps release new revisions.

Validation​

CheckExpected
gh run list --workflow deploy-dev-rcm-core.yml --limit 1latest run is completed/success
curl api.rcm.medsuite.com/health200 + {"status":"ok"}
curl edi.medsuite.com/health200 + {"status":"ok"}
curl api.rcm.medsuite.com/metrics200 (Prometheus exposition)

Troubleshooting​

SymptomLikely causeFix
/health returns 503Service can't reach master DBCheck the App Service VNet integration + firewall rule on the master DB. PG passwords with ?#&: characters silently empty out — see PG URL gotcha.
Cannot find secret in App Service logsManaged identity missing role assignmentRe-run step 3 for the affected app.
Build fails with turbo daemon errors in CIStale Turbo cacheClear .turbo cache in the workflow or pin the Turbo version.
Bundle fails with native .node binaryssh2 / ssh2-sftp-client not externalConfirm esbuild marks them external (commit bc65543).

Next​

1.4 — Master DB initialization