Azure CI/CD
Set up OIDC (OpenID Connect) federation for GitHub Actions to authenticate with Azure without storing client secrets.
For generating workflow files and general CI/CD setup, see CI/CD Setup.
Overview
GitHub Actions mints a short-lived OIDC token for each workflow run. Azure verifies this token against the Federated Credential on the App Registration and grants access. No clientSecret is stored in GitHub.
The CI/CD setup reuses the same App Registration (Service Principal) created during Account Setup. The only difference is the authentication method.
You need an App Registration for each environment before continuing. If you haven't created one yet, complete Account Setup Steps 1–5 first.
Step 1: Add Federated Credential
Do this for each environment's App Registration (dev, staging, prod).
Navigate to the App Registration
- Go to Azure Portal > search "Microsoft Entra ID"
- Click App registrations, then select the All applications tab
- Click on your App Registration for this environment
Add the Credential
- Click Certificates & secrets > Federated credentials tab
- Click + Add credential
- Scenario: Select "GitHub Actions deploying Azure resources"
- Fill in:
- Organization: Your GitHub username or org
- Repository: Your repo name (e.g.,
my-project) - Entity type: Branch
- GitHub branch name:
main - Name:
github-actions-main
- Click Add
Repeat for each environment's App Registration.
Running from other branches? Add additional federated credentials with those branch names. You can have up to 20 federated credentials per App Registration.
Step 2: GitHub Repository Secrets
Go to your GitHub repository > Settings > Secrets and variables > Actions.
For each environment, set 4 secrets:
Repeat for staging and prod with _STAGING and _PROD suffixes.
No AZURE_CLIENT_SECRET needed — OIDC handles authentication.
The framework has no naming convention for environments. dev, staging, prod are common choices, but you can use any name. The suffix is always the UPPERCASE version of your environment name.
Private npm packages — NPM_TOKEN (optional, single global secret)
If your project depends on private npm packages, add a single NPM_TOKEN repository secret — not per-environment. Registry tokens identify your developer / org account, not a deployment target, so the same value works for every env.
The token is auto-detected and wired through generated workflows when an .npmrc exists at your project root. See CI/CD Setup — Private npm packages for the full setup.
User Secrets (Required Before First Deployment)
The CI workflow pushes framework-generated secrets automatically (cloud-secrets:push --skip-user-secrets), but user secrets must be created manually in Azure Key Vault before the first CI deployment.
Without these secrets, deployment will fail. The CI pipeline cannot prompt for interactive input.
Create the Key Vault (if it doesn't exist)
If you're setting up CI without running cloud:init locally, you need to create the Key Vault manually first.
- Go to the Azure Portal > search "Key vaults" > + Create
- Subscription: Select the subscription for this environment
- Resource group: Select the resource group you created during Account Setup
- Key vault name:
{projectName}-{env}-kv(e.g.,myapp-dev-kv) - Region: Must match your resource group region
- Pricing tier: Standard
- Click Review + create > Create
- Wait for the deployment to complete (a few minutes)
- After creation, open the Key Vault > Access control (IAM) > + Add > Add role assignment
- Tab: Job function roles > search Key Vault Secrets Officer
- Select members > select your own user account > assign
- Wait a couple minutes for the role to take effect before creating secrets
Creating Secrets in Azure Portal
- Go to the Azure Portal
- Search for "Key vaults" and select your project's Key Vault (
{projectName}-{env}-kv) - Click Secrets in the left menu
- Click + Generate/Import
- Upload options: Manual
- Name: Enter the full secret name with hyphens (e.g.,
myapp-shared-DOMAIN) - Secret value: Enter the value (e.g.,
example.com) - Click Create
Repeat for each required secret in each environment.
Secret Naming Format
Secrets in Azure Key Vault follow the format: {project-name}-{scope}-{KEY}
Where {project-name} is the project.name from your .tsdevstack/config.json.
For example, if your project name is myapp:
Required User Secrets
These are the minimum secrets required by the framework:
Azure Key Vault only allows alphanumeric characters and hyphens — no underscores. Use hyphens when creating secrets in the portal. The framework transforms them back to underscores (RESEND-API-KEY → RESEND_API_KEY) when injecting into your services.
Your project may have additional user secrets (e.g., STRIPE_KEY, TWILIO_SID). Any secret defined in .secrets.user.json that is not framework-generated must also be created manually in Azure Key Vault before deployment. Use the same naming format: {project-name}-shared-{KEY} (with hyphens instead of underscores).
Alternative: Using the CLI
If you have local credentials configured (see Account Setup), you can push all user secrets from your machine:
This will prompt for DOMAIN, RESEND_API_KEY, EMAIL_FROM, and any custom secrets interactively.
Workflow Authentication Pattern
The generated workflows authenticate using azure/login@v2:
The id-token: write permission is required — without it, GitHub won't issue the OIDC token and azure/login will fail.
No separate Docker auth step needed. Container Apps and Container Apps Jobs use a user-assigned managed identity with AcrPull and Key Vault Secrets User roles (provisioned by Terraform) to pull images from ACR and access secrets in Key Vault. No tokens, passwords, or service principal credentials are involved — the identity is permanent and works reliably with scale-to-zero. Docker build/push in CI still uses DefaultAzureCredential (picks up the OIDC session).
Troubleshooting
"AADSTS700016: Application with identifier '...' was not found"
Wrong AZURE_CLIENT_ID in GitHub secrets, or App Registration was deleted. Verify the Application (client) ID matches.
"AADSTS70021: No matching federated identity record found"
- Federated credential not created on the App Registration
- Branch name doesn't match (credential is for
mainbut workflow ran from another branch) - Wrong repository in the federated credential
Check: App Registration > Certificates & secrets > Federated credentials tab.
"azure/login failed" with no specific error
id-token: write permission missing from the workflow. Check the permissions block.
Terraform fails with "Error building AzureRM Client"
ARM_USE_OIDC=true not set, or ARM_CLIENT_ID/ARM_TENANT_ID missing. Verify the "Set Azure environment variables" step is present.
ACR push fails with "unauthorized: authentication required"
The azure/login OIDC session wasn't established before the CLI tried to push images. Ensure azure/login@v2 runs before any deploy commands.
"does not have authorization to perform action ... register/action"
Resource providers are not registered on the subscription. The CI service principal has resource-group-scoped roles, which are not sufficient for provider registration (a subscription-level operation).
Register all required providers in the Azure Portal before running CI. See Register Resource Providers in the Account Setup guide.