Database¶
One DjangoDeployment creates a single Azure Database for PostgreSQL Flexible Server (Entra ID authentication only; password auth disabled). Multiple Django apps on that deployment each get their own database via db_name.
Defaults include 32 GB storage with auto-grow and 7-day backup retention.
Deployment parameters¶
pgsql_sku¶
pgsql_sku=azure.dbforpostgresql.SkuArgs(
name="Standard_B1ms",
tier=azure.dbforpostgresql.SkuTier.BURSTABLE,
)
pgsql_ip_prefix¶
Subnet prefix for the PostgreSQL subnet (typically /24).
pgsql_version¶
Defaults to "17".
pgsql_parameters¶
Optional server configuration key/value map.
pgadmin_access_ip¶
IP allowlist for pgAdmin. Empty means open (still password-protected).
Per-website database¶
Exports:
{name}_site_db_user— e.g.prod_managed_identity{name}_site_principal_id— managed identity object IDpgsql_host— server FQDNpgsql_bootstrap_function_url— shared bootstrap Function HTTP endpoint
Entra principal for the Web App (automatic)¶
On pulumi up, add_django_website (default auto_create_db_principal=True) invokes a shared Azure Function on the existing App Service plan. That Function:
- Runs as a deployment-scoped user-assigned managed identity registered as an Entra Postgres administrator.
- Checks whether
{name}_managed_identityalready exists (viapgaadauth_list_principals); if not, callspgaadauth_create_principal_with_oid(non-admin). - Grants
CONNECTand sets the app role as owner of the app database. - If
db_schemais set: connects to the app database, runsCREATE SCHEMA IF NOT EXISTS, and grantsUSAGE, CREATEon that schema to the app role. Ifdb_schemais omitted, no schema is created andpublicis not assumed.
The flow is idempotent — re-running pulumi up skips principal creation when the role is already present with the expected object ID.
The Function only does meaningful work when invoked (during deploy / when principal inputs change). It stays Always On on the shared plan so invokes stay reliable.
Set auto_create_db_principal=False to skip automation and manage SQL yourself.
Optional schema (db_schema)¶
Pulumi creates the schema and grants permissions only. It does not set Django’s Postgres search_path. In your production settings you still need something like:
Manual fallback¶
As an Entra admin on the server, on the postgres database:
SELECT * FROM pgaadauth_create_principal_with_oid(
'prod_managed_identity',
'c8b25b85-d060-4cfc-bad4-b8581cfdf946',
'service',
false,
false
);
Use the role name from {name}_site_db_user and the GUID from {name}_site_principal_id. Then grant connect/ownership on the app database.
Microsoft docs: Create a role using Microsoft Entra object identifier.
Token authentication from Django¶
pulumi_django_azure.settings points DATABASES["default"]["ENGINE"] at pulumi_django_azure.db.backends.postgresql. It is Django's PostgreSQL backend with one override: get_connection_params() sets the password to a fresh Managed Identity access token (get_db_password()) at the moment a connection is opened.
Why per connection and not once at import: the token lives roughly 24 h, and Gunicorn workers are separate processes. A token read into settings.DATABASES at import time, or refreshed inside one worker, is never seen by the master (which forks new workers from its own stale copy) nor by the other workers or the RQ worker. Symptom of the old approach: FATAL: The access token has expired and HTTP 500s for 1-3 minutes after every worker recycle once the app had been running for a day.
Tokens are served from the credential's in-memory cache, so the per-connection cost is negligible; with CONN_MAX_AGE = None connections are persistent anyway. CONN_HEALTH_CHECKS = True makes Django verify a persistent connection at the start of each request and reopen it (with a fresh token) when the server dropped it, e.g. after a failover.
Because tokens are resolved per connection, long-running processes no longer need periodic restarts to pick up credentials: the RQ worker and rqcron programs in supervisord.conf run without a timeout wrapper.
Administrator login¶
django.add_database_administrator(
object_id="b306adf5-fc61-4a32-8156-ce032dc1571f",
user_name="you@example.com",
)
Temporary password/token:
Use your email as the username and the token as the password (pgAdmin or psql).
pgAdmin¶
Created on the shared App Service plan. Export: pgadmin_url.
Default credentials (change immediately):
- Login:
dbadmin@dbadmin.net - Password:
dbadmin
Create your own user and remove the default.