| name | fly-secure-webapp-deploy |
| description | Secure Fly.io web application deployment workflow. Use when configuring, reviewing, or debugging Fly apps with private internal services, public frontends, custom domains, Cloudflare Access, OIDC origin protection, Fly secrets, volumes, Docker images, or smoke checks. |
Fly Secure Webapp Deploy
Workflow
Start by mapping the network boundary:
- Keep backend/API/agent services private unless they explicitly need public ingress.
- For private services, omit
http_service/public services from fly.toml and verify fly ips list --app <app> has no public IPs.
- Use Fly private DNS for app-to-app calls:
http://<app>.internal:<port>.
- Still require application-layer auth for private APIs that expose privileged actions.
Treat the public app as a separately hardened edge:
- Set
force_https = true in http_service.
- Remember Fly's default
https://<app>.fly.dev hostname remains reachable when public ingress exists.
- If only a custom domain should work, add an in-container host gate or reverse proxy that forwards only the configured public host and returns
404 for everything else.
- Do not assume Cloudflare Access protects the direct Fly origin. A caller can reach Fly ingress directly with the custom Host/SNI if they know the origin IP.
Cloudflare And Origin Auth
Use two layers for sensitive web UIs:
- Cloudflare Access on the custom hostname as the front-door policy.
- App-level SSO/OIDC using Cloudflare Access as the identity provider, so direct-origin requests still hit an identity gate.
For Open WebUI-style apps:
- Prefer OIDC-only production login: disable local password/login form after bootstrap.
- Keep a temporary bootstrap target that enables both OIDC and local password login only long enough to migrate/promote the OIDC admin.
- Keep user default role restrictive, such as
pending, when OAuth signup is enabled.
- Store OIDC client secrets as Fly secrets. Client IDs, provider URLs, redirect URIs, and admin emails can be normal deployment env unless local policy says otherwise.
Secrets And Config
- Keep
.env ignored; commit .env.example with placeholders and safe defaults.
- Import secrets with
fly secrets import or fly secrets set; avoid putting secrets in fly.toml, Dockerfiles, or docs.
- Preflight required secrets before importing so partial secret syncs do not happen after a missing value is detected.
- Use deployment env (
fly deploy --env) for non-secret runtime defaults that should be reproducible from the repo.
- Pin Docker base images by digest for production-like deployments; update digests intentionally.
Validation
Run these checks before calling the deployment secure:
fly config validate -c <fly.toml>
fly ips list --app <private-app>
fly secrets list --app <app>
curl -sSI https://<custom-domain>/
curl -sSI https://<public-app>.fly.dev/
curl -sSI --resolve <custom-domain>:443:<fly-ingress-ip> https://<custom-domain>/
Expected results:
- Private services have no public IPs and do not respond on
*.fly.dev.
- The raw public Fly hostname is blocked or intentionally documented.
- The custom domain routes through Cloudflare Access when reached normally.
- Direct-origin requests do not expose authenticated app content without app-level auth.