Temporal on Azure Container Apps: Complete Setup Guide
Last Updated: December 24, 2025
Status: Production-Ready ✅
Maintainer: Engram Platform Team
Executive Summary
This document provides the complete, battle-tested configuration for running Temporal durable workflows on Azure Container Apps (ACA). After extensive debugging on December 23-24, 2025, we identified the exact networking requirements for gRPC communication.
TL;DR - The Working Configuration
Temporal Server:
- Ingress Type: EXTERNAL (not internal!)
- Transport: HTTP/2
- Target Port: 7233
- FQDN: staging-env-temporal-server.gentleriver-dd0de193.eastus2.azurecontainerapps.io
Worker/API Connection:
- Endpoint: {server-fqdn}:443 (NOT :7233!)
- TLS: Enabled (required)
- Protocol: gRPC over HTTPS
Step-by-Step Setup Guide
Step 1: Deploy Temporal Server with Correct Ingress
The Temporal Server must use external ingress with HTTP/2 transport.
// infra/modules/temporal-aca.bicep
resource temporalServer 'Microsoft.App/containerApps@2023-05-01' = {
name: '${appName}-server'
properties: {
configuration: {
ingress: {
// CRITICAL: Must be external for gRPC to work!
external: true
targetPort: 7233
// REQUIRED: HTTP/2 transport for gRPC protocol
transport: 'http2'
allowInsecure: false
}
}
template: {
containers: [{
name: 'temporal-server'
image: 'temporalio/auto-setup:latest'
env: [
{ name: 'DB', value: 'postgres12' }
{ name: 'POSTGRES_USER', value: 'cogadmin' }
{ name: 'POSTGRES_PWD', secretRef: 'postgres-password' }
{ name: 'POSTGRES_SEEDS', value: '<postgres-fqdn>' }
{ name: 'POSTGRES_DB', value: '<database-name>' }
// IMPORTANT: Set to false to auto-create "default" namespace
{ name: 'SKIP_DEFAULT_NAMESPACE_CREATION', value: 'false' }
{ name: 'SQL_TLS', value: 'true' }
{ name: 'SQL_TLS_ENABLED', value: 'true' }
{ name: 'POSTGRES_TLS_ENABLED', value: 'true' }
{ name: 'POSTGRES_TLS_DISABLE_HOST_VERIFICATION', value: 'true' }
]
}]
}
}
}
If already deployed with wrong config, fix via CLI:
# Make ingress external
az containerapp ingress update \
--name staging-env-temporal-server \
--resource-group engram-rg \
--type external
# Set HTTP/2 transport
az containerapp ingress update \
--name staging-env-temporal-server \
--resource-group engram-rg \
--transport http2
# Enable namespace creation
az containerapp update \
--name staging-env-temporal-server \
--resource-group engram-rg \
--set-env-vars "SKIP_DEFAULT_NAMESPACE_CREATION=false"
Step 2: Get the Temporal Server FQDN
az containerapp show \
--name staging-env-temporal-server \
--resource-group engram-rg \
--query "properties.configuration.ingress.fqdn" \
--output tsv
# Example output:
# staging-env-temporal-server.gentleriver-dd0de193.eastus2.azurecontainerapps.io
⚠️ IMPORTANT: The FQDN should NOT contain .internal. - if it does, your ingress is still set to internal.
Step 3: Configure Worker with Correct TEMPORAL_HOST
The worker must connect to port 443 (not 7233) because Azure ingress routes through HTTPS.
# Set the correct TEMPORAL_HOST format
az containerapp update \
--name staging-env-worker \
--resource-group engram-rg \
--set-env-vars \
"TEMPORAL_HOST=staging-env-temporal-server.gentleriver-dd0de193.eastus2.azurecontainerapps.io:443" \
"TEMPORAL_NAMESPACE=default" \
"TEMPORAL_TASK_QUEUE=engram-agents"
Step 4: Configure API with Same TEMPORAL_HOST
az containerapp update \
--name staging-env-api \
--resource-group engram-rg \
--set-env-vars \
"TEMPORAL_HOST=staging-env-temporal-server.gentleriver-dd0de193.eastus2.azurecontainerapps.io:443" \
"TEMPORAL_NAMESPACE=default" \
"TEMPORAL_TASK_QUEUE=engram-agents"
Step 5: Update Python SDK to Use TLS
The Temporal Python SDK must enable TLS when connecting through Azure ingress.
# backend/workflows/worker.py and backend/workflows/client.py
async def create_temporal_client() -> Client:
"""Create a Temporal client with Azure Container Apps support"""
settings = get_settings()
host_parts = settings.temporal_host.split(":")
host = host_parts[0]
port = int(host_parts[1]) if len(host_parts) > 1 else 7233
logger.info(f"Connecting to Temporal at {host}:{port}")
# Azure Container Apps ingress uses TLS on port 443
use_tls = port == 443 or ".azurecontainerapps.io" in host
if use_tls:
logger.info("Using TLS for Azure Container Apps connection")
client = await Client.connect(
f"{host}:{port}",
namespace=settings.temporal_namespace,
tls=True, # REQUIRED for Azure Container Apps
)
else:
client = await Client.connect(
f"{host}:{port}",
namespace=settings.temporal_namespace,
)
logger.info(f"Connected to Temporal namespace: {settings.temporal_namespace}")
return client
Step 6: Add Required Secrets to Worker
The worker needs the Anthropic API key for story generation:
# Copy secret from API to worker
ANTHROPIC_KEY=$(az containerapp secret show \
--name staging-env-api \
--resource-group engram-rg \
--secret-name anthropic-api-key \
--query "value" --output tsv)
az containerapp secret set \
--name staging-env-worker \
--resource-group engram-rg \
--secrets "anthropic-api-key=$ANTHROPIC_KEY"
az containerapp update \
--name staging-env-worker \
--resource-group engram-rg \
--set-env-vars "ANTHROPIC_API_KEY=secretref:anthropic-api-key"
Step 7: Verify Connection
Local Python Test:
import asyncio
from temporalio.client import Client
async def test():
client = await Client.connect(
'staging-env-temporal-server.gentleriver-dd0de193.eastus2.azurecontainerapps.io:443',
namespace='default',
tls=True,
)
print('SUCCESS! Connected to Temporal!')
asyncio.run(test())
Check Worker Logs:
az containerapp logs show \
--name staging-env-worker \
--resource-group engram-rg \
--type console \
--tail 20 | grep -i "temporal\|connected\|started"
Expected output:
Connecting to Temporal at ...azurecontainerapps.io:443
Using TLS for Azure Container Apps connection
Connected to Temporal namespace: default
Starting worker on task queue: engram-agents
Worker started successfully
Step 8: Test Story Workflow
curl -X POST https://engram.work/api/v1/story/create \
-H "Content-Type: application/json" \
-d '{"topic": "Test Story", "include_diagram": false}'
Troubleshooting Guide
Error: Connection Timeout to :7233
Symptom: dial tcp 100.100.x.x:7233: i/o timeout
Cause: Connecting to wrong port (7233 instead of 443)
Fix: Update TEMPORAL_HOST to use port 443:
az containerapp update --name staging-env-worker --resource-group engram-rg \
--set-env-vars "TEMPORAL_HOST=<fqdn>:443"
Error: ConnectionReset / BrokenPipe
Symptom: transport error: hyper::Error(Io, BrokenPipe)
Cause: TLS not enabled in Python SDK
Fix: Enable tls=True in Client.connect()
Error: Namespace Not Found
Symptom: Namespace default is not found
Cause: Temporal Server has SKIP_DEFAULT_NAMESPACE_CREATION=true
Fix:
az containerapp update --name staging-env-temporal-server \
--resource-group engram-rg \
--set-env-vars "SKIP_DEFAULT_NAMESPACE_CREATION=false"
Error: Anthropic API Key Not Configured
Symptom: Anthropic API key not configured. Cannot invoke Claude.
Cause: Worker missing ANTHROPIC_API_KEY secret
Fix: Add the secret (see Step 6 above)
Configuration Summary
| Component | Setting | Value |
|---|---|---|
| Temporal Server Ingress | external | true |
| Temporal Server Transport | transport | http2 |
| Temporal Server Port | targetPort | 7233 |
| Worker/API TEMPORAL_HOST | Port | 443 (not 7233!) |
| Python SDK | tls | True |
| Namespace Creation | SKIP_DEFAULT_NAMESPACE_CREATION | false |
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ Azure Container Apps Environment │
│ │
│ ┌──────────────────┐ gRPC/TLS ┌──────────────────────────────────┐│
│ │ Engram API │─────────────────▶ │ ││
│ │ (Port 8080) │ port 443 │ Azure Load Balancer ││
│ └──────────────────┘ │ (Ingress) ││
│ │ ││
│ ┌──────────────────┐ gRPC/TLS │ TLS Termination + HTTP/2 ││
│ │ Engram Worker │─────────────────▶ │ ││
│ │ (Temporal SDK) │ port 443 └──────────────────────────────────┘│
│ └──────────────────┘ │ │
│ │ HTTP/2 │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Temporal Server │ │
│ │ (Port 7233) │ │
│ │ temporalio/auto- │ │
│ │ setup:latest │ │
│ └──────────────────────┘ │
│ │ │
└────────────────────────────────────────────────────│────────────────────────┘
│ PostgreSQL
▼
┌──────────────────────┐
│ Azure PostgreSQL │
│ (Temporal DB) │
└──────────────────────┘
Key Discoveries (December 2025)
-
Internal ingress does NOT work for gRPC - Azure Container Apps internal routing doesn’t properly handle gRPC traffic
-
Port 443, not 7233 - Azure ingress does TLS termination on port 443 and forwards to the container’s targetPort internally
-
TLS is mandatory - Even though Azure handles TLS termination, the Python SDK must still use TLS to communicate with the ingress
-
Namespace must be created - The
temporalio/auto-setupimage hasSKIP_DEFAULT_NAMESPACE_CREATION=trueby default
Related Documentation
Changelog
| Date | Change |
|---|---|
| 2025-12-24 | Complete step-by-step guide added |
| 2025-12-24 | Successfully tested story workflow execution |
| 2025-12-23 | Initial debugging and discovery of configuration requirements |