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

RiskDetail
Accidental prod deploysA developer with Contributor on dev can touch prod resources in the same subscription
Shared resource quotasA staging load test could exhaust vCPU/Redis quota and block prod autoscaling
RBAC complexityCannot cleanly restrict prod access without affecting dev workflows
iGaming complianceMGA, UKGC, and CZ licensing audits require demonstrable production environment isolation
Cost visibilitySingle 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

RoleNon-Prod SubscriptionProd Subscription
DevelopersContributorReader only
CI/CD Service Principal (non-prod)ContributorNo access
CI/CD Service Principal (prod)AcrPull on ACR onlyContributor
Team Lead / DevOpsContributorContributor

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 AcrPush role
  • Developers get AcrPull only — 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 ConnectionSubscriptionUsed By
feg-azure-nonprodfeg-non-prodDev + staging deploy pipelines, all Docker builds, ACR push
feg-azure-prodfeg-prodProd 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

EnvironmentSubscriptionResource GroupACR
devfeg-non-prodfeg-slot-rg-devfegslotacr (same sub)
stagingfeg-non-prodfeg-slot-rg-stagingfegslotacr (same sub)
productionfeg-prodfeg-slot-rg-prodfegslotacr (cross-sub pull)
Built with LogoFlowershow