Deploy anywhere

Single cloud: Azure

The Azure reference profile: Container Apps, Flexible Server with Entra auth, Entra ID sign-in, Blob Storage, a Container Apps Job tick, Communication Services, Key Vault secrets and keys, Azure Cache for Redis and Azure Monitor.

The Azure reference profile. Every area runs in one Azure subscription and region, and the app reaches every service with one user-assigned managed identity, with no keys. It is tested locally against emulators (Deployment profiles), and the Bicep and Terraform templates in deploy/azure/ create it.

Single cloud: Azure

People and clients

Browser · REST API · MCP · CLI

UserTasks

Web, API and worker · Azure Container Apps (web, migrate job, tick job)

Built in

  • RealtimeSSE

Azure subscription (one region)Azure

  • DatabaseFlexible Server (Entra auth)
  • AuthBuilt-in + Entra ID
  • File storageBlob Storage
  • SchedulerContainer Apps Job tick
  • EmailCommunication Services
  • SecretsKey Vault
  • KMSKey Vault key / Managed HSM
  • CacheAzure Cache for Redis
  • Log exportAzure Monitor / Event Hubs
The Azure reference profile: Container Apps, PostgreSQL Flexible Server with Entra token auth, built-in auth with Entra ID sign-in, Blob Storage, a Container Apps Job tick, Communication Services email, Key Vault secrets and keys, Azure Cache for Redis and Azure Monitor log export, all with one managed identity.

What runs where

AreaAzure serviceProvider idNative or compatible
HostingContainer Apps: the web app, a migrate job, a tick job—The Docker image
DatabaseAzure Database for PostgreSQL – Flexible Server 16, Entra-only authpostgres, DATABASE_AUTH=azureNative: managed-identity token as the password
AuthBuilt-in, with Microsoft Entra ID over OIDC (or SAML)builtinNative presets entra, entra_multitenant, entra_external_id
StorageBlob Storage, shared keys disabledazure_blobNative: Storage Blob Data Contributor (and Storage Blob Delegator for signed URLs)
RealtimeBuilt-in Server-Sent EventssseBuilt in
SchedulerA Container Apps Job every minute calling POST /api/cron/tickexternalThe templates use CRON_SECRET; Entra-verified tokens (SCHEDULER_ENTRA_*) need an app registration
EmailAzure Communication Services Email (or Microsoft 365 through Graph)azure_communication / microsoft_graphNative: managed identity
SecretsKey Vault (RBAC)azure_key_vaultNative: Key Vault Secrets User
KMSA Key Vault key or Managed HSM key wrapping the data keysazure_key_vaultNative: Key Vault Crypto Service Encryption User
CacheAzure Cache for Redis / Azure Managed Redis with Entra authredis, CACHE_REDIS_AUTH=entraNative: managed-identity tokens, re-sent before they expire
Log exportAzure Monitor Logs Ingestion (Log Analytics, Sentinel) or Event Hubsazure_monitor, azure_event_hubs destinationsNative: an app registration, or the app's identity when LOG_EXPORT_ALLOW_INSTANCE_IDENTITY=true (single-tenant installs only)
Directory syncEntra ID through Microsoft Graph, or SCIM pushed by Entraper workspaceNative

The image includes the Blob Storage, Communication Services Email, Key Vault secrets and keys SDKs by default. The Snowflake, Oracle and MongoDB drivers need --build-arg INCLUDE_OPTIONAL_DB_DRIVERS=true; see Optional drivers.

Configuration

bash
DB_PROVIDER=postgres
DATABASE_AUTH=azure
AZURE_CLIENT_ID=<managed identity client id>
DATABASE_URL=postgresql://<identity name>@acme-ut.postgres.database.azure.com:5432/usertasks?sslmode=require
AUTH_PROVIDER=builtin
AUTH_SSO_PROVIDERS=[{"id":"entra","type":"oidc","preset":"entra","tenantId":"contoso.onmicrosoft.com","clientId":"…","clientSecret":"secret://entra-client-secret","allowedDomains":["contoso.com"]}]
REALTIME_PROVIDER=sse
SCHEDULER_PROVIDER=external
STORAGE_PROVIDER=azure_blob
AZURE_STORAGE_ACCOUNT=acmeusertasks
STORAGE_BUCKET_PREFIX=usertasks-
EMAIL_PROVIDER=azure_communication
ACS_EMAIL_ENDPOINT=https://acme-ut.communication.azure.com
EMAIL_FROM=DoNotReply@<guid>.azurecomm.net
SECRETS_PROVIDER=azure_key_vault
AZURE_KEY_VAULT_URL=https://acme-ut.vault.azure.net
ENCRYPTION_KEY=secret://encryption-key
KMS_PROVIDER=azure_key_vault
AZURE_KMS_KEY_ID=https://acme-ut.vault.azure.net/keys/usertasks-kek
CACHE_PROVIDER=redis
CACHE_REDIS_URL=rediss://acme-ut.redis.cache.windows.net:6380
CACHE_REDIS_AUTH=entra
CACHE_REDIS_USERNAME=<managed identity object id>

The step-by-step deployment (infrastructure, image, grants, migrate job, first workspace, Entra sign-in) is in Azure setup.

Getting here from Supabase

Deploy the infrastructure with the templates, and run the app on Container Apps against the existing Supabase project. Then switch each area.

bash
# 1. Areas that don't depend on the database
usertasks-admin switch storage --to azure_blob --set azure_account_name=acmeusertasks --set bucket_prefix=usertasks- --yes
usertasks-admin switch email   --to azure_communication --set acs_endpoint=https://acme-ut.communication.azure.com \
  --set from=DoNotReply@<guid>.azurecomm.net --test-to you@contoso.com --yes
usertasks-admin switch secrets --to azure_key_vault --set azure_key_vault_url=https://acme-ut.vault.azure.net --yes
usertasks-admin switch kms     --to azure_key_vault --set azure_key_id=https://acme-ut.vault.azure.net/keys/usertasks-kek --yes
usertasks-admin kms rewrap --yes
usertasks-admin switch cache   --to redis --set redis_url=rediss://acme-ut.redis.cache.windows.net:6380 \
  --set redis_auth=entra --set redis_username=<object id> --yes

# 2. The Supabase-only areas
usertasks-admin switch auth      --to builtin --map azure=oidc:entra --yes   # everyone signs in again
usertasks-admin switch realtime  --to sse --yes
usertasks-admin switch scheduler --to external --yes                         # the tick job takes over

# 3. Stop writes, then copy the database (connect as an Entra admin or azure_pg_admin)
usertasks-admin switch database --to postgres --target-url 'postgresql://…@acme-ut.postgres.database.azure.com:5432/usertasks?sslmode=require' --yes

Then restart with the printed database settings plus DATABASE_AUTH=azure, and remove the SUPABASE_* variables.

Trade-offs

  • Cost: Flexible Server and Container Apps are the main items. Azure Cache for Redis is optional with one replica (use CACHE_PROVIDER=postgres instead). Log Analytics ingestion grows with the event stream.
  • Operations: one managed identity, no keys to rotate. Everything is visible in one subscription. You own database sizing, maintenance windows and high availability (zone-redundant HA).
  • Latency: the app and the database share a region. The Flexible Server's built-in PgBouncer (port 6432) suits the app's queries. Keep migrations, LISTEN and the worker's leader lock on port 5432.
  • Compliance: Entra ID for people and services, Key Vault or Managed HSM for keys, Azure Monitor for the audit trail. Sovereign clouds work through endpoint overrides (AZURE_STORAGE_BLOB_ENDPOINT, GRAPH_ENDPOINT, the log destinations' cloud).

Data residency

Choose one region for the resource group. Blob Storage redundancy (LRS, ZRS, GRS) decides whether files leave the region. Pick the paired-region options on purpose. Communication Services has a data location of its own, set when you create the resource.

Failure domains

One cloud. Use zone-redundant HA for the database, and more than one Container Apps replica with the shared cache. If the tick job fails, jobs wait and catch up on the next successful tick. As an alternative, the built-in worker can run in the app (minimum replicas ≥ 1).

Not wired yet

  • Entra-verified tick tokens in the templates (the job uses CRON_SECRET). The app supports them with SCHEDULER_ENTRA_*.