Skip to content

Add an Azure Container Apps sample (guestbook on ACR + Blob Storage) - #117

Merged
lazarkanelov merged 5 commits into
mainfrom
add-container-apps-sample
Aug 13, 2026
Merged

Add an Azure Container Apps sample (guestbook on ACR + Blob Storage)#117
lazarkanelov merged 5 commits into
mainfrom
add-container-apps-sample

Conversation

@lazarkanelov

Copy link
Copy Markdown
Contributor

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 the aci-blob-storage sample's structure (CLI scripts + Bicep + Terraform variants, numbered deploy steps, validate.sh check 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)
  • App create from a private registry with explicit --registry-* credentials
  • Secrets (--secrets + secretref: env var, secret list/show in validate)
  • External HTTP ingress + live requests through the returned FQDN
  • Multiple-revisions mode: validate.sh rolls out a v2 revision with az containerapp update, asserts both revisions exist, that v2 serves traffic (the app reports its revision in /health and the UI), and that entries survive the switch
  • Min/max replicas, an HTTP scale rule, replica list

The Bicep template pins Microsoft.App@2025-07-01 (the api-version the az CLI uses); the Terraform variant uses azurerm_container_app_environment / azurerm_container_app on azurerm 4.81.0.

Wiring

  • run-samples.sh: registered in SAMPLES, TERRAFORM_SAMPLES, BICEP_SAMPLES, and ARM64_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 --list
  • Top-level README.md: outline row + architecture-support table row

Verification

Run end-to-end against a current emulator dev build (LS_AZURE_CONTAINER_APPS_RUNTIME on by default):

  • scripts/deploy.sh + scripts/validate.sh: 28/28 checks pass, including the HTTP round trip through http://<fqdn>:4566 and the v2 rollout
  • Bicep and Terraform deploys: app healthy with storage_configured: true, entry round trip verified
  • cleanup.sh removes everything including the environment's k3d cluster
  • Works with and without ENVIRONMENT=LocalStack (the emulator returns an http:// blob endpoint natively)

Note: validate.sh re-states AZURE_STORAGE_CONNECTION_STRING=secretref:storage-conn in the az containerapp update call. Without it, the CLI's read-modify-write of the env list round-trips the value: "" the emulator echoes alongside secretRef, and the new revision's pod loses the secret env (control plane keeps the secretRef, so only the data plane breaks). Harmless on real Azure, and the check keeps passing once the emulator omits value for secret-backed env vars.

CI dependency: the sample needs Container Apps support in the released localstack/localstack-azure:latest image; it was verified against a local dev build that includes it.

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.
@lazarkanelov
lazarkanelov requested a review from a team as a code owner August 12, 2026 12:10
@paolosalvatori
paolosalvatori requested a lite review from Copilot August 12, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh and adds it to the top-level README.md tables (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.

Comment thread samples/container-apps-blob-storage/python/src/blob_storage_client.py Outdated
Comment thread samples/container-apps-blob-storage/python/src/blob_storage_client.py Outdated
Comment thread samples/container-apps-blob-storage/python/src/app.py Outdated

@paolosalvatori paolosalvatori left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 🎉

Comment thread samples/container-apps-blob-storage/python/bicep/main.bicep
Comment thread samples/container-apps-blob-storage/python/scripts/validate.sh Outdated
Comment thread samples/container-apps-blob-storage/python/bicep/main.bicep
Comment thread samples/container-apps-blob-storage/python/terraform/main.tf
Comment thread README.md
…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.
@lazarkanelov

Copy link
Copy Markdown
Contributor Author

CI note: the red runs on this PR were not the sample — three separate infra events, now handled:

  1. Two Start LocalStack timeouts on slow runners (localstack wait -t 300 expired mid-bootstrap, different shard each time) — fixed in 8f07cd1 by raising the readiness wait to 600s, same rationale as the earlier 120s→300s bump.
  2. One Docker Hub auth timeout during the image pull (network flake, cleared on rerun).
  3. web-app-mysql-flexible-server failing deterministically since ~15:00 UTC: GitHub's runner-image rollout (ubuntu22/20260810.260) bumped the preinstalled az CLI 2.88.0 → 2.89.0, and the emulator answers the new az mysql flexible-server firewall-rule create payload with a 500 (AttributeError: 'NoneType' object has no attribute 'start_ip_address' in mysql_flexible/apis/firewall_rules.py — the body parses to a None properties). Same emulator image digest passed with az 2.88.0 minutes earlier, so the CLI version is the only variable. 2f47f5f pins az to 2.88.0 in the workflow (with the removal trigger in the step comment) so all shards stay green until the emulator handles the 2.89 shape — this would otherwise break every PR in the repo, not just this one. The postgresql flexible-server handler is worth checking for the same None-properties pattern.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants