Airflow Connections: Rotated Credentials That Broke DAGs
Airflow rotated DB password broke every DAG at 2 AM.
20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.
- ✓A DAG that currently holds a hardcoded credential
- ✓CLI access to run airflow connections commands
- ✓One target database you can reconnect safely
- Connections bundle type, host, login, password, and extras under a conn_id that tasks reference
- Create them via UI, airflow connections add CLI, env vars, YAML in 3.x, or a Vault or AWS backend
- Hardcoded passwords turn one 2 AM rotation into twenty failing DAGs with twenty separate pages
- Production rule: backends own prod secrets so rotation is a vault event with zero code deploys
- Hooks are the reusable client layer giving every task uniform retries and timeouts
Hardcoding passwords in DAGs is like taping the office safe combination to every desk: one combination change means visiting every desk in the dark, while connections keep a single master key in a guarded vault where every task checks it out under supervision.
A database password rotated at 2 AM and every DAG died within minutes. Twenty files held the same hardcoded credential, and the rotation invalidated all of them at once. The fix took hours because the secret lived in code.
Connections exist so credentials live in one place. You'll learn how hooks, backends, and rotation turn 2 AM pages into non-events.
We'll build connections four ways and wire hooks properly. Secrets stop shipping with code.
What a Connection Stores
A connection stores everything a client needs: type, host, port, login, password, schema, and an extras JSON for knobs like warehouse or role. It lives under a conn_id such as analytics_db. Tasks name the id; Airflow resolves the secret.
Resolution order matters. Env vars override the metadata DB, and configured backends slot into the lookup chain. That layering lets local dev use simple vars while prod reads Vault through the same conn_id.
Treat conn_ids as public interface. Rename one and every consumer breaks, so standardize names per system and document them like API endpoints.
You'll reference them in templates as {{ conn.analytics_db.host }} and in code via BaseHook.get_connection("analytics_db"). Types span postgres, http, aws, snowflake, and custom provider types — full list lives under provider Connections docs. Extra JSON carries sslmode, IAM flags, and pool knobs.
Creating Connections: UI, CLI, Env Vars, YAML in 3.x
The UI path suits exploration: Admin, Connections, add, test. The CLI path suits automation: airflow connections add with JSON or URI forms, checked into setup scripts. Both land in the metadata DB.
Env vars suit containers: AIRFLOW_CONN_ANALYTICS_DB carries the URI without touching the DB. YAML suits versioned non-prod config in 3.x. Backends suit production, serving secrets at lookup time.
Choose per environment, keep conn_ids identical. A DAG referencing analytics_db runs unchanged from laptop to prod because only resolution differs.
Install the code first: pip install apache-airflow-providers-postgres ships PostgresHook and SQL operators. Env form is AIRFLOW_CONN_ANALYTICS_DB='postgres://etl_svc:pw@db.internal:5432/analytics?sslmode=require' (URL-encoded). PostgresHook defaults to conn_id postgres_default when you'd omit the arg.
Hooks: The Reusable Client Layer
A hook turns a connection into an authenticated client with retries, timeouts, and helper methods. PostgresHook.get_records runs queries, S3Hook moves bytes, SnowflakeHook manages warehouses. Tasks call hooks; hooks own the wire.
This layering pays twice. Client logic lives once instead of per task, and behavior stays uniform: every Postgres task shares timeouts and retry posture. Debugging means reading one helper, not six dialects.
Never construct raw clients in tasks. Raw psycopg2 or boto3 calls bypass connection resolution, ignore backends, and scatter credentials. Hooks are the sanctioned path.
Methods split cleanly: hook.run() for CREATE/INSERT/UPDATE/DELETE with no return, hook.get_records("SELECT ...") for list-of-tuples, hook.get_df("SELECT ...") for pandas, plus bulk_load/bulk_dump for files. You'll prefer CREATE TABLE IF NOT EXISTS for idempotent setup and parameterized queries over f-strings.
Secret Backends: Stop Shipping Credentials
Secrets backends serve connections from Vault, AWS Secrets Manager, or SSM at lookup time. Airflow checks env, then backends, then the DB. Credentials rotate in the vault; DAGs never redeploy.
Configure once in the secrets section and scope policies tightly. Each environment reads its own vault path, so staging credentials cannot leak into prod lookups. Least privilege applies to pipelines too.
The UI only shows DB-stored values, so backend secrets stay invisible there by design. Verify through test tasks and backend audit logs, not the connections page.
Rotation Without Redeploy
Rotation without redeploy has three steps: update the vault entry, confirm a test task resolves it, and watch scheduled runs stay green. No merge, no image build, no restart.
Schedule drills quarterly. Rotate staging first, verify with dags test, then rotate prod during business hours. Each drill proves the path that 2 AM rotations will use.
Keep a conn_id inventory. Every DAG's connections list lives in code review, so auditors and on-call see exactly which systems each pipeline touches.
Connection Testing in CI
CI catches missing connections cheaply. Build test connections from fixtures, run dags test for touched DAGs, and execute a hook get_conn check per conn_id. A missing staging connection fails the merge, not the midnight run.
DagBag import tests complement connectivity checks. Parsing proves structure; connection tests prove runtime. Together they gate the two failure classes that reach schedulers most.
Keep prod credentials out of CI. Test fixtures use throwaway databases and fake backends. The gate validates wiring and names, never real secrets.
Rotated Credentials That Broke Every DAG
- Credentials in code turn one 2 AM rotation into 20 edits and 22 pages.
- Connections plus Vault make rotation a 2-minute backend event, not a deploy.
- Hooks as the single client path keep retries and timeouts uniform across 14 DAGs.
| File | Command / Code | Purpose |
|---|---|---|
| add_connection.sh | pip install apache-airflow-providers-postgres | Creating Connections |
| dags | from airflow.sdk import dag, task | Hooks |
| rotate_secret.sh | vault kv put secret/airflow/connections/analytics_db \ | Rotation Without Redeploy |
Key takeaways
Common mistakes to avoid
4 patternsHardcoding passwords and keys in DAG files
Copy-pasting connection details per DAG instead of sharing conn_ids
Constructing raw DB clients in every task
Skipping connection testing in CI
Interview Questions on This Topic
What is a connection and how do you create one?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.
That's Airflow. Mark it forged?
3 min read · try the examples if you haven't