Connecting to Aspire's Azure Postgres

The deployed clusters (prod, test) provision Azure Postgres Flexible Server with password auth disabled — only Azure AD entra-id auth is accepted. Password from dotnet user-secrets is for legacy/break-glass and does not work.

Quick connect

Mode FQDN
prod <pg-server>.postgres.database.azure.com
test look up: az postgres flexible-server list -g test-memex --query "[].fullyQualifiedDomainName" -o tsv
# Verify which AAD identity you're signed in as — your user must be granted
# Postgres AAD admin (or be a member of an AAD group that is).
az account show --query "user.name" -o tsv

# 1-shot token good for ~1 hour
PGPASSWORD=$(az account get-access-token \
  --resource-type oss-rdbms --query accessToken -o tsv)

psql "host=<pg-server>.postgres.database.azure.com \
      port=5432 dbname=memex user=$(az account show --query user.name -o tsv) \
      sslmode=require"

The oss-rdbms token resource maps to https://ossrdbms-aad.database.windows.net/.default. SSL is mandatory.

If psql is not installed: winget install PostgreSQL.PostgreSQL (Windows) — picks up psql.exe on PATH. Or use the C# script below.

C# alternative (no psql install needed)

Drop a script anywhere outside the repo (e.g. a temp directory):

#r "nuget: Npgsql, 9.0.2"
#r "nuget: Azure.Identity, 1.13.1"
using Azure.Core;
using Azure.Identity;
using Npgsql;

const string Host = "<pg-server>.postgres.database.azure.com";
const string Db   = "memex";
const string User = "rbuergi@systemorph.com"; // your AAD UPN

var token = await new DefaultAzureCredential().GetTokenAsync(
    new TokenRequestContext(new[] { "https://ossrdbms-aad.database.windows.net/.default" }));
await using var conn = new NpgsqlConnection(
    $"Host={Host};Database={Db};Username={User};Password={token.Token};SSL Mode=Require");
await conn.OpenAsync();

await using var cmd = new NpgsqlCommand("SELECT current_user, version()", conn);
await using var rdr = await cmd.ExecuteReaderAsync();
while (await rdr.ReadAsync())
    Console.WriteLine($"{rdr[0]}  {rdr[1]}");

Run with dotnet script your-query.csx (one-time dotnet tool install -g dotnet-script if missing). Write such scripts as throwaways — run, then delete; the connection pattern above plus the cheat sheet below is everything a new one needs. Don't commit them to the repo.

Cheat sheet for migration / partition state

-- 1. What migration version did the runner reach?
SELECT id, content
  FROM admin.mesh_nodes
 WHERE id = 'db_version';

-- 2. Per-user / per-org content schemas (post-V10 layout)
SELECT schema_name FROM information_schema.schemata s
 WHERE EXISTS (SELECT 1 FROM information_schema.tables t
               WHERE t.table_schema = s.schema_name AND t.table_name='mesh_nodes')
   AND s.schema_name NOT IN ('public','admin','information_schema','pg_catalog','pg_toast','user')
   AND s.schema_name NOT LIKE '%\_versions' ESCAPE '\'
 ORDER BY schema_name;

-- 3. Where do AccessAssignments live for a given user?
SELECT 'user'      AS schema, namespace, content
  FROM "user".access  WHERE content->>'accessObject' = 'rbuergi'
UNION ALL
SELECT 'acme'      AS schema, namespace, content
  FROM acme.access    WHERE content->>'accessObject' = 'rbuergi';

-- 4. Cross-schema search for a node by id (use when "where does X live?" is the question)
DO $$
DECLARE r RECORD;
BEGIN
    FOR r IN SELECT schema_name FROM information_schema.schemata s
             WHERE EXISTS (SELECT 1 FROM information_schema.tables t
                           WHERE t.table_schema = s.schema_name AND t.table_name='mesh_nodes')
               AND s.schema_name NOT IN ('information_schema','pg_catalog','pg_toast','public')
    LOOP
        EXECUTE format(
          'SELECT %L AS schema, id, namespace, node_type FROM %I.mesh_nodes WHERE id = ''loss-model''',
          r.schema_name, r.schema_name);
    END LOOP;
END $$;

Reading migration logs

The migration runs as an Aspire db-migration resource that completes before the portal starts. Logs are in Container Apps:

az containerapp logs show -n db-migration -g prod-memex --tail 200
# follow live:
az containerapp logs show -n db-migration -g prod-memex --follow

If migration crashed mid-run, you'll see the Unhandled exception at the bottom and the partial schema state in the DB. The db_version row is only written after all migrations complete cleanly — so a missing db_version plus a non-empty schema set means the runner crashed mid-flight.

Common failure modes

Where the prod DB lives

Resource Value
Resource Group prod-memex
Server <pg-server>.postgres.database.azure.com
Database memex
Auth Azure AD only (password disabled)
Tenant your AAD tenant — az account show --query tenantId -o tsv
Logs Loki (via Promtail scraping pod stdout); metrics/traces via OTLP → Prometheus/Grafana

For test cluster, swap prod-memextest-memex and discover the FQDN with the az postgres flexible-server list command above.

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.