Plan: Azure Container Apps Jobs¶
Status: Planning
Roadmap item: aca-jobs
Scope: Migrate one-off/batch tasks to Container Apps Jobs
What are Container Apps Jobs?¶
A Container Apps Job is a container that runs to completion rather than staying alive as a server. It is triggered once, runs its workload, then exits. Azure bills only for the actual execution time (CPU+memory while running).
How it differs from a Container App¶
| Container App | Container App Job | |
|---|---|---|
| Lifecycle | Always running (or scaled to 0 idle) | Runs once, exits |
| Trigger | HTTP requests / KEDA scale rules | Schedule (cron), manual, or event-driven |
| Billing | Per vCPU-second while active | Per vCPU-second during execution only |
| Use case | Web servers, APIs | One-off tasks, batch, migrations |
Beneficial use cases in this project¶
| Task | Why a Job fits |
|---|---|
| Alembic migrations | Currently runs on every backend pod startup — wasteful and racy on cold-start. A Job runs once pre-deploy, fails loudly if migration fails, and the app deploy only proceeds after the Job succeeds. |
| CV generation (on-demand) | Long-running pagedjs-cli render (10–30s). Today it blocks an API request thread. A Job could be triggered by the API, run async, and store the PDF in Blob Storage. The API polls for completion. |
| Database seed / fixture load | Run once at environment setup, not on every restart. |
| Scheduled cost report | Already runs as a GitHub Actions workflow — could move to a Container Apps Job on a cron trigger, keeping it closer to the infrastructure. |
| Smoke test / health check | Run a Job post-deploy that pings all endpoints and fails the deployment if any are unhealthy. |
Recommended priority: Alembic migration Job¶
Running Alembic in a Job before each app deploy is the highest-value change:
- Eliminates startup race condition (current source of intermittent crashes)
- Makes migration failures explicit and deployment-blocking
- Follows standard Kubernetes init containers / Jobs pattern
Rough implementation¶
- Add
portfolio-migratorJob resource tocontainer_apps.bicep(same image as backend,CMD ["alembic", "upgrade", "head"]) - In
deploy-application.yml, after image push:
az containerapp job start --name portfolio-migrator-job --resource-group <rg>
# poll until succeeded
az containerapp job execution show ... --query "properties.status"
- Only proceed to Container App deploy if Job exits 0
Bicep sketch¶
bicep
resource migrator_job 'Microsoft.App/jobs@2023-05-01' = {
name: '${org}-${env_short}-${project}-${location_short}-job-migrate'
location: location
properties: {
environmentId: container_app_environment.id
configuration: {
triggerType: 'Manual'
replicaTimeout: 300
replicaRetryLimit: 1
manualTriggerConfig: { parallelism: 1, replicaCompletionCount: 1 }
registries: [{ server: acr_server, identity: backend_identity_id }]
}
template: {
containers: [{
name: 'migrator'
image: '${acr_server}/portfolio-backend:${image_tag}'
command: ['alembic', 'upgrade', 'head']
resources: { cpu: json('0.25'), memory: '0.5Gi' }
env: [
{ name: 'DATABASE_URL', secretRef: 'database-url' }
{ name: 'MANAGED_IDENTITY_CLIENT_ID', value: backend_client_id }
]
}]
}
}
}
Success Criteria¶
- [ ] Migration Job completes before new revision receives traffic
- [ ] Failed migration blocks the deploy (workflow fails)
- [ ] Job logs are accessible in Azure portal and GitHub Actions output
- [ ] No duplicate migrations on cold-start (Alembic idempotent by design)