[!WARNING] EXPERIMENTAL ONLY: This onboarding flow is provided for testing and evaluation. Do not use Git-Ape in production environments. Validate all generated configuration manually before any real deployment.
Set up a GitHub repository to use Git-Ape’s CI/CD pipelines for Azure deployments. This guide covers Entra ID (Azure AD) configuration, OIDC federation, RBAC, and GitHub repository setup.
Git-Ape supports two onboarding modes:
| Mode | Use case | GitHub Environments | Secrets scope |
|---|---|---|---|
| Single environment | One Azure subscription for all deployments | azure-deploy, azure-destroy |
Repository-level |
| Multi-environment | Separate subscriptions per stage (dev/staging/prod) | azure-deploy-dev, azure-deploy-staging, azure-deploy-prod, azure-destroy |
Environment-level |
You can run onboarding from Copilot Chat with:
@Git-Ape Onboarding onboard this repository
or directly invoke the skill:
/git-ape-onboarding
Both paths execute the same onboarding playbook through Copilot Chat.
The skill-driven onboarding flow will gather or use:
https://github.com/your-org/your-reposp-git-ape-your-repoaz subscriptionSingle environment:
/git-ape-onboarding onboard https://github.com/your-org/your-repo on subscription 00000000-0000-0000-0000-000000000000 with Contributor
Multi-environment:
/git-ape-onboarding onboard https://github.com/your-org/your-repo with dev on 11111111-1111-1111-1111-111111111111 as Contributor, staging on 22222222-2222-2222-2222-222222222222 as Contributor, prod on 33333333-3333-3333-3333-333333333333 as Contributor+UserAccessAdministrator
Each multi-environment entry creates:
azure-deploy-{name} with environment-level secretsIf you prefer to inspect or execute each component manually, follow the steps below.
Tip: Run
/prereq-checkin Copilot Chat to automatically validate all tools and auth sessions.
| Tool | Minimum Version | Purpose |
|---|---|---|
Azure CLI (az) |
2.50+ | Azure resource management, RBAC, OIDC |
GitHub CLI (gh) |
2.0+ | Repo secrets, environments, OIDC detection |
| jq | 1.6+ | JSON parsing in scripts and workflows |
| git | any | Version control (usually pre-installed) |
Install (pick your platform):
You must be logged in to both:
az login # Azure — needs Owner or User Access Administrator on the subscription(s)
gh auth login # GitHub — needs admin access to the target repository
This creates the identity that GitHub Actions will use to authenticate with Azure.
# Choose a name for your app registration
SP_NAME="sp-git-ape-your-repo"
# Create the app registration
CLIENT_ID=$(az ad app create --display-name "$SP_NAME" --query appId -o tsv)
echo "Client ID: $CLIENT_ID"
# Create the service principal
az ad sp create --id "$CLIENT_ID"
# Note your tenant ID
TENANT_ID=$(az account show --query tenantId -o tsv)
echo "Tenant ID: $TENANT_ID"
OIDC eliminates stored secrets by letting GitHub Actions exchange a short-lived token for Azure access at runtime. The number of federated credentials depends on your mode.
# Get the app object ID (different from client ID)
OBJECT_ID=$(az ad app show --id "$CLIENT_ID" --query id -o tsv)
# Your GitHub repo (org/repo format)
REPO="your-org/your-repo"
# Detect whether the GitHub org uses default or customized OIDC subjects
USE_DEFAULT_SUBJECT=$(gh api "orgs/${REPO%%/*}/actions/oidc/customization/sub" --jq '.use_default' 2>/dev/null || echo true)
if [[ "$USE_DEFAULT_SUBJECT" == "false" ]]; then
REPO_ID=$(gh api "repos/$REPO" --jq '.id')
OWNER_ID=$(gh api "repos/$REPO" --jq '.owner.id')
OIDC_PREFIX="repository_owner_id:${OWNER_ID}:repository_id:${REPO_ID}"
else
OIDC_PREFIX="repo:$REPO"
fi
Use $OIDC_PREFIX for all subjects below. On orgs with a customized subject template, repo:org/repo:... will fail with AADSTS700213.
az ad app federated-credential create --id "$OBJECT_ID" --parameters '{
"name": "fc-main-branch",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "'"$OIDC_PREFIX"':ref:refs/heads/main",
"description": "Main branch deployments",
"audiences": ["api://AzureADTokenExchange"]
}'
az ad app federated-credential create --id "$OBJECT_ID" --parameters '{
"name": "fc-pull-request",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "'"$OIDC_PREFIX"':pull_request",
"description": "Pull request validation",
"audiences": ["api://AzureADTokenExchange"]
}'
az ad app federated-credential create --id "$OBJECT_ID" --parameters '{
"name": "fc-env-destroy",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "'"$OIDC_PREFIX"':environment:azure-destroy",
"description": "Destroy environment",
"audiences": ["api://AzureADTokenExchange"]
}'
az ad app federated-credential list --id "$OBJECT_ID" --query "[].{name:name, subject:subject}" -o table
Single environment — expected 4 credentials:
Name Subject
----------------- -----------------------------------------------
fc-main-branch <OIDC_PREFIX>:ref:refs/heads/main
fc-pull-request <OIDC_PREFIX>:pull_request
fc-env-deploy <OIDC_PREFIX>:environment:azure-deploy
fc-env-destroy <OIDC_PREFIX>:environment:azure-destroy
Multi-environment (3 envs) — expected 6 credentials:
Name Subject
---------------------- ---------------------------------------------------
fc-main-branch <OIDC_PREFIX>:ref:refs/heads/main
fc-pull-request <OIDC_PREFIX>:pull_request
fc-env-deploy-dev <OIDC_PREFIX>:environment:azure-deploy-dev
fc-env-deploy-staging <OIDC_PREFIX>:environment:azure-deploy-staging
fc-env-deploy-prod <OIDC_PREFIX>:environment:azure-deploy-prod
fc-env-destroy <OIDC_PREFIX>:environment:azure-destroy
Grant the service principal permissions on your Azure subscription(s).
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
SP_OBJECT_ID=$(az ad sp show --id "$CLIENT_ID" --query id -o tsv)
# Contributor — create, modify, and delete resources
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope "/subscriptions/$SUBSCRIPTION_ID"
If your templates include RBAC role assignments (e.g., managed identity access to storage), also add:
# User Access Administrator — manage role assignments
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "User Access Administrator" \
--scope "/subscriptions/$SUBSCRIPTION_ID"
Assign roles on each target subscription. Each environment can have a different role if needed:
SP_OBJECT_ID=$(az ad sp show --id "$CLIENT_ID" --query id -o tsv)
# Dev — Contributor only
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope "/subscriptions/$DEV_SUBSCRIPTION_ID"
# Staging — Contributor only
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope "/subscriptions/$STAGING_SUBSCRIPTION_ID"
# Production — Contributor + User Access Administrator
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope "/subscriptions/$PROD_SUBSCRIPTION_ID"
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "User Access Administrator" \
--scope "/subscriptions/$PROD_SUBSCRIPTION_ID"
Note: If multiple environments share the same subscription, you only need one set of role assignments for that subscription.
az role assignment list --assignee "$SP_OBJECT_ID" --query "[].{role:roleDefinitionName, scope:scope}" -o table
These are identifiers, not credentials — OIDC means no actual secrets are stored.
azure-destroy — for destroy jobs (same for both modes):
gh api -X PUT "repos/$REPO/environments/azure-destroy" --input - <<'EOF'
{
"deployment_branch_policy": null
}
EOF
For production deployments, add required reviewers to the deploy environment:
In multi-environment mode, you might want:
azure-deploy-dev — no reviewer required (fast iteration)azure-deploy-staging — optional reviewerazure-deploy-prod — required reviewer (gate for production)Copy the workflow files to your repository:
# Clone the git-ape repo if you haven't
git clone https://github.com/your-org/git-ape.git /tmp/git-ape
# Copy workflows to your repo
cp /tmp/git-ape/.github/workflows/git-ape-*.yml your-repo/.github/workflows/
# Commit and push
cd your-repo
git add .github/workflows/
git commit -m "feat: add Git-Ape deployment workflows"
git push
The following workflows will be added:
| Workflow | Trigger | Purpose |
|---|---|---|
git-ape-plan.yml |
PR with template changes | Validate, security scan, what-if, cost estimate |
git-ape-deploy.yml |
Merge to main or /deploy comment |
Execute ARM deployment |
git-ape-destroy.yml |
Merge PR with destroy-requested status |
Delete resource group |
git-ape-verify.yml |
Manual dispatch | Verify OIDC, RBAC, and pipeline health |
Note: Drift detection and TTL-based cleanup are being replaced by agentic workflows — coming soon.
Create a test deployment to verify the pipeline works:
# Create a minimal test template
mkdir -p .azure/deployments/deploy-test
cat > .azure/deployments/deploy-test/template.json <<'EOF'
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": { "type": "string", "defaultValue": "eastus" }
},
"resources": []
}
EOF
cat > .azure/deployments/deploy-test/parameters.json <<'EOF'
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": { "value": "eastus" }
}
}
EOF
# Open a PR
git checkout -b test/git-ape-onboarding
git add .azure/deployments/deploy-test/
git commit -m "test: verify git-ape pipeline"
git push -u origin test/git-ape-onboarding
gh pr create --title "Test: Git-Ape onboarding" --body "Verify the OIDC pipeline works end-to-end."
If the PR triggers the Git-Ape: Plan workflow and it succeeds, your setup is complete.
To get Slack notifications on deploy/destroy/drift events:
echo "https://hooks.slack.com/services/T.../B.../..." | gh secret set SLACK_WEBHOOK_URL -R "$REPO"
The federated credential subject doesn’t match the workflow’s token. Verify:
az ad app federated-credential list --id "$OBJECT_ID" -o table
Common issues:
subject fieldpull_request subject is needed for PR-triggered workflowsenvironment:azure-deploy subject is needed for jobs using environment: azure-deployThe service principal lacks permissions. Check assignments:
az role assignment list --assignee "$SP_OBJECT_ID" -o table
Ensure Contributor role is assigned at the subscription scope.
The OIDC token exchange succeeded but the subscription doesn’t match. Verify:
# Check which subscription the service principal can access
az account list --query "[?tenantId=='$TENANT_ID']" -o table
Environment creation requires admin access to the repository. Ask a repo admin to create the azure-deploy and azure-destroy environments manually via Settings → Environments.
┌──────────────────────────────────────────────────────────────────────┐
│ GitHub Repository │
│ │
│ Secrets (repo-level): Environments: │
│ ┌─────────────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ AZURE_CLIENT_ID │ │ azure-deploy │ │ azure-destroy │ │
│ │ AZURE_TENANT_ID │ │ (main only) │ │ (any branch) │ │
│ │ AZURE_SUBSCRIPTION_ID│ └──────┬───────┘ └──────┬────────┘ │
│ │ SLACK_WEBHOOK_URL ⁽¹⁾│ │ │ │
│ └──────────┬──────────┘ │ │ │
│ │ │ │ │
│ Workflows: │ │ │ │
│ ┌──────────┴──────────────────────────┴──────────────────┴────────┐ │
│ │ git-ape-plan.yml → OIDC token (PR subject) │ │
│ │ git-ape-deploy.yml → OIDC token (main / azure-deploy env) │ │
│ │ git-ape-destroy.yml → OIDC token (azure-destroy env) │ │
│ │ git-ape-verify.yml → OIDC token (workflow_dispatch) │ │
│ └──────────┬──────────────────────────────────────────────────────┘ │
└─────────────┼────────────────────────────────────────────────────────┘
│ OIDC token exchange
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Entra ID (Azure AD) │
│ │
│ App Registration: sp-git-ape-{repo} │
│ ┌────────────────────────────────────────────┐ │
│ │ Client ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxx │ │
│ │ │ │
│ │ Federated Credentials: │ │
│ │ • repo:org/repo:ref:refs/heads/main │ │
│ │ • repo:org/repo:pull_request │ │
│ │ • repo:org/repo:environment:azure-deploy │ │
│ │ • repo:org/repo:environment:azure-destroy│ │
│ └────────────────────┬───────────────────────┘ │
└───────────────────────┼──────────────────────────────────────────────┘
│ Service Principal
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Azure Subscription │
│ │
│ RBAC: Contributor (+ User Access Administrator if RBAC in templates) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ rg-app-dev │ │ rg-api-prod │ │ rg-data-stg │ ... │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
⁽¹⁾ Optional
┌──────────────────────────────────────────────────────────────────────────────────┐
│ GitHub Repository │
│ │
│ Repo-level Secrets: Environment Secrets: │
│ ┌───────────────────┐ ┌─ azure-deploy-dev ──────────────────────────┐ │
│ │ AZURE_CLIENT_ID │ │ AZURE_CLIENT_ID, AZURE_TENANT_ID │ │
│ │ AZURE_TENANT_ID │ │ AZURE_SUBSCRIPTION_ID → Dev Sub │ │
│ └───────────────────┘ └─────────────────────────────────────────────┘ │
│ ┌─ azure-deploy-staging ──────────────────────┐ │
│ │ AZURE_CLIENT_ID, AZURE_TENANT_ID │ │
│ │ AZURE_SUBSCRIPTION_ID → Staging Sub │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─ azure-deploy-prod ─────────────────────────┐ │
│ │ AZURE_CLIENT_ID, AZURE_TENANT_ID │ │
│ │ AZURE_SUBSCRIPTION_ID → Prod Sub │ │
│ │ ⚠️ Required reviewers │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─ azure-destroy ─────────────────────────────┐ │
│ │ AZURE_CLIENT_ID, AZURE_TENANT_ID │ │
│ │ AZURE_SUBSCRIPTION_ID → Default Sub │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────────────────────┘
│ OIDC token exchange
▼
┌──────────────────────────────────────────────────────────────────────────────────┐
│ Entra ID (Azure AD) │
│ │
│ App Registration: sp-git-ape-{repo} │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Federated Credentials: │ │
│ │ • repo:org/repo:ref:refs/heads/main │ │
│ │ • repo:org/repo:pull_request │ │
│ │ • repo:org/repo:environment:azure-deploy-dev │ │
│ │ • repo:org/repo:environment:azure-deploy-staging │ │
│ │ • repo:org/repo:environment:azure-deploy-prod │ │
│ │ • repo:org/repo:environment:azure-destroy │ │
│ └────────────────────┬───────────────────────────────────┘ │
└───────────────────────┼──────────────────────────────────────────────────────────┘
│ Service Principal (shared)
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Dev Sub │ │ Staging Sub │ │ Prod Sub │
│ Contributor │ │ Contributor │ │ Contributor + │
│ │ │ │ │ UAA │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ rg-*-dev │ │ │ │ rg-*-stg │ │ │ │ rg-*-prod│ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
└──────────────┘ └──────────────┘ └──────────────┘
| Aspect | Implementation |
|---|---|
| No stored secrets | OIDC federated identity — no client secrets or certificates |
| Scoped access | Federated credentials are scoped per repo + branch/environment |
| Least privilege | Only Contributor role by default; add UAA only if needed |
| Environment gates | Deploy environments restricted to main branch; reviewers optional |
| Destructive protection | azure-destroy environment can require manual approval |
| Subscription isolation | Multi-env mode targets separate subscriptions per stage |
| Audit trail | All deployments logged in state.json with actor, timestamp, run URL |
When using multi-environment mode:
azure-deploy-prodWith multi-environment mode, update your deploy workflow to select the correct environment:
# In git-ape-deploy.yml, change the environment field to be dynamic:
deploy:
environment: azure-deploy-$
# This resolves to azure-deploy-dev, azure-deploy-staging, or azure-deploy-prod
# based on the "environment" parameter in parameters.json
The environment parameter in your parameters.json determines which GitHub environment (and therefore which Azure subscription) is used:
{
"parameters": {
"environment": { "value": "prod" },
"location": { "value": "eastus" },
"project": { "value": "myapp" }
}
}