* feat(deploy): add Azure deployment templates (App Service + ACI) Adds a deploy/azure/ lane so Open Design can be deployed to Microsoft Azure from the published runtime image, alongside the existing Docker Compose and Helm options. Covers the App Service + ACI scope of #1028. Two Bicep templates run the same single-port Alpine image used by deploy/docker-compose.yml and charts/open-design: - app-service.bicep: App Service for Containers with managed HTTPS, Always On, and health checks on /api/health. - aci.bicep: a single Azure Container Instances group with a public FQDN and an /api/health liveness probe. These are evaluation deployments: state lives on the container's local disk and is ephemeral. Open Design stores SQLite under OD_DATA_DIR, and SQLite needs real file locking, which the Azure Files (SMB) storage behind both App Service and ACI persistence cannot provide without corruption. App Service sets WEBSITES_ENABLE_APP_SERVICE_STORAGE=false to keep the data dir on local disk. Durable self-hosting stays on the Compose named volume or the Helm PVC. Both wire the daemon's env contract (OD_BIND_HOST/OD_PORT/OD_WEB_PORT/ OD_DATA_DIR/OD_PUBLIC_BASE_URL/OD_ALLOWED_ORIGINS/OD_API_TOKEN) and take the API token as a secure parameter so it never appears in deployment outputs. deploy-azure.sh wraps `az` to create the resource group, generate a token when one isn't supplied, deploy a lane, and print the URL. README.md documents both lanes, the ephemeral-data caveat, and the security trade-offs. deploy/tests/azure-bicep.test.ts guards the runtime contract and that the data dir is never mounted to Azure Files; when the bicep CLI is present it also compiles both templates. * docs(deploy): add ACI health-check example to Azure quick start
Azure deployment (evaluation)
Deploy Open Design to Microsoft Azure from the published runtime image — the
same single Alpine image used by deploy/docker-compose.yml
and the Helm chart. The daemon serves both the API
and the built web UI on one port, so there is no separate web container.
Important
These lanes are for evaluation and demos, not durable data. Open Design stores its state in a SQLite database under
/app/.od, and SQLite needs real file locking. The persistent-storage options on both App Service and ACI are backed by Azure Files (SMB), where SQLite WAL/locking is unsupported and will corrupt the database. These templates therefore keep the data dir on the container's local disk, which has correct locking but is ephemeral — data is reset on restart, redeploy, or scale.For durable self-hosting today, use
deploy/docker-compose.yml(named volume) or the Helm chart (PVC withReadWriteOnce). A durable Azure lane needs block storage (e.g. a VM with a managed disk) and is out of scope here.
Two lanes are provided:
| Lane | Template | Best for | Public endpoint |
|---|---|---|---|
| App Service for Containers | app-service.bicep |
Always-on eval with managed HTTPS | https://<app>.azurewebsites.net |
| Azure Container Instances (ACI) | aci.bicep |
Quick, pay-per-second eval | http://<dns>.<region>.azurecontainer.io:7456 |
Both run as a single instance (Open Design uses single-writer SQLite).
Prerequisites
- Azure CLI (
az), logged in withaz login - An API token. Generate one with
openssl rand -hex 32; the daemon requiresOD_API_TOKENand will not start without it.
Quick start
The wrapper script creates the resource group, generates a token if you don't supply one, deploys the chosen template, and prints the URL:
# App Service (managed HTTPS, always on)
deploy/azure/deploy-azure.sh \
--target app-service \
--resource-group open-design-rg \
--location eastus
# Azure Container Instances (serverless, pay-per-second)
deploy/azure/deploy-azure.sh \
--target aci \
--resource-group open-design-rg \
--location eastus
First container start takes a couple of minutes while Azure pulls the image.
deploy-azure.sh prints the exact Health: URL when it finishes — poll that
until it returns ok:
# App Service
curl -fsS https://<app>.azurewebsites.net/api/health
# ACI (note the :7456 port)
curl -fsS http://<dns>.<region>.azurecontainer.io:7456/api/health
Deploy with az directly
The templates are standard Bicep, so you can skip the wrapper and call az:
az group create --name open-design-rg --location eastus
az deployment group create \
--resource-group open-design-rg \
--template-file deploy/azure/app-service.bicep \
--parameters apiToken="$(openssl rand -hex 32)"
Swap app-service.bicep for aci.bicep to use the ACI lane.
Parameters
Both templates share these parameters (defaults in parentheses):
| Parameter | Description |
|---|---|
name (open-design) |
Base name; a unique suffix is appended to globally-scoped resources |
location (resource group location) |
Azure region |
image (docker.io/vanjayak/open-design:latest) |
Container image; pin to a digest for production |
apiToken (required, secure) |
API token guarding the daemon |
nodeOptions (--max-old-space-size=192) |
Node.js heap cap |
extraAllowedOrigins (empty) |
Extra comma-separated browser origins allowed to call /api |
App Service adds appServicePlanSku (B1). ACI adds cpuCores (1) and
memoryInGb (1.5).
The deployed app's own origin is always added to OD_ALLOWED_ORIGINS, and
OD_PUBLIC_BASE_URL is set to that origin so OAuth callbacks resolve correctly.
Use extraAllowedOrigins only if you serve the UI from an additional hostname.
Pin a specific image
Use a digest instead of the mutable latest tag for reproducible deployments:
deploy/azure/deploy-azure.sh \
--target app-service \
--resource-group open-design-rg \
--image docker.io/vanjayak/open-design@sha256:<digest>
Security notes
- App Service terminates HTTPS for you (
httpsOnlyis enabled) and is the recommended lane for any internet-facing deployment. To restrict access further, layer App Service Authentication ("Easy Auth") or IP restrictions on top. - ACI exposes the daemon's port directly over plain HTTP with no managed
TLS. Access is still gated by
OD_API_TOKEN, and browser traffic byOD_ALLOWED_ORIGINS, but treat this lane as evaluation / trusted-network use. For production with HTTPS, use App Service or place ACI behind Application Gateway / Front Door. - Keep
OD_API_TOKENsecret. It is passed as a secure parameter (and a secure environment variable on ACI), so it is not returned in deployment outputs.
Updating
Re-run the same command with a newer --image (or the same tag after a new
push). Note that App Service / ACI store data on ephemeral local disk, so a
redeploy starts from an empty data dir.
Tearing down
Delete the whole resource group when you're done:
az group delete --name open-design-rg --yes --no-wait