Add an Azure Container Apps sample (guestbook on ACR + Blob Storage) - #117
Conversation
A Flask guestbook deployed as a container app: the deploy script builds the image into ACR, creates a managed environment, and creates an app with external HTTP ingress, an ACA secret injected via a secretref env var, multiple-revisions mode, 1-3 replicas and an HTTP scale rule. validate.sh drives the live app through its ingress FQDN (health check, guestbook round trip against Blob Storage) and rolls out a second revision, asserting entries survive the switch. Bicep (Microsoft.App 2025-07-01) and Terraform (azurerm_container_app) variants mirror the aci-blob-storage sample's deploy.sh structure. Registered in run-samples.sh (scripts + terraform + bicep, arm64-native since the sample builds its own image and the k3d runtime images are multi-arch) and in the top-level README outline and architecture table.
There was a problem hiding this comment.
Pull request overview
Adds a new end-to-end sample under samples/container-apps-blob-storage/python demonstrating a Flask “guestbook” deployed to Azure Container Apps, pulling from ACR, and persisting entries in Blob Storage (with the storage connection string injected via a Container Apps secret). The sample is wired into the repo’s sample runner and top-level documentation.
Changes:
- Introduces a new Container Apps + Blob Storage Python sample app (Flask UI + Blob-backed persistence) with deploy/validate/cleanup scripts.
- Adds IaC variants for the same sample (Bicep and Terraform), including ACR image build/push steps and Container Apps configuration (secrets, ingress, revisions, scaling).
- Registers the sample in
run-samples.shand adds it to the top-levelREADME.mdtables (including arm64 support notes).
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/container-apps-blob-storage/python/terraform/variables.tf | Terraform input variables for the sample. |
| samples/container-apps-blob-storage/python/terraform/README.md | Terraform deployment instructions for the sample. |
| samples/container-apps-blob-storage/python/terraform/providers.tf | Terraform/provider requirements and LocalStack azurerm config. |
| samples/container-apps-blob-storage/python/terraform/outputs.tf | Terraform outputs (RG, storage, ACR, ACA FQDN, revision). |
| samples/container-apps-blob-storage/python/terraform/main.tf | Terraform resources for Storage + ACA environment/app + secrets/ingress/scale. |
| samples/container-apps-blob-storage/python/terraform/deploy.sh | Terraform flow script (pre-create ACR, build/push image, apply). |
| samples/container-apps-blob-storage/python/src/templates/index.html | Guestbook UI template showing revision and entries. |
| samples/container-apps-blob-storage/python/src/requirements.txt | Python dependencies (Flask + azure-storage-blob). |
| samples/container-apps-blob-storage/python/src/Dockerfile | Container image build for the guestbook app. |
| samples/container-apps-blob-storage/python/src/blob_storage_client.py | Blob Storage persistence client (single JSON blob). |
| samples/container-apps-blob-storage/python/src/app.py | Flask app routes (/ , /delete, /health) and client initialization. |
| samples/container-apps-blob-storage/python/scripts/validate.sh | Validation harness exercising ACA secrets, ingress, replicas, revisions, and HTTP checks. |
| samples/container-apps-blob-storage/python/scripts/README.md | Azure CLI deployment/validation docs for the scripts variant. |
| samples/container-apps-blob-storage/python/scripts/deploy.sh | Azure CLI deployment script for Storage + ACR + ACA (secretref env). |
| samples/container-apps-blob-storage/python/scripts/cleanup.sh | Cleanup script removing app/env/ACR/storage/RG. |
| samples/container-apps-blob-storage/python/README.md | Sample-level README (architecture, quick start, variants). |
| samples/container-apps-blob-storage/python/bicep/README.md | Bicep deployment instructions for the sample. |
| samples/container-apps-blob-storage/python/bicep/main.bicepparam | Bicep parameter file for prefix/suffix/image inputs. |
| samples/container-apps-blob-storage/python/bicep/main.bicep | Bicep template for Storage + ACR + ACA (secrets/ingress/scale). |
| samples/container-apps-blob-storage/python/bicep/deploy.sh | Bicep flow script (build/push image then group deployment). |
| run-samples.sh | Registers the new sample in CLI/Bicep/Terraform matrices and arm64 list. |
| README.md | Adds the new sample to the repository overview + architecture support table. |
Suppressed comments (2)
samples/container-apps-blob-storage/python/src/app.py:56
- This exception handler catches ConnectionError/ValueError/KeyError, but Azure SDK read failures are usually not ConnectionError. If BlobGuestbookClient._read_blob is updated to raise on read errors (to prevent silent data loss), this handler should catch those exceptions too.
except (ConnectionError, ValueError, KeyError) as e:
logger.error("Error reading entries: %s", e)
samples/container-apps-blob-storage/python/src/app.py:76
- Same as above: catching only the built-in ConnectionError is unlikely to intercept Azure SDK/network failures. Catch a broader exception type and log the stack trace so failed deletes don't crash the request handler.
except (ConnectionError, ValueError) as e:
logger.error("Error deleting entry: %s", e)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
paolosalvatori
left a comment
There was a problem hiding this comment.
Thanks for quickly building a sample with ACA. Please, see my comments, suggestions, requests for changes. In addition, look at GitHub Copilot comments and suggestions. You can them merge updates, so we officially have our first ACA sample 🎉
…ling - blob_storage_client: a failed read no longer masquerades as an empty guestbook (only ResourceNotFoundError maps to []), so a read-modify-write can no longer truncate entries.json after a read error. - blob_storage_client: writes are ETag-conditional (If-Match, or overwrite=False for the first write) with jittered-backoff retries, so concurrent replicas cannot lose each other's updates. Verified with 10 simultaneous POSTs: 10/10 entries survive (5 attempts without backoff lost 2 to retry exhaustion under the thundering herd). - app: handlers catch AzureError/RuntimeError instead of the builtin ConnectionError the Azure SDK never raises, and log full stack traces. - validate.sh/deploy.sh: match the LocalStack FQDN as a suffix. - README: link the separate AKS samples repository (review suggestion).
The shared "Start LocalStack" step timed out twice in a row on this PR, each time on a different, unrelated sample shard (function-app-storage-http terraform, then servicebus scripts): `localstack wait -t 300` expired while the emulator was still bootstrapping on a slow runner, with no error in the emulator log. The wait returns as soon as the emulator is healthy, so the larger budget only affects the unhappy path - same rationale as the earlier 120s -> 300s bump documented on the step.
GitHub's runner-image rollout (ubuntu22/20260720.234 -> 20260810.260) bumped the preinstalled az from 2.88.0 to 2.89.0 mid-PR. Since then `az mysql flexible-server firewall-rule create` deterministically fails against the emulator: the request body parses to a None `properties` and flexible_servers__firewall_rules__create_or_update crashes with AttributeError -> 500 (same image digest passed with az 2.88.0 minutes earlier, so the CLI version is the only variable). Pinning the CLI keeps every sample shard green until the emulator handles the new shape; the step carries the removal trigger.
|
CI note: the red runs on this PR were not the sample — three separate infra events, now handled:
|
The Functions-image prebuild inside the emulator lists Azure/azure-functions-core-tools releases via api.github.com; anonymous requests share the runner egress IP's 60 req/h budget, which is routinely exhausted on GitHub-hosted runners (observed: 5/5 build attempts rate- limited on the arm64 shard). localstack-core's GitHubReleaseInstaller already honors GITHUB_API_TOKEN, so forward the job's ephemeral contents:read token through DOCKER_FLAGS.
What
A new sample,
samples/container-apps-blob-storage/python: a Flask guestbook running on Azure Container Apps, pulling its image from ACR and storing entries as a JSON blob in Blob Storage. It mirrors theaci-blob-storagesample's structure (CLI scripts + Bicep + Terraform variants, numbered deploy steps,validate.shcheck harness,cleanup.sh).Instead of Key Vault, the storage connection string is stored as a Container Apps secret and injected through a
secretref:environment variable — the idiomatic ACA pattern, and what differentiates this sample from the ACI one.Container Apps features exercised
az containerapp env create(managed environment)--registry-*credentials--secrets+secretref:env var,secret list/showin validate)validate.shrolls out av2revision withaz containerapp update, asserts both revisions exist, thatv2serves traffic (the app reports its revision in/healthand the UI), and that entries survive the switchreplica listThe Bicep template pins
Microsoft.App@2025-07-01(the api-version the az CLI uses); the Terraform variant usesazurerm_container_app_environment/azurerm_container_appon azurerm 4.81.0.Wiring
run-samples.sh: registered inSAMPLES,TERRAFORM_SAMPLES,BICEP_SAMPLES, andARM64_SAMPLE_DIRS(the sample builds its own image from a multi-arch base, and the k3d runtime images are multi-arch) — the CI matrix picks it up via--listREADME.md: outline row + architecture-support table rowVerification
Run end-to-end against a current emulator dev build (
LS_AZURE_CONTAINER_APPS_RUNTIMEon by default):scripts/deploy.sh+scripts/validate.sh: 28/28 checks pass, including the HTTP round trip throughhttp://<fqdn>:4566and the v2 rolloutstorage_configured: true, entry round trip verifiedcleanup.shremoves everything including the environment's k3d clusterENVIRONMENT=LocalStack(the emulator returns anhttp://blob endpoint natively)