name: flet-aca-deploy description: Deploy a Flet web app to Azure Container Apps — covers all pitfalls: container startup, WebSocket transport, GHCR auth, ACA provisioning, revision forcing, and health diagnosis author: POWR-DATA version: 2.0.0 license: MIT
Flet ACA Deploy
Purpose
Deploy a Flet web app to Azure Container Apps (ACA) and stand up its CI/CD deploy job — covering the container-startup, WebSocket-transport, GHCR-auth, provisioning, and revision-forcing pitfalls that otherwise leave the container running but the Flet UI unable to connect. Use alongside flet-multiplatform-build, which builds and publishes the Docker image.
When to use
After the Flet web Docker image is building and pushing to GHCR successfully (see flet-multiplatform-build). Apply when:
- Deploying the web app to Azure Container Apps for the first time
- An existing ACA deployment is broken — WebSocket timeout,
ImagePullBackOff, or container crash on startup - Setting up the CI/CD deploy job
- Debugging a container that starts but whose Flet UI never connects
Inputs expected
- Working Dockerfile with
python main.pyas CMD andFLET_HOST=0.0.0.0 - GitHub repository with GHCR image publishing configured (
build-web.ymlfromflet-multiplatform-build) - Azure subscription (free tier is sufficient for initial deployment)
- Preferred Azure region (e.g.
australiaeast) - App name — used for resource group, container app, and service principal naming
Guiding principles
- Run
python main.py, neverflet run --web, in the container. Even in web mode thefletCLI importsflet_desktop, which needs native GUI libs absent frompython:3.12-slim— the container crashes withModuleNotFoundError: No module named 'flet_desktop'. Configure web mode in code withft.run()(not the deprecatedft.app()) and aFLET_HOSTenv var so the bind address differs between local (localhost) and container (0.0.0.0). flet-webmust be pinned inrequirements.txt. It auto-installs at runtime but not at build time, so DockerfileRUN python -c "import flet_web …"asset steps fail withModuleNotFoundErrorwithout it. Pin the same version asflet.- Overwrite branding assets inside the
flet_webpackage, not the project root. Flet does not serve/favicon.pngfrom the project root; the real defaults live inflet_web/web/(favicon) andflet_web/web/icons/(PWA icons,loading-animation.png). Patch them in one DockerfileRUNstep, and patch thescale()CSS inindex.html(it appears twice — portrait and landscape). - Force ACA ingress to HTTP/1.1. The default
transport: Automay negotiate HTTP/2, which has no WebSocket upgrade — the Flutter client loads but the WebSocket never connects ("stream timeout"). Set--transport httpimmediately after container creation. - Make the GHCR package public — every private-pull approach fails. Classic PATs 404 on private manifests,
GITHUB_TOKENexpires before ACA's async pull (ImagePullBackOff), and stored registry creds persist even after going public. The image is a compiled artefact; the source repo stays private. Remove any previously stored ACA registry credentials. - Set ACA min replicas to 1. With 0, the first browser request arrives before the container is ready and the WebSocket cold-starts into a timeout.
- Bootstrap, then deploy. Create the app with the helloworld placeholder image at
--target-port 8550, then let CI deploy the real image — the deploy step updates the image, not the port. - Force a new revision on every deploy.
az containerapp update --image ...:latestmay reuse a revision if the tag is unchanged; add--revision-suffix r${{ github.run_number }}. Useaz containerapp updatedirectly rather thancontainer-apps-deploy-action@v2, which does not support revision suffixes. - Script all provisioning in
infra/setup-azure.sh. RegisterMicrosoft.AppandMicrosoft.OperationalInsightsbefore creating the environment; build the SP credentials JSON manually (--sdk-authis deprecated in CLI 2.37+); prefixazcalls passing/subscriptions/...paths in Git bash withMSYS_NO_PATHCONV=1.
Process
Commands and templates for every step are in reference.md.
- Confirm container startup config —
main.pyusesft.run()with theFLET_HOSTguard andweb_renderer=CANVAS_KIT; Dockerfile setsENV FLET_HOST=0.0.0.0andCMD ["python","main.py"]. See main.py web mode + Dockerfile. - Verify branding assets —
flet-webpinned inrequirements.txt; DockerfileRUNstep overwrites favicon, PWA icons, andloading-animation.png, and patches the splash CSS scale. See flet_web asset patch. - Provision Azure — register providers, create resource group, environment (
--logs-destination none), and the container app from the placeholder image at port 8550 with min-replicas 1. See ACA provisioning / infra/setup-azure.sh. - Force ingress to HTTP/1.1 —
az containerapp ingress update --transport http. See ACA ingress. - Make the GHCR package public and remove any stored ACA registry credentials. See GHCR auth.
- Create the service principal and store
AZURE_CREDENTIALSas a GitHub secret. See infra/setup-azure.sh. - Add the deploy job —
az containerapp updatewith a lowercase image ref and--revision-suffix r${{ github.run_number }}. See Deploy job. - Diagnose failures if needed — inspect
runningStateDetailsand container logs to tellImagePullBackOfffrom a startup crash. See Diagnosing revision failures.
Output format
Present the deployment as:
- Provisioning summary — resource group, environment, container app, region, replica config
- Files / config produced —
infra/setup-azure.sh, the deploy job YAML, anymain.py/Dockerfile corrections (templates fromreference.md) - Connectivity checks — ingress transport
Http, GHCR package public, min-replicas 1 - Deploy result — revision created with a unique suffix, app reachable, WebSocket connects (UI loads)
Quality checklist
- Dockerfile CMD is
python main.py, notflet run --web -
main.pyusesft.run()withFLET_HOSTandweb_renderer=ft.WebRenderer.CANVAS_KIT - Dockerfile sets
ENV FLET_HOST=0.0.0.0 -
flet-web==<version>inrequirements.txt(matchesflet) - Dockerfile RUN step overwrites favicon, PWA icons,
icons/loading-animation.png, and patches theindex.htmlscale - ACA ingress transport set to
Http(scripted ininfra/setup-azure.sh) - ACA target port 8550, min replicas 1
- GHCR package public; no registry credentials stored in ACA
- Deploy job uses
az containerapp updatewith--revision-suffix r${{ github.run_number }}and a lowercase image ref -
infra/setup-azure.shprovisions everything from scratch;AZURE_CREDENTIALSsecret set
Avoid
flet run --webas Dockerfile CMD — crashes in slim containers; usepython main.pyft.app()(deprecated) orweb_renderer="html"(removed, raisesValueError) — useft.run()+CANVAS_KIT- Hardcoding
host="0.0.0.0"inft.run()— breaks local dev; use theFLET_HOSTenv var - Expecting
assets/favicon.pngto appear as the tab icon — overwriteflet_web/web/favicon.pngin the Dockerfile - Omitting
flet-webfromrequirements.txt— build-timeimport flet_websteps fail - Targeting
flet_web/web/loading-animation.png— it is inweb/icons/ - ACA ingress transport
Auto— may break WebSocket; forceHttp - Pulling a private GHCR package from ACA — PATs and
GITHUB_TOKENboth fail; make it public - Leaving stale registry credentials in ACA after going public — remove them
az containerapp update --image ...:latestwithout--revision-suffix— may not create a new revisioncontainer-apps-deploy-action@v2for forced revisions — no--revision-suffixsupport- Min replicas 0 — WebSocket cold-start breaks the UI
--sdk-authwithaz ad sp create-for-rbac— deprecated in CLI 2.37+- Unix-style paths in
azfrom Git bash withoutMSYS_NO_PATHCONV=1
Example usage
"The Flet web Docker image is building and pushing to GHCR. Set up Azure Container Apps and the GitHub Actions deploy job — provision everything from scratch with the az CLI, force HTTP/1.1 ingress for the WebSocket, and store AZURE_CREDENTIALS automatically."
Source: This skill is sourced from the Matrix Skills library. Learn more at the AI Agent Skills Library.