Files
youcef zr 6efac95b53 feat(deploy): Azure deployment support (App Service + ACI) (#3387)
* 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
2026-06-23 06:53:45 +00:00
..

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 with ReadWriteOnce). 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 with az login
  • An API token. Generate one with openssl rand -hex 32; the daemon requires OD_API_TOKEN and 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 (httpsOnly is 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 by OD_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_TOKEN secret. 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