Skip to main content

Overview

Apache Airflow is an open-source platform for programmatically authoring, scheduling and monitoring workflows. This template deploys an Airflow 3.x stack using the CeleryExecutor: one webserver workload running the API server, scheduler, dag-processor and triggerer, Celery workers that execute tasks, Redis as the task broker and PostgreSQL as the metadata database. Workers run at a fixed replica count or, optionally, are scaled by KEDA on the queue length; DAGs are placed on a shared volume or delivered by an optional git-sync sidecar. The admin password, the JWT signing secret and the Fernet key are not template values. They come from a dictionary secret you create before installing, so they never pass through Helm. The web UI ships closed to the internet: it can trigger arbitrary code and decrypt every credential stored in an Airflow Connection, so you reach it over a port-forward until you choose to expose it.
This template deploys into an existing GVC; it does not create one. Every resource is pinned to the single location named by the location value, which must already be a location of that GVC. Other locations the GVC has are left alone: nothing Airflow-related starts in them. There is no multi-location mode, because the shared Airflow home provisions one volume per location and the database and broker are each a single volume.

What Gets Created

Prerequisites

One secret must exist before you install, and the GVC must already have the location you configure. Secrets are org-level, so no GVC flag is involved.
1

Confirm the GVC has the location you will configure

location must name a location the GVC already has. Read the current list under spec.staticPlacement.locationLinks:
Set location to one of those names (the default is aws-us-east-1). To add a location, see the GVC reference — every workload in a GVC runs in every location it has, so add locations to a shared GVC deliberately.
2

Create the auth secret

A dictionary secret holding exactly three keys — jwtSecret, fernetKey and adminPassword:
Set airflow.auth.secretName to this name.
3

Read the secret back later

Pass -o yaml — a bare cpln secret reveal prints only a summary table, not the values:
4

Optional: create a git token secret for a private DAG repository

Only when gitSync.enabled is true and the repository is private. An opaque secret with encoding plain whose payload is a personal access token; the sidecar sends it as the Git password:
Set gitSync.auth.secretName to this name. Leave it empty for a public repository or when git-sync is off — setting it while git-sync is disabled is rejected at render.
5

Optional: enable KEDA on the GVC

Only when you set keda.enabled: true. KEDA is a GVC-level setting this template cannot turn on for you; see Keda. Export the GVC, set spec.keda.enabled: true in the exported file, apply it and read it back:
Always start from the exported file. cpln apply replaces a GVC’s spec rather than merging into it, so a hand-written file carrying only spec.keda removes the GVC’s locations.
Create the auth secret before installing, or the deployment wedges silently. A secretName pointing at a secret that does not exist installs “successfully” and then never starts. No container runs, so cpln logs returns zero lines. The one place the reason appears is status.versions[].message:
The command is get-deployments — plain cpln workload get has no versions key. Once the secret exists the workload recovers on its own, slowly; force a redeployment to skip the wait:
Generate the Fernet key once and keep it for the life of the install. It encrypts every Connection and Variable Airflow stores. A different key does not fail loudly — it makes all of them unreadable, and there is no re-encrypt path short of re-entering each one by hand. Back the secret up somewhere you will still have it later.

Installation

Install from the marketplace registry, naming the location your GVC has and the auth secret you created:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Location

Every workload in the release is pinned here. GVC locations you do not name run nothing — their deployments read This workload location is deactivated because maxScale is set to 0., which is what a healthy install looks like, not a fault.

PostgreSQL Metadata Database

The database serves Airflow only and is unreachable from outside the GVC, so its credentials stay values and the chart writes them into RELEASE_NAME-airflow-config. Set postgres.config.password at first install: the shipped placeholder is published, and PostgreSQL reads the password only when the data directory is initialized, so it cannot be changed through values afterwards.

Redis Broker

Airflow

The webserver runs the API server, scheduler, dag-processor and triggerer in one container pinned to a single replica, so airflow.celeryWorker.replicas (or KEDA) is the only horizontal scaling knob: you scale task execution, not scheduling. The admin account is provisioned by Airflow’s SimpleAuthManager, which has no SSO or LDAP support; the webserver rewrites its password file from the secret on every container start.

Storage

The Airflow home is a shared filesystem mounted at /opt/airflow by the webserver and every Celery worker, so they read the same DAGs and write to the same log directory. The database and broker volumes are sized separately under postgres.volumeset.capacity and redis.volumeset.capacity.

Access

firewallConfig.inboundAllowCIDR applies to the webserver only. With the shipped [] the canonical endpoint returns 403 and the UI is reachable only over a port-forward; a change takes up to a few minutes to take effect. internalAccess controls which workloads inside the org may reach Airflow’s own workloads: Under workload-list you only list your own clients: the template merges in the webserver, workers, database and broker so they can keep reaching each other, and allows the KEDA operator onto the broker when keda.enabled is true.

DAG Delivery With git-sync

When enabled, a git-sync sidecar runs on the webserver and worker workloads, clones the repository under /opt/airflow/dags-sync on the shared volume, and the DAGs folder on both tiers points at the synced checkout (plus gitSync.subPath when set). gitSync.repo and gitSync.branch are required when git-sync is on.

KEDA Autoscaling

KEDA scales the Celery workers on the Redis queue length, including down to zero between runs. With it off, workers run at the fixed airflow.celeryWorker.replicas.
Enable KEDA on the GVC before you set keda.enabled: true. Getting the order wrong is accepted without any error: the worker workload is created, never autoscales, and sits at keda.minScale forever — at minScale: 0 that is zero workers, and every task queues with nothing reporting a problem. On a fresh install the webserver checks the GVC at boot and refuses to start with [airflow] FATAL: keda.enabled is true in your values, but GVC ... has spec.keda.enabled = false; on an established install the same condition is logged as a warning and Airflow keeps running. Enabling KEDA on the GVC clears the refusal on the webserver’s next restart. See Prerequisites for the commands.

Connecting

Verify the webserver is serving through the tunnel:

First Run

1

Install with the UI closed

Keep the shipped firewallConfig.inboundAllowCIDR: [].
2

Tunnel to the UI and sign in

Open http://localhost:8080 and log in with airflow.admin.username and the adminPassword from your auth secret. There is no setup wizard; the admin account already exists.
3

Expose it only if you need to

Run a Helm upgrade with your own CIDR ranges in firewallConfig.inboundAllowCIDR, then allow a few minutes for the firewall change to take effect before testing the canonical endpoint.

API Access

Airflow 3.x issues JWT access tokens signed with your jwtSecret. Request one with the admin credentials and pass it on the v2 API:
Tokens expire after airflow.auth.jwtExpirationTime seconds.

Operations

Deploying DAGs

With git-sync off, DAG files go in /opt/airflow/dags on the shared volume; the dag-processor picks up a new file within airflow.scheduler.dagDirListInterval seconds. Copy a local folder in through the webserver container (--stdin is required, and the archive is base64-encoded because cpln workload exec is not binary-safe):
With gitSync.enabled: true, commit DAGs to the configured repository and branch instead; the sidecar syncs them every gitSync.period.

Backing Up

The chart schedules no backups and no volume set snapshots. The metadata database — every Connection, Variable, DAG run and task history — is the one thing to back up; the broker is transient and DAGs live in your repository or on the shared volume. Two options: Take a snapshot of the database volume set:
Or dump the database with pg_dump, which exists in the postgres:18 image. The dump is base64-encoded in transit because cpln workload exec corrupts binary output:
Back up the auth secret too: the Fernet key inside it is the only way to read the dump’s Connections and Variables.

Restoring a Backup

This restore path has not been exercised end to end against this template. The commands are built from the chart’s configuration (container postgresql, credentials in POSTGRES_USER / POSTGRES_DB) and the tools present in the pinned images, but verify them on a throwaway release before relying on them.
Restore a pg_dump archive into the metadata database, then restart the webserver and workers so they re-read it. Stop triggering DAG runs while the restore runs:
The restored Connections and Variables are readable only under the Fernet key that encrypted them, so the release must point at the same auth secret the backup was taken under. Restoring a volume set snapshot in place (cpln volumeset snapshot restore) is not verified for this template.

Rotating the Admin Password

The webserver rewrites its SimpleAuthManager password file from the secret on every container start, so changing adminPassword and restarting the webserver is enough. A secret change is not picked up by a running replica, so redeploy after changing it. Export the secret, edit only adminPassword (leave fernetKey untouched), re-apply it and confirm the change:
Then restart the webserver:
jwtSecret can be rotated the same way — redeploy both the webserver and RELEASE_NAME-airflow-celery-worker, and every outstanding API token becomes invalid. Never change fernetKey.

Upgrading from 1.4.x or Earlier

Template version 1.5.0 removed the credentials that 1.4.x took as plain Helm values. Each removed key is rejected at render with a message naming its replacement, so an old values file fails loudly instead of being silently ignored. There is no in-place path from 1.4.x to 2.0.0 — follow Migrating from 1.x, carrying only the new keys:
If your 1.4.x install ran on the shipped CHANGE_ME Fernet key, every stored Connection and Variable is encrypted under a value published in a public repository. The key cannot be rotated, so there is no in-place fix: export what you need, install with a freshly generated key, re-enter the Connections and Variables by hand, and rotate every credential they held at its source.

Migrating from 1.x

Template versions through 1.5.0 created their own GVC. Version 2.0.0 deploys into an existing GVC and does not declare one — and Helm deletes what a chart stops declaring.
Never helm upgrade a 1.x release onto 2.0.0. The upgrade drops the GVC from the manifest and deletes it, with every workload, volume set and identity inside — your metadata database and your DAGs — while reporting success. The chart refuses to render when your values still carry the gvc key, and the refusal leaves the running release untouched:
An upgrade run with no values at all has no gvc key to catch and proceeds, so the rule matters more than the guard. The only supported path is a new release against an existing GVC.
1

Pick the GVC and location for the new release

Choose a GVC that already has the location you will set as location, and enable KEDA on it first if you plan to use it. See Prerequisites.
2

Install 2.0.0 as a new release against the same auth secret

Use a different release name — policy and secret names are org-wide, so a same-named release collides with the 1.x one even in another GVC. Point airflow.auth.secretName at the same auth secret the 1.x release used: the fernetKey must be identical, or every restored Connection and Variable becomes unreadable.
3

Move the metadata database

Dump from the old release’s database workload, in the GVC the 1.x chart created, and restore into the new one. Both go through base64 because cpln workload exec is not binary-safe:
This data move has not been exercised end to end; see Restoring a Backup.
4

Move the DAGs

With gitSync.enabled: true there is nothing to do — the sidecar clones onto the new volume. Otherwise copy them off the old shared volume:
5

Verify, then uninstall the old release

Force a redeployment of the new webserver so the scheduler re-reads the restored database, and confirm in the UI that your DAGs, Connections and Variables are present. Only then uninstall the 1.x release — against the GVC you installed it into, where Helm tracks it, not the GVC it created. That uninstall deletes the created GVC and everything still in it.
Values that moved or were removed in 2.0.0:

Scaling and Availability

  • Workers scale; the webserver does not. Raise airflow.celeryWorker.replicas for a fixed pool, or enable KEDA to scale on queue length (see KEDA Autoscaling). The webserver, database and broker are each a single replica on a single volume.
  • 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. Run a separate release per location if you need Airflow in two places.
  • The first Helm upgrade after an install can restart the bundled database and the webserver even when nothing changed. Expect a brief interruption of the UI; later upgrades with unchanged values are clean.
  • A wide simultaneous fan-out can fail tasks without starting them. The Celery executor’s Redis transport in this worker image has an upstream race when many tasks are dispatched at the same instant; the affected tasks are marked failed with no hostname, no start time and an empty log. Stagger task submission rather than fanning out wide at once.

Troubleshooting

Symptom: every resource reports created, no workload becomes ready, cpln logs is empty.Cause: the auth secret (or the git token secret) does not exist, so no container ever starts.Fix: read the deployment message, which names the missing secret:
Create the secret exactly as in Prerequisites, then force a redeployment rather than waiting for the automatic recovery.
Symptom: the install succeeded, all four workloads sit at zero replicas, and cpln workload get-deployments shows that message for every location.Cause: location names a location the GVC does not have. The platform stores the placement without validating it.Fix: compare location with spec.staticPlacement.locationLinks from cpln gvc get GVC_NAME -o yaml, then either add the location to the GVC or upgrade the release with a location the GVC already has. Seeing the message on the GVC’s other locations while the configured one runs is normal.
Symptom: the webserver crash-loops on a fresh install with that line in cpln logs.Cause: keda.enabled: true without KEDA enabled on the GVC. The worker workload was accepted anyway and will never autoscale.Fix: enable KEDA on the GVC as in Prerequisites; the webserver passes the check on its next restart. Or set keda.enabled: false and use airflow.celeryWorker.replicas.
Symptom: keda.enabled: true, the worker workload is healthy at keda.minScale (zero workers at minScale: 0), queued tasks never run, nothing is logged.Cause: KEDA is not enabled on the GVC, and the install was onto an existing Airflow home, so the webserver only warned instead of refusing to start. Under internalAccess.type: workload-list the template already allows the KEDA operator onto the broker, so that is not the cause.Fix: read spec.keda.enabled with cpln gvc get GVC_NAME -o yaml and enable it as in Prerequisites.
Symptom: several tasks of a wide DAG run are marked failed with an empty log, no hostname and no start time, while the rest succeed.Cause: an upstream race in the Celery executor’s Redis transport (module 'redis' has no attribute 'client' from kombu/transport/redis.py) when many tasks are dispatched at the same instant.Fix: stagger task submission, and count completed task instances rather than relying on the UI to flag the failures.
Symptom: upgrading a 1.x release to 2.0.0 fails at render with the message quoted in Migrating from 1.x.Cause: your values still carry the 1.x gvc key. The refusal is deliberate; the in-place upgrade would delete the GVC the 1.x chart created.Fix: do not remove the key and retry. Install 2.0.0 as a new release and move the data as described in Migrating from 1.x.
Symptom: the webserver is ready but its public URL returns 403.Cause: firewallConfig.inboundAllowCIDR is [] (the default), or a recent change has not taken effect yet.Fix: use the port-forward from First Run, or add your CIDR ranges and allow a few minutes for the change to apply.
Symptom: after an upgrade that changed postgres.config.password, the webserver and workers fail to authenticate against PostgreSQL.Cause: PostgreSQL reads POSTGRES_PASSWORD only when the data directory is initialized, so the database still has the old password while Airflow’s connection string carries the new one.Fix: revert the value to the password the volume was initialized with. The password can only be set at first install.

Important Notes

  • Create the auth secret before installing. Without it the deployment wedges with no log output; cpln workload get-deployments is the one place the missing secret is named.
  • The Fernet key can never be rotated. Generate it once, keep it, and back it up with the auth secret.
  • 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. See Migrating from 1.x.
  • location must be a location of your GVC. Otherwise the install succeeds and nothing starts anywhere.
  • Enable KEDA on the GVC before setting keda.enabled: true, or the workers sit at keda.minScale forever with no error.
  • Set postgres.config.password at first install. The shipped placeholder is published and the value cannot be changed once the data directory exists.
  • The UI is closed by default. Reach it with cpln port-forward; a firewall change takes a few minutes to apply.
  • The first Helm upgrade after an install can restart the database and webserver even with unchanged values; expect a brief interruption.
  • Changing the admin password means editing the secret and forcing a webserver redeployment; see Rotating the Admin Password.
  • Uninstalling deletes all three volume sets, including the DAGs and logs on the shared Airflow home. Back up the metadata database first; see Backing Up.

External References

Apache Airflow Documentation

Official Apache Airflow documentation

Fernet Key and Encryption at Rest

How Airflow encrypts Connections and Variables

Airflow Public API Authentication

Requesting and using JWT access tokens

CeleryExecutor

The CeleryExecutor and distributed task execution

KEDA Documentation

Event-driven autoscaling documentation

Airflow Template

View the source files, default values, and chart definition