Overview
Apache Airflow is an open-source platform for programmatically authoring, scheduling, and monitoring workflows. This template deploys a full Airflow 3.x stack into an existing GVC using the CeleryExecutor, with Redis as the task broker, PostgreSQL as the metadata database, Celery workers at a fixed replica count or scaled by KEDA, and optional git-sync DAG delivery. The admin login and both signing keys are not template values. They come from a dictionary secret you create before installing, so they never pass through Helm and never land in the release. The web UI ships closed to the internet, because it can trigger arbitrary code and decrypt every credential stored in an Airflow Connection.What Gets Created
- Airflow Webserver — (
RELEASE_NAME-airflow-webserver): a stateful, single-replica workload running the API server, scheduler, dag-processor and triggerer in one container, serving the UI and REST API on port8080. It is the only tier with external inbound access, and the only one that runs the boot-time GVC checks. - Celery Workers — (
RELEASE_NAME-airflow-celery-worker): a stateful workload executing DAG tasks, at a fixedairflow.celeryWorker.replicasor scaled by KEDA on the Redis queue length. - Redis — (
RELEASE_NAME-airflow-redis): the Celery broker, on its own persistent volume set. - PostgreSQL — (
RELEASE_NAME-airflow-postgres): the Airflow metadata database, on its own persistent volume set. - Airflow Volume Set — (
RELEASE_NAME-airflow-vs): a shared-filesystem volume mounted as the Airflow home by the webserver and every worker, so they all see the same DAGs and logs. - Config Secret — (
RELEASE_NAME-airflow-config): a dictionary secret holding the metadata database username and password. It carries no key material. - Webserver Startup Secret — (
RELEASE_NAME-airflow-webserver-startup): an opaque secret holding the webserver’s startup script, which runs the topology checks before Airflow’s own boot sequence. - Identity & Two Policies — An identity bound to the workloads, with
revealon exactly the secrets they mount (the database config secret, the startup script, your auth secret, and your git token secret when git-sync uses one), plusviewon the one GVC you install into so the webserver can check its own topology at boot. - git-sync Sidecar (optional) — Polls a Git repository and syncs DAGs onto the shared volume.
location you configure, so cpln workload exec, cpln logs and uninstalling all work against that GVC, and uninstalling can never delete it. Nothing Airflow-related runs in the GVC’s other locations.Migrating from 1.x
Template versions through 1.5.0 created their own GVC — KEDA is a GVC-level setting, and the chart wanted to own it. Version 2.0.0 does not declare a GVC at all, and Helm deletes what a chart stops declaring. Migrate to a new release instead:Choose the GVC for the new release
location. See Prerequisites. If you plan to use KEDA, enable it on that GVC now.Install 2.0.0 as a NEW release, pointing at the SAME auth secret
airflow.auth.secretName at the same auth secret the 1.x release used: the fernetKey must be identical, or every Connection and Variable you restore becomes unreadable.Move the metadata database
postgres.config.username and postgres.config.database values each release actually uses:Move the DAGs
gitSync.enabled: true there is nothing to do — the sidecar re-clones onto the new volume. Otherwise copy them off the old shared volume:Verify before you uninstall anything
Earlier Breaking Changes
Template version 1.4 and earlier carried the JWT signing secret, the Fernet key, the admin password and the git token as plain Helm values, and shipped a workingCHANGE_ME default for the first three. All four were removed in 1.5.0 with no compatibility fallback; a values file that still sets one fails at render with a message naming its replacement.
- The metadata database password cannot be changed in place either.
POSTGRES_PASSWORDis read only when the data directory is initialized, so pointingpostgres.config.passwordat a new value on an existing volume leaves the database on the old one and Airflow can no longer connect. Set it at first install. - The web UI now ships closed. If you were relying on the old
0.0.0.0/0default, put your own CIDRs infirewallConfig.inboundAllowCIDR. - Two JWT knobs were removed because they did nothing.
jwtExpirationDeltaandjwtRefreshThresholdrenderedAIRFLOW__API_AUTH__JWT_EXPIRATION_DELTAandAIRFLOW__API_AUTH__JWT_REFRESH_THRESHOLD, neither of which is an option in Airflow 3.x — tokens were issued with Airflow’s own 86400-second lifetime regardless of what those values said. The live knob isairflow.auth.jwtExpirationTime; there is no refresh-threshold equivalent.
Prerequisites
A GVC Containing the Location You Configure
A GVC must already exist, andlocation must be one of its locations. Extra locations in the GVC are fine — nothing Airflow-related runs in them. Check what a GVC has before installing:
spec.staticPlacement.locationLinks. To add a missing one:
One Secret Must Exist Before You Install
Its values never pass through Helm, so they never land in the release. Secrets are org-level, so no GVC flag is involved.Create the auth secret
jwtSecret, fernetKey and adminPassword:airflow.auth.secretName to the name you used.Read the secret back later
-o yaml is required — plain cpln secret reveal prints only a summary table, not the values:Optional: create a git token secret for a private DAG repository
gitSync.enabled is true and the repository is private. Create an opaque secret with encoding plain whose payload is a personal access token, and set gitSync.auth.secretName to its name — the sidecar receives the payload as its Git password.gitSync.auth.secretName empty for a public repository or when git-sync is off. Empty turns the credential off entirely, including its policy grant, and setting it while git-sync is disabled is rejected at render.KEDA on the GVC
Only needed when you setkeda.enabled: true. KEDA is a GVC-level setting, so this template cannot turn it on for you. Enable it on the GVC first, then install with keda.enabled: true. Edit the file the first command writes rather than composing one:
spec.keda.enabled: true in that file, apply it, and confirm:
Installation
To install, follow the instructions for your preferred method:UI
CLI
Terraform
Pulumi
Configuration
The defaultvalues.yaml for this template:
Location
location— the single location name every workload in the release is pinned to. It must already be a location of the GVC you install into.
shared volume set, and a shared volume set provisions one volume per location — so two locations would mean two Airflow homes that cannot see each other’s DAGs or task logs. The metadata database and the Celery broker are each a single volume bound to a single replica on top of that. There is no multi-location shape to configure.
This workload location is deactivated because maxScale is set to 0. That is the mechanism that keeps a shared GVC safe — it is what a healthy install looks like, not a fault. It is also why a location the GVC lacks starts nothing anywhere; see Prerequisites.0 scale by default with a single complete entry for location.PostgreSQL Metadata Database
postgres.image— The PostgreSQL image.postgres.resources— Reservation and limit for the database container (minCpu/maxCpu/minMemory/maxMemory).postgres.config.username/postgres.config.password/postgres.config.database— The metadata database credentials. This is bundled plumbing: it serves Airflow only, is unreachable from outside the GVC, and no human ever types it, so it stays a value. Set the password at first install — the shippedchange-me-airflow-dbis a published placeholder, and PostgreSQL only reads it when the data directory is initialized.postgres.volumeset.capacity— Database volume size in GiB (minimum 10).
Redis Broker
redis.image— The Redis image.redis.resources— CPU and memory limit for the broker (this block exposes only a limit).redis.volumeset.capacity— Broker volume size in GiB (minimum 10).
Airflow
airflow.webserver.image/airflow.celeryWorker.image— Images for the webserver and the Celery workers.airflow.webserver.resources/airflow.celeryWorker.resources— CPU and memory limits for each tier.airflow.celeryWorker.replicas— The fixed number of Celery workers. Ignored whenkeda.enabledistrue, which hands the replica count to KEDA instead.airflow.webPort— The port serving the UI and the REST API.airflow.auth.secretName— Name of the prerequisite dictionary secret holdingjwtSecret,fernetKeyandadminPassword. It must exist before you install.airflow.auth.jwtExpirationTime— API access-token lifetime in seconds, applied asAIRFLOW__API_AUTH__JWT_EXPIRATION_TIME. The shipped86400is Airflow’s own default.airflow.admin.username— The admin login paired with theadminPasswordkey of your auth secret.airflow.scheduler.dagDirListInterval— How often the DAG folder is rescanned, in seconds.airflow.scheduler.minFileProcessInterval— Minimum interval between processing the same DAG file, in seconds.airflow.celery.workerConcurrency— Tasks each Celery worker runs concurrently.
airflow.celeryWorker.replicas (or KEDA) is the only horizontal scaling knob here — you scale task execution, not scheduling.SimpleAuthManager: the container writes a password file from the secret on every start, so the login always matches the current secret contents.
SimpleAuthManager is Airflow 3.x’s default auth manager and has no SSO or LDAP support. Front the UI with an OAuth/OIDC auth manager if you need one.Storage
volumeset.airflow.capacity— Size in GiB of the shared Airflow home holding DAGs and logs (minimum 10).
postgres.volumeset.capacity and redis.volumeset.capacity.
Access
firewallConfig.inboundAllowCIDR— CIDR ranges allowed to reach the webserver from the internet. The shipped[]means closed: the canonical endpoint returns403and the UI is reachable only over a port-forward. Add your own ranges (for example203.0.113.0/24) to expose it.
503 before it settles on 200 or 403, so re-poll rather than trusting the first response.internalAccess controls which workloads inside the org may reach Airflow’s own workloads:
keda.enabled is true it also allows the KEDA operator onto the Redis tier, whose queue length KEDA polls. So you only need to list your own clients.DAG Delivery With git-sync
git-sync runs as a sidecar on the webserver and worker workloads, pulling a Git repository onto the shared Airflow volume and pointingAIRFLOW__CORE__DAGS_FOLDER at the synced checkout on both tiers.
/opt/airflow/dags on the shared volume — the dag-processor picks up a new file within seconds at the shipped dagDirListInterval: 10.
KEDA Autoscaling
KEDA scales the Celery workers on the Redis queue length, including down to zero between runs. It is off by default as of 2.0.0. With it off, workers run at the fixedairflow.celeryWorker.replicas.
Wide Fan Outs Can Silently Lose Tasks
A DAG that dispatches many tasks at the same instant loses roughly a third of them, and nothing surfaces in the UI as an error — the tasks are simply marked failed, with no hostname, no start time and an empty log. A measured 24-task burst finished 7 failed and 9 succeeded, with the failures never starting at all. The cause is upstream, not anything this template configures: the Celery executor’s Redis transport is imported lazily and concurrently by several task-sending threads, and one of them observes a half-initialized module —module 'redis' has no attribute 'client' from kombu/transport/redis.py. The scheduler then marks those queued tasks failed.
Until it is fixed upstream, stagger task submission rather than fanning out wide simultaneously, and count completed task instances rather than trusting the UI to flag a problem. A single task, and tasks arriving at a normal rate, are unaffected.
Connecting to Airflow
403.
First Run Sequence
Install with the UI closed
firewallConfig.inboundAllowCIDR: [].Tunnel to the UI and sign in
http://localhost:8080 and log in with airflow.admin.username and the adminPassword from your auth secret.Expose it only if you need to
firewallConfig.inboundAllowCIDR, then allow up to a few minutes for the firewall to propagate before testing.API Access
Airflow 3.x issues JWT access tokens signed with yourjwtSecret. Request one with the same admin credentials:
Important Notes
- The auth secret must exist before you install. Without it the deployment wedges with no log output at all; see Prerequisites for the one diagnostic that names it, and expect recovery to take up to about ten and a half minutes after you create it.
- The Fernet key can never be rotated — every stored Connection and Variable is encrypted under it. Generate it once, keep it, and back it up somewhere you will still have it later.
- A wide simultaneous fan-out silently loses roughly a third of its tasks — see Wide Fan Outs Can Silently Lose Tasks.
- Never upgrade a 1.x release onto 2.0.0 in place — it deletes the GVC the 1.x chart created and everything in it, including your metadata database and DAGs. Migrate to a new release: Migrating from 1.x.
locationmust be a location of your GVC. If it is not, the install still succeeds and then nothing starts anywhere, with no container to produce a log line. Checkcpln workload get-deploymentsforThis workload location is deactivated because maxScale is set to 0.on every location.- Airflow runs in a single location by construction — the shared Airflow home provisions one volume per location, so a second location would be a second, disconnected set of DAGs and logs.
- Enable KEDA on the GVC before setting
keda.enabled: true. Getting the order wrong is accepted with no error and leaves the workers stuck atkeda.minScaleforever — zero workers, and silently queuing tasks, atminScale: 0. - Set
postgres.config.passwordat first install. It is bundled plumbing, but the shipped placeholder is published and the value cannot be changed once the data directory exists. - The UI is closed by default and a firewall change takes up to a few minutes to apply, so an endpoint still returning
403right after an upgrade is not necessarily broken. - The first Helm upgrade after an install re-applies resources even with identical values, restarting the webserver and the database — the UI was unavailable for about three minutes in testing. Later upgrades are clean.
- Changing the admin password means editing the auth secret and restarting the webserver; the password file is rewritten from the secret on every container start.
- Uninstalling deletes all three volume sets, including the DAGs and logs on the shared Airflow home. Export anything you need first.