Azure-arkitektur
4. Azure-arkitektur
Denne side er din mental-model af TesseraCMS's Azure-infrastruktur — hvad lever hvor, hvordan de hænger sammen, og hvad du skal vide for at drifte det.
Resource group
Alt lever i rg-palle-portfolio (legacy-navn fra dengang dette var Palle's portfolio; nu hoster det hele platformen). Resource group + subscription:
Subscription: 1df54212-9e59-4904-a0e5-0bcfe5529ba1
Resource group: rg-palle-portfolio
Region: northeurope
Når vi laver nye ressourcer, prefix med tesseracms-* så det er klart de tilhører platform-CMSet og ikke Palle specifikt.
Container App
tesseracms — Azure Container App der hoster Next.js SSR-appen.
- Image: bygges fra
Dockerfilei repo'et; pushet til Azure Container Registry (ACR)palleportfolioacraf GitHub Actions - Replicas: min 1, max 3 (auto-scaling baseret på HTTP-traffic)
- CPU/memory: 0.5 CPU / 1 GB per replica
- Ingress: external (offentligt tilgængelig), port 3000
- Easy Auth: AAD provider configureret → app er hidden bag login (med public-page-undtagelser via middleware)
- Custom domains: pallejacobsen.dk + nyborg-rideklub.dk + flere (per tenant). Hver med managed cert
Skalering
Default håndterer fint dagens trafik (~1000 requests/dag på tværs af alle tenants). Hvis vi vokser:
- Bump
max-replicastil 5-10 - Overvej
Workload profiletil consumption+dedicated mix - For >10k req/dag overvej caching-layer foran (CDN eller Front Door)
Storage Account
palleportfoliostor — Azure Storage account med både tabeller og blobs.
Tables
- Tenants — én row pr. tenant (slug, displayName, siteType, layoutPackage, status, customDomains)
- TenantModules — én row pr. tenant + module (aktivering, konfig som JSON)
- TenantRoles — én row pr. tenant + bruger (rolle, tildelingsdato)
- RoleAuditLog — append-only log over rolle-ændringer
- ContentItems — ALT user-genereret content (pages, heros, formularer, rollups, brands, snippets, ponies, news, …). Partition key =
<slug>:<type>, row key =<id> - Counters — sequential ID-tællere pr. (tenant, type)
- FormSubmissions — indkomne form-submissions (separat fra ContentItems for retention-politik)
- MediaIndex — index over uploadede blobs med alt-text, dimensions, refs
Blobs
media-container — alle uploadede billeder + dokumenter- Path-struktur:
<tenant-slug>/<type>/<id>/<filename>— fxpalle_jacobsen/Hero/hero_main/cover.jpg - Public access: blob-level (offentligt læsbart), container-level lukket
- CORS: konfigureret via
scripts/configure-blob-cors.ts— kun vores domains må læse
Azure Communication Services (Email)
tesseracms-acs — ACS-ressource til transactional email (form-submission-notifikationer, invite-mails).
- Connected email-domain: managed domæne (Azure-leveret) eller custom domain afhængigt af tenant
- Sender adresse: fx
DoNotReply@<some-hash>.azurecomm.netfor managed, ellernoreply@kunde.dkfor custom - Templates: bor i koden som MJML, kompileres til HTML on-send
- Brand inheritance: hver tenant kan have egen logo + farver via
MailAsset-records (TASK-126-ish)
Cost-control: ACS koster pr. email + pr. data-volume. Vi sender ~10-50 mails/dag i øjeblikket — godt under nogen tærskel.
GitHub Actions deploy-pipeline
.github/workflows/deploy.yml håndterer CI/CD:
- Trigger: push til
master - Build job:
- Checkout
- Setup Node 22
npm cinpx tsc --noEmit(typecheck)npm run build(Next standalone-build)- Build Docker image fra
Dockerfile - Push image til ACR
- Deploy job:
- Azure login via OIDC (federated credential — INGEN secret)
az containerapp updatemed ny image-tag- Container App roller automatisk replicas til nye image
Typisk varighed: 3-4 min. Hvis det går længere, tjek Actions-fanen for stuck step.
Roll-back: GitHub → Actions → forrige successful run → Re-run all jobs → samme image-tag deploys igen.
Identity og auth
- Azure AD tenant: deler vi med Anthropic (mit ejer-tenant — vi har et eget app-registration)
- App registration:
TesseraCMS-Auth— håndterer Easy Auth-flowet - Graph API client:
TesseraCMS-Graph— bruges til user-search + B2B-invite. Credentials i Container App env-vars (GRAPH_CLIENT_ID, GRAPH_CLIENT_SECRET, GRAPH_TENANT_ID) - Federated credential: GitHub Actions bruger OIDC; ingen long-lived secret i repo
DNS & certs
Kunder ejer deres egne domains. For at koble:
- Kunden tilføjer CNAME-record:
<kunde-domain>→<acaapp-default-domain>.azurecontainerapps.io - Vi tilføjer custom domain i Azure (Verification + binding) — Let's Encrypt-cert udstedes automatisk
- Vi tilføjer domain i Tenants.customDomains
- Forventet propagation: 5 min - 24 timer (DNS-cache)
Cert auto-renewal: Azure håndterer det, men hvis CNAME går væk, fejler renewal. Tjek Custom domains-blade'n nu og da for warning-ikoner.
Cost-overblik (omtrentligt)
Månedlig drift:
- Container App: ~50-150 kr (afhænger af scaling-aktivitet)
- Storage Account: ~5-20 kr (lavt; vi har ikke meget data endnu)
- ACS Email: ~5-30 kr (afhænger af volumen)
- ACR: ~10 kr (basic tier)
- DNS records (hvis vi hoster): ~0 (kunden ejer deres)
Total: ~70-200 kr/måned for hele platformen. Bumps når vi får flere tenants med mere trafik.