Azure
The Django backend and both PostgreSQL databases live in Azure under nonprofit credits. Everything else is in Cloudflare.
Resource groups
EMAW-Development
| Resource | Type | Purpose | Access |
|---|---|---|---|
development-django-vm | Linux VM | Runs the backend compose stack from /opt/emaw/emaw-django-app | SSH over Tailscale with a key |
development-database-vm | Linux VM | PostgreSQL (behind pgbouncer) for dev | Only over Tailscale or from vnet-emaw-dev; no public endpoint |
vnet-emaw-dev | Virtual network | Connects the two VMs so the stack reaches the DB privately |
The dev Django VM connects to the database over the vnet. A developer's laptop running the stack connects to the same database over Tailscale (DB_HOST in .env is a Tailscale IP).
EMAW-APP-RG (production)
| Resource | Type | Purpose | Access |
|---|---|---|---|
DockerApp | Linux VM | Runs the production compose stack, deployed through Portainer | SSH with a key from an allow-listed IP; Portainer UI from an allow-listed IP |
emawweb-server | Azure Database for PostgreSQL flexible server | Production database | EMAWWebVnet only; the Django app is the only client. SSH is blocked. |
EMAWWebVnet | Virtual network | Links the VM and the database | |
emawblob | Storage account, container media | Django default file storage for both environments: resource cover images, generated verse audio, the Bible embeddings parquet | Key in AZURE_BLOB_KEY; SAS token in AZURE_BLOB_SAT for audio |
emaw-app | Key Vault | Holds the emaw-app-salt secret the production backend reads for username hashing (EMAW/utils.py); dev uses EMAW_SALT from .env | Managed identity on the VM |
EMAWWeb | App Service (legacy) | The pre-Docker deployment target of the main_emawweb.yml workflow and azure-pipelines.yml. Not the live backend. | See Backend deployment |
Other Azure services the backend uses
| Service | Used by | Env vars |
|---|---|---|
| Azure Communication Services (Email) | Marketing campaigns (campaigns/acs.py), delivery and engagement events via Event Grid webhook | ACS_CONNECTION_STRING, ACS_SENDER_ADDRESS, ACS_SENDER_NAME, ACS_REPLY_TO, ACS_EVENTGRID_WEBHOOK_KEY |
| Azure Maps (Search) | Geocoding contacts and event venues (crm/geocoding.py) | AZURE_MAPS_KEY, GEOCODING_ENABLED |
| Microsoft Graph (Entra app registration) | Transactional email: verification codes, password resets, receipts (EMAW/backends/graph_email.py) | MS_TENANT_ID, MS_CLIENT_ID, MS_CLIENT_SECRET, MS_SENDER_UPN |
Portainer
Production deploys are done through Portainer at https://52.234.34.217:9443/. Your public IP must be allow-listed (ask Matt Birne). Portainer manages the compose stack on the DockerApp VM: pull the new image or git pull, then redeploy the stack. See Backend deployment.
Nonprofit credits
EMAW receives Microsoft nonprofit Azure credits that cover these resources. Keep an eye on the credit balance in the portal; if credits lapsed, the server and database environment would cost roughly $150 a month.
Things to know
- Both environments write to the same blob container. Do not delete or overwrite blobs casually; prod cover images and audio live next to dev's.
- The production VM and database are not on Tailscale yet. That migration is planned; until then the IP whitelist is the control.
- The legacy
EMAWWebApp Service and its workflows still exist. A push tomainon the backend triggers them. They do not affect the Docker deployment, but they are a trap for anyone expectingmainto deploy the real backend.