Skip to main content

Azure — NHS Quickstart

☁️ App Service / Container Apps · Azure SQL · Key Vault · Managed Identity · Front Door · App Insights
Why Azure for NHS internal apps

Managed TLS-by-default hosting close to M365/Entra ID. Great for APIs (FastAPI/Express), dashboards (React/Dash/Shiny), and Azure SQL when you want a managed SQL Server engine. Use Key Vault for secrets and Managed Identity so apps run without passwords.

Great for: Developer · Data Engineer (services & pipelines) · BI (thin UI).


⚙️ 10‑minute “hello” deploy (App Service — container)

Goal: containerise a tiny API and deploy to an HTTPS URL (lock down later).

0) Minimal API + Dockerfile

service/main.py
from fastapi import FastAPI
app = FastAPI()

@app.get("/health")
def health(): return {"status":"ok"}

@app.get("/hello")
def hello(): return {"message":"Hello, NHS!"}
service/Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY service/main.py ./main.py
RUN pip install fastapi uvicorn[standard]
EXPOSE 8000
CMD ["uvicorn","main:app","--host","0.0.0.0","--port","8000"]

1) Create ACR and push

# variables
RG=rg-nhs
REGION=uksouth
ACR=nhsregistry
IMAGE=nhs-hello-api

az group create -n $RG -l $REGION
az acr create -g $RG -n $ACR --sku Basic
az acr login -n $ACR

# build & push
docker build -t $IMAGE ./
docker tag $IMAGE:latest $ACR.azurecr.io/$IMAGE:latest
docker push $ACR.azurecr.io/$IMAGE:latest

2) App Service (Linux, container)

PLAN=nhs-plan
APP=nhs-hello-api

az appservice plan create -g $RG -n $PLAN --is-linux --sku B1
az webapp create -g $RG -p $PLAN -n $APP --deployment-container-image-name $ACR.azurecr.io/$IMAGE:latest

# configure container port and health check
az webapp config appsettings set -g $RG -n $APP --settings WEBSITES_PORT=8000
az webapp config set -g $RG -n $APP --health-check-path /health

You’ll get an HTTPS URL like: https://<app-name>.azurewebsites.net/hello.

Lock down later: Private networking (VNET integration), Front Door + WAF, and IP allow‑lists. For private DB access, use Private Endpoints.


🗃️ Azure SQL (managed SQL Server)

SQLSERVER=nhs-sql-srv
SQLDB=NHS_Analytics

az sql server create -g $RG -n $SQLSERVER -u svc_ds -p "ChangeMe_123!"
az sql db create -g $RG -s $SQLSERVER -n $SQLDB --service-objective S0

Recommended: use Managed Identity + contained users (no passwords).

# enable system-assigned identity on the web app
az webapp identity assign -g $RG -n $APP
APP_OBJECT_ID=$(az webapp identity show -g $RG -n $APP --query principalId -o tsv)
echo $APP_OBJECT_ID

Connect to Azure SQL as an Azure AD admin, then in the database run:

-- Create contained AAD user mapped to the app's managed identity
CREATE USER [nhs-hello-api] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [nhs-hello-api];
-- add db_datawriter only if needed

Use your actual app name in square brackets. If names collide, use the identity’s object ID notation supported by your tooling.


🔑 Key Vault for secrets

KV=nhs-secrets

az keyvault create -g $RG -n $KV
az keyvault secret set --vault-name $KV -n SqlPassword --value "ChangeMe_123!"
# grant the web app's managed identity access to secrets
az keyvault set-policy -n $KV --object-id $APP_OBJECT_ID --secret-permissions get list

At runtime, retrieve secrets via SDKs (Python: azure-identity, azure-keyvault-secrets; Node: @azure/identity, @azure/keyvault-secrets).
For App Service, you can also use Key Vault references in app settings:

@Microsoft.KeyVault(SecretUri=https://nhs-secrets.vault.azure.net/secrets/SqlPassword/)

🚀 Azure Container Apps (serverless containers)

If you prefer Kubernetes-like flexibility without running AKS:

ENV=nhs-env

az containerapp env create -g $RG -n $ENV -l $REGION
az containerapp create -g $RG -n nhs-hello-api --environment $ENV --image $ACR.azurecr.io/$IMAGE:latest --ingress external --target-port 8000 --registry-server $ACR.azurecr.io --registry-username $(az acr credential show -n $ACR --query username -o tsv) --registry-password $(az acr credential show -n $ACR --query passwords[0].value -o tsv)

Container Apps supports scale to zero, VNETs, secrets, and Dapr (optional).


🌐 Static UI (Storage Static Website + Front Door)

STO=nhsstatic$RANDOM

# storage for static website
az storage account create -g $RG -n $STO -l $REGION --sku Standard_LRS
az storage blob service-properties update --account-name $STO --static-website --404-document 404.html --index-document index.html

# upload build output
az storage blob upload-batch --account-name $STO -d '$web' -s dist/

# Front Door (Standard/Premium) recommended for HTTPS, WAF, and caching (configure via portal)

Set your frontend env to call the API:

.env.local
VITE_API_URL=https://<app-name>.azurewebsites.net

🔒 NHS‑ready patterns

  • Identity: prefer Managed Identity over passwords; Entra ID for user auth (NextAuth / OAuth2 bearer for APIs).
  • Networking: VNET integration, Private Endpoints for Azure SQL and Key Vault; restrict public access where possible.
  • Observability: App Insights + Log Analytics; alerts on 5xx rate, latency, and CPU.
  • WAF/Edge: Front Door + WAF; IP allow‑lists; custom headers to protect origins.
  • Cost control: right‑size plans; autoscale on Container Apps; lifecycle rules for Storage and ACR.

🛡 IG & safety checklist

  • Keep secrets out of code; store in Key Vault; rotate regularly.
  • Avoid PHI in logs; set log retention per Trust policy.
  • Use least‑privilege DB roles; prefer contained AAD users.
  • Apply small‑number suppression in aggregated endpoints.
  • Record DPIA reference and system owner in the repo README/runbook.

📏 Measuring impact

  • Reliability: uptime, p95 latency, error rate.
  • Security: secret rotation cadence; zero committed secrets.
  • Cost: monthly cost per service; storage lifecycle effectiveness.
  • Adoption: endpoints used; UI sessions; teams onboarded.

🔗 See also

What’s next?

You’ve completed the Learn — Azure stage. Keep momentum: