Deployment Guide
How to build, deploy, and operate ArrowFlow on Azure Container Apps — from ACR image builds to production promotion.
ArrowFlow is deployed as a Docker container on Azure Container Apps (ACA). This guide covers the full deployment lifecycle from building an image to promoting to production.
Architecture Overview
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ ACR Build │────▶│ Staging │────▶│ Production │ │ (image tag) │ │ arrowflow- │ │ arrowflow- │ │ │ │ staging │ │ app │ └──────────────┘ └──────────────┘ └──────────────┘
| Component | Resource | URL |
|---|---|---|
| Production | arrowflow-app | https://app.arrowflow.app |
| Staging | arrowflow-staging | On-demand |
| Website | arrowflow-website | https://arrowflow.app |
| Registry | arrowflowacr.azurecr.io | — |
| Environment | arrowflow-app-env | North Central US |
Prerequisites
- Azure CLI (
az) logged in - Access to subscription
SAWORKS DEV Subs - Resource group:
arrowflow-rg
Step 1: Build the Image
ArrowFlow uses a multi-stage Dockerfile (Dockerfile.backend):
- Stage 1: Build the Vite frontend
- Stage 2: Node 20 Alpine production image with backend + frontend dist
# Build and push to ACR
VERSION=v3.5.0
SHORT_SHA=$(git rev-parse --short HEAD)
IMAGE_TAG="${VERSION}-${SHORT_SHA}"
az acr build \
--registry arrowflowacr \
--image arrowflow-app:${IMAGE_TAG} \
--file Dockerfile.backend .
Step 2: Deploy to Staging
Deploy using the Bicep template to validate before production:
az deployment group create \
--resource-group arrowflow-rg \
--template-file infra/staging.bicep \
--parameters containerImage="arrowflowacr.azurecr.io/arrowflow-app:${IMAGE_TAG}" \
--parameters cosmosConnectionString="<secret>" \
--parameters storageConnectionString="<secret>" \
--parameters clerkSecretKey="<secret>" \
--parameters clerkPublishableKey="<secret>" \
--parameters credentialMasterKey="<secret>" \
--parameters adminApiKey="<secret>" \
--parameters payloadInternalApiKey="<secret>" \
--parameters payloadContentEncryptionKey="<secret>"
Smoke Test Staging
STAGING_FQDN=$(az containerapp show --name arrowflow-staging \
--resource-group arrowflow-rg --query "properties.configuration.ingress.fqdn" -o tsv)
curl -s "https://${STAGING_FQDN}/api/health" | jq .
curl -s -o /dev/null -w "%{http_code}" "https://${STAGING_FQDN}/"
Step 3: Promote to Production
Update the production container app to use the new image:
az containerapp update \
--name arrowflow-app \
--resource-group arrowflow-rg \
--image "arrowflowacr.azurecr.io/arrowflow-app:${IMAGE_TAG}"
Verify Production
curl -s https://app.arrowflow.app/api/health | jq .
curl -s -o /dev/null -w "%{http_code}" https://app.arrowflow.app/
Step 4: Tear Down Staging
Once validated, remove the staging app to save costs:
az containerapp delete --name arrowflow-staging \ --resource-group arrowflow-rg --yes
Production Container Resources
| Setting | Value |
|---|---|
| CPU | 0.75 vCPU |
| Memory | 1.5 Gi |
| Min replicas | 1 |
| Max replicas | 10 |
| Scale rule | HTTP — 50 concurrent requests |
| Identity | SystemAssigned (Key Vault RBAC) |
Required Secrets
All secrets are stored as ACA secrets and mapped to environment variables:
| Env Var | Secret Name | Purpose |
|---|---|---|
AZURE_COSMOS_CONNECTION_STRING | cosmos-conn-str | CosmosDB access |
AZURE_STORAGE_CONNECTION_STRING | storage-conn-str | Blob storage |
CLERK_SECRET_KEY | clerk-secret-key | Auth backend |
CLERK_PUBLISHABLE_KEY | clerk-publishable-key | Auth frontend |
CREDENTIAL_MASTER_KEY | credential-master-key | AES-256-GCM key encryption |
ADMIN_API_KEY | admin-api-key | Admin API authentication |
PAYLOAD_INTERNAL_API_KEY | payload-internal-api-key | Payload CMS internal |
PAYLOAD_CONTENT_ENCRYPTION_KEY | payload-content-encryption-key | Payload 64-char hex |
AZURE_FOUNDRY_API_KEY | foundry-api-key | Azure AI Foundry |
Non-secret env vars (set as plain values):
NODE_ENV=productionAZURE_FOUNDRY_ENDPOINT=https://arrowflow-ai.cognitiveservices.azure.com/
Clerk Authentication
The app uses Clerk for authentication with a custom domain setup:
- Frontend API:
clerk.arrowflow.app - Sign-in domain:
app.arrowflow.app
Redirect URLs (Clerk Dashboard)
These must be added to the Clerk dashboard under "Redirect URLs":
https://app.arrowflow.app/https://arrowflow-app.politehill-bd759da7.northcentralus.azurecontainerapps.io/
If deploying staging with auth, also add the staging FQDN.
Rollback
To roll back to a previous image:
# List recent revisions
az containerapp revision list \
--name arrowflow-app \
--resource-group arrowflow-rg \
--query "[].{name:name, active:properties.active, traffic:properties.trafficWeight}" -o table
# Revert to previous image tag
az containerapp update \
--name arrowflow-app \
--resource-group arrowflow-rg \
--image "arrowflowacr.azurecr.io/arrowflow-app:<previous-tag>"
Reference Files
deployment/production.env— Full production ACA config (non-secret values)infra/staging.bicep— Staging Bicep templateDockerfile.backend— Multi-stage build definition.github/instructions/deployment.instructions.md— Agent deployment instructions