Azure Subscription Strategy
Azure Subscription Strategy
Decision: 2 Subscriptions (Non-Prod + Prod)
All FEG environments are split across two Azure subscriptions — not one, not three.
Azure Tenant (FEG)
├── Subscription: feg-non-prod
│ ├── feg-slot-rg-dev (Resource Group — dev environment)
│ ├── feg-slot-rg-staging (Resource Group — staging environment)
│ └── fegslotacr (Azure Container Registry — shared, all envs)
│
└── Subscription: feg-prod
└── feg-slot-rg-prod (Resource Group — production environment)
Why Not a Single Subscription
| Risk | Detail |
|---|---|
| Accidental prod deploys | A developer with Contributor on dev can touch prod resources in the same subscription |
| Shared resource quotas | A staging load test could exhaust vCPU/Redis quota and block prod autoscaling |
| RBAC complexity | Cannot cleanly restrict prod access without affecting dev workflows |
| iGaming compliance | MGA, UKGC, and CZ licensing audits require demonstrable production environment isolation |
| Cost visibility | Single subscription mixes dev/staging burn with production costs |
Why Not 3 Separate Subscriptions
- Dev and staging share the same risk profile — no real player data, no financial transactions
- Adding a third subscription adds IaC complexity, extra Service Principals, and extra Azure DevOps Service Connections for minimal compliance benefit
- The 2-subscription model already satisfies regulatory environment separation requirements
ACR Lives in Non-Prod Subscription
A single Azure Container Registry (fegslotacr) lives in the feg-non-prod subscription.
Prod Container Apps pull images from it cross-subscription via Managed Identity RBAC.
Rationale:
- Build once, promote everywhere — no image copying between registries
- Single registry to manage and pay for ($20/month Standard SKU)
- Image promotion is a re-tag operation, not a rebuild or copy
Image Promotion Flow
1. Push to main branch
- CI builds Docker image
- Tags: {commit-sha}
- Pushes to fegslotacr (non-prod subscription)
- Deploys to dev Container Apps
2. Manual promote to staging
- Re-tag: staging-{sha}
- Deploy to staging Container Apps (same non-prod ACR)
3. QA approved - promote to prod
- Re-tag: prod-{sha}
- Deploy to prod Container Apps (cross-subscription pull from non-prod ACR)
- No rebuild, no image copy — exact same artifact that passed staging
Cross-Subscription ACR Pull Setup
The prod Managed Identity needs AcrPull role on the non-prod ACR. This is a cross-subscription role assignment configured in Pulumi:
// In feg-prod Pulumi stack
// nonprodAcrId comes from feg-non-prod stack output
new azure.authorization.RoleAssignment("prod-acr-pull", {
scope: nonprodAcrId,
roleDefinitionName: "AcrPull",
principalId: prodManagedIdentity.principalId,
});
Intra-region image pulls within Azure are free regardless of subscription boundary.
RBAC Model
| Role | Non-Prod Subscription | Prod Subscription |
|---|---|---|
| Developers | Contributor | Reader only |
| CI/CD Service Principal (non-prod) | Contributor | No access |
| CI/CD Service Principal (prod) | AcrPull on ACR only | Contributor |
| Team Lead / DevOps | Contributor | Contributor |
Prod deployments go through CI/CD pipeline only — no human has direct Contributor access to prod outside of break-glass scenarios.
ACR Push Security
Because the ACR lives in the non-prod subscription where developers have Contributor access, restrict image push at the registry level:
- CI/CD Service Principal only gets
AcrPushrole - Developers get
AcrPullonly — they can pull images locally but cannot push to the shared registry - This ensures no developer can push a modified image that reaches production regardless of subscription placement
Azure DevOps Service Connections
Two Service Connections in Azure DevOps, each pointing to a different subscription:
| Service Connection | Subscription | Used By |
|---|---|---|
feg-azure-nonprod | feg-non-prod | Dev + staging deploy pipelines, all Docker builds, ACR push |
feg-azure-prod | feg-prod | Prod promotion pipeline only (manual trigger + approval gate) |
Cost Impact of This Split
Negligible — slightly positive.
- Azure subscriptions themselves are free
- Each subscription gets its own Container Apps free tier grant (180,000 vCPU-seconds/month) — non-prod free grant reduces staging compute cost by ~$5/month
- Single ACR in non-prod costs $20/month — same as if it were in prod, no duplication cost
- No second ACR needed — saves $20/month vs a two-registry setup
Environment-to-Subscription Mapping Summary
| Environment | Subscription | Resource Group | ACR |
|---|---|---|---|
| dev | feg-non-prod | feg-slot-rg-dev | fegslotacr (same sub) |
| staging | feg-non-prod | feg-slot-rg-staging | fegslotacr (same sub) |
| production | feg-prod | feg-slot-rg-prod | fegslotacr (cross-sub pull) |