Skip to main content
There are two environments: production, and a shared development environment. They are deliberately different — production is network-isolated, development is publicly reachable.

Production

Topology

Production runs in a dedicated Azure subscription, separate from development.

App Gateway

Application Gateway WAF v2 as the single public entry point. Everything behind it is private. Routing is path-based: /api/* to the backend, /inspect/* to the inspector PWA, and everything else to the frontend SPA.

Network isolation

The Container Apps environment is created with internal ingress — its load balancer is private and no public endpoint exists. App FQDNs resolve, through a private DNS zone, to an address inside the VNet. Three subnets, each with its own NSG: the gateway subnet, the private-endpoint subnet, and the Container Apps subnet. The gateway subnet can reach the apps on 443 and nothing else.

Data platform

PostgreSQL Flexible Server sits behind a private endpoint with publicNetworkAccess disabled. There are no firewall rules because there is no public listener to firewall.
  • Geo-redundant backups with 35-day retention, committed at first provisioning — this setting is immutable after creation, so it was not deferred.
  • RPO ≈ minutes from continuous backup. RTO ≈ tens of minutes for a point-in-time restore; geo-restore covers a regional disaster.
  • TLS required on all connections.
  • A zone-redundant HA standby is not enabled — recovery is restore-based. Unlike the backup setting this one is mutable, so HA can be switched on later without recreating the server.
Azure OpenAI is reached over a private endpoint with public access disabled. Key Vault runs in firewall mode, denying by default and admitting only the two VNet subnets. Container Registry stays public but authenticated — images are pulled by managed identity and pushes are gated by Entra RBAC, on the reasoning that images carry no secrets, since secrets are injected at runtime from Key Vault.

Storage and the ingestion contract

The storage account keeps a public endpoint by deliberate decision, because both of its workloads require one: inspection photos are uploaded and read directly from the browser via SAS URLs, and producers drop ingestion files into landing containers. Authorisation is by SAS with stored access policies rather than by network position:
  • Producer tokens are create, write and list only — no read, no delete. A leaked token cannot read ingestion history from archive/ or tamper with processed files.
  • Policy expiry is 12 months, with rotation built into project onboarding.
  • Anonymous blob access is disabled; HTTPS-only, TLS 1.2.
The practical consequence for an integrating system is that there are no network prerequisites. No static egress IP to register, no firewall rule to request. Drop correctly formatted files into the project’s landing/ container with the issued SAS, and the pipeline takes them from there. The landing zone is the interface, and it is producer-agnostic by design. Data Factory reaches the private database through a Managed VNet integration runtime with a managed private endpoint — pipeline definitions, staging schema and stored procedures are unchanged from development.

Development

The development environment is public by design and unchanged — Container Apps with external ingress, a PostgreSQL server reachable on its public endpoint, and public Key Vault, registry and OpenAI. It is the reference for what production would look like without hardening, which is precisely why the two are kept separate.
Because development is publicly reachable, a data-hygiene rule applies: production data does not go into the development environment. Treat everything there as test data.
Both environments run the same application images. The differences are build arguments and environment variables, not code branches — the production frontend build points at a relative /api path since it is same-origin behind the gateway, and the inspector PWA is mounted under /inspect/.