Files
nexu-io-open-design/docs/deployment/cloud/azure.md
Marc Chan 7abb7888df fix(deploy): align Docker defaults with GHCR releases (#4327)
* fix(deploy): align Docker defaults with GHCR releases

Generated-By: looper 0.9.9 (runner=worker, agent=opencode)

* fix(ci): publish stable Docker tags from release workflow

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(ci): fold reusable workflow guard expression

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(ci): gate Docker release publish

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(ci): publish stable Docker tags after release

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(ci): guard Docker latest tag enable expression

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(deploy): update Helm chart GHCR defaults

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)

* fix(ci): publish latest from release workflow inputs

Generated-By: looper 0.9.9 (runner=fixer, agent=opencode)
2026-06-17 12:25:18 +08:00

5.9 KiB

Azure Container Instances

This guide deploys the Docker image to Azure Container Instances (ACI) with persistent Open Design data. Before choosing or documenting any daemon data mount, read root AGENTS.mdDaemon data directory contract. That section is mandatory and must not be restated here.

ACI is the daemon upstream in this topology. The browser-facing app URL must be served by an authenticated TLS reverse proxy that forwards traffic to ACI, injects the daemon bearer token on /api/* requests, and sends a browser origin listed in OD_ALLOWED_ORIGINS.

Before You Start

  • Azure CLI installed and signed in
  • Permission to create a resource group, storage account, file share, and container group
  • A public Docker image, or an image in a registry that ACI can pull

Step 1: Choose Names

export RESOURCE_GROUP=open-design-aci
export LOCATION=eastus
export DEPLOYMENT_NAME=open-design-aci
export DNS_LABEL=open-design-$RANDOM
export BROWSER_ORIGIN=https://od.example.com
export OD_API_TOKEN="$(openssl rand -hex 32)"

The DNS label must be unique in the Azure region. BROWSER_ORIGIN is the HTTPS origin users will open after a trusted proxy is in front of the daemon. The API token is required because the daemon binds to 0.0.0.0 inside the container; keep this token in your proxy or deployment secrets and do not expose it to browser code.

Step 2: Create The Resource Group

az group create \
  --name "$RESOURCE_GROUP" \
  --location "$LOCATION"

Step 3: Deploy The Bicep Template

Run this from the repository root:

az deployment group create \
  --name "$DEPLOYMENT_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --template-file deploy/azure/container-instance.bicep \
  --parameters \
    location="$LOCATION" \
    dnsNameLabel="$DNS_LABEL" \
    allowedOrigins="$BROWSER_ORIGIN" \
    odApiToken="$OD_API_TOKEN"

The template creates:

  • Azure Storage account
  • Azure Files share for persistent daemon storage. Before choosing or documenting its mount, you MUST read root AGENTS.mdDaemon data directory contract.
  • Linux Azure Container Instances container group
  • Public upstream DNS name and TCP port 7456
  • Liveness probe against /api/health

Step 4: Fetch The ACI Upstream

Fetch the daemon upstream host for your reverse proxy:

export ACI_FQDN="$(az deployment group show \
  --resource-group "$RESOURCE_GROUP" \
  --name "$DEPLOYMENT_NAME" \
  --query "properties.outputs.daemonFqdn.value" \
  --output tsv)"
export ACI_UPSTREAM_URL="http://${ACI_FQDN}:7456"

Do not open this URL directly in a browser. The daemon requires Authorization: Bearer <OD_API_TOKEN> on non-loopback /api/* requests, and the web UI does not put that secret in browser requests.

Step 5: Put A Trusted Proxy In Front

Serve BROWSER_ORIGIN from a TLS reverse proxy that authenticates users before forwarding traffic to the ACI upstream. The proxy must add the daemon token to API requests:

upstream open_design_aci {
  server <aci-fqdn>:7456;
}

server {
  listen 443 ssl;
  server_name od.example.com;

  # Add your organization's authentication layer here before proxying.

  location /api/ {
    proxy_set_header Authorization "Bearer <OD_API_TOKEN>";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;
    gzip off;
    proxy_pass http://open_design_aci;
  }

  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://open_design_aci;
  }
}

Replace <aci-fqdn> with ACI_FQDN, replace <OD_API_TOKEN> with the same secret passed to the Bicep deployment, and keep BROWSER_ORIGIN equal to the origin served by the proxy. Keep the /api/ buffering, gzip, HTTP/1.1, and timeout directives in place so streamed generation responses are not cut off by nginx. After the proxy is configured, open BROWSER_ORIGIN in your browser.

Optional Parameters

az deployment group create \
  --name "$DEPLOYMENT_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --template-file deploy/azure/container-instance.bicep \
  --parameters \
    odApiToken="$OD_API_TOKEN" \
    dnsNameLabel="$DNS_LABEL" \
    allowedOrigins="$BROWSER_ORIGIN" \
    image="ghcr.io/nexu-io/od:latest" \
    cpuCores=1 \
    memoryInGB=1 \
    fileShareQuotaGB=10

Set allowedOrigins to the comma-separated browser origins served by trusted proxies. A direct public ACI browser URL is not supported because browser API calls cannot safely carry the daemon token.

Azure DevOps

Use deploy/azure/azure-pipelines.yml as a starting point.

Before running it:

  • Create an Azure Resource Manager service connection.
  • Set OD_API_TOKEN as a secret pipeline variable.
  • Update resourceGroupName, location, openDesignImage, and browserOrigin.
  • Replace <your-azure-service-connection> with your service connection name.

Operations

View logs:

az container logs \
  --resource-group "$RESOURCE_GROUP" \
  --name open-design

Restart the container group:

az container restart \
  --resource-group "$RESOURCE_GROUP" \
  --name open-design

Delete all Azure resources created by this guide:

az group delete \
  --name "$RESOURCE_GROUP"

Security Notes

  • Do not expose the raw ACI endpoint as the browser-facing app URL. Use it as the upstream for a trusted proxy or for token-authenticated operational checks.
  • Keep OD_API_TOKEN secret. The proxy may use it upstream, but browser clients must not receive it. Rotate it by redeploying with a new value.
  • Keep allowedOrigins aligned with the browser-visible proxy origin; otherwise the daemon origin guard will reject browser API requests.
  • The Azure Files share persists project data after container restarts and image updates.