Deployment guide

Apache Airflow 3.2

Overview

Apache Airflow is a workflow orchestration platform for authoring, scheduling and monitoring pipelines as code. This SecureAnyCloud image runs Apache Airflow 3.2.0 as a single-node standalone deployment on Ubuntu 22.04 LTS, so you can have Airflow running in minutes. It saves you the work of packaging, virtual environment setup, PostgreSQL role creation, systemd units and first admin user provisioning.

What you get:

  • The API server (which serves the web UI and the REST API), the scheduler, the triggerer and the DAG processor, each running as a dedicated systemd unit under the airflow service user. In Airflow 3.x the DAG processor is a separate mandatory process, and the scheduler no longer parses DAG files inline as it did in 2.x.
  • A PostgreSQL 14 metadata database on the same virtual machine, bound to 127.0.0.1 and reachable only by processes on the host
  • LocalExecutor, so task instances run as subprocesses of the scheduler with no external Celery or Kubernetes dependency
  • An admin user already created in the metadata database, and one example DAG, example_hello_world, staged in /opt/airflow/dags

The admin password is set when the image is built. It is identical across all customers who deploy from the same gallery image version, so rotate it as soon as you log in (see Section 12).

This image is not a multi-node Airflow deployment with a shared executor. It is not TLS encrypted out of the box, and it does not ship with LDAP, SSO, Redis, Celery workers or a KubernetesExecutor. Section 13 describes the recommended path for hardening and scaling beyond a single node.

All SecureAnyCloud URLs in this guide use the form https://secureanycloud.com.

Prerequisites

Before you deploy this image, make sure you have:

  • A Microsoft Azure subscription where you can create resource groups, virtual networks and virtual machines
  • Azure role permissions equivalent to Contributor on the target resource group
  • An SSH public key for your first login to the admin user account
  • A virtual network and subnet in the same region as the Azure Compute Gallery that the image is published into, with an associated network security group
  • The Azure CLI (az version 2.50 or later) installed locally, if you plan to use the CLI deployment path in Section 4
  • The SecureAnyCloud Apache Airflow 3.2 offer enabled on your tenant in Azure Marketplace
1

Deploy the Virtual Machine from the Azure Portal

In the Azure Portal, open Marketplace, search for Apache Airflow 3.2, and select the SecureAnyCloud publisher entry. Click Create to start the wizard.

On the Basics tab, choose your subscription, target resource group and region. The region must match the region where your Azure Compute Gallery exposes the image. Set the virtual machine name. Choose SSH public key as the authentication type, enter a username, and paste your SSH public key.

On the Disks tab, choose Premium SSD as the OS disk type and leave the OS disk size at the default. On the shipped image, the PostgreSQL metadata directory and the Airflow logs directory both live on the OS disk. For any real workload, attach a separate Premium SSD data disk and move at least /var/lib/postgresql onto it.

On the Networking tab, select your existing virtual network and subnet. Attach a network security group that allows inbound TCP 22 from your management IP range. Allow inbound TCP 8080 only from the CIDRs that need to reach the Airflow web UI.

Do not expose 8080 to the public internet. The API server runs plain HTTP on 8080, and the admin role has full write access to the metadata database. Any exposure beyond a trusted management network requires the HTTPS reverse proxy described in Section 13.

The defaults on the Management, Monitoring and Advanced tabs are appropriate. Click Review + create, wait for validation to pass, then click Create. Deployment takes around three minutes.

2

Deploy the Virtual Machine from the Azure CLI

If you prefer the command line, accept the Marketplace terms once. Then deploy from the Marketplace image URN:

RG="airflow-prod" LOCATION="eastus" VM_NAME="airflow-1" ADMIN_USER="airflowops" IMAGE_URN="sac1647283583153:airflow-3-2-ubuntu-22-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create \
  --resource-group "$RG" \
  --name airflow-vnet \
  --address-prefix 10.30.0.0/16 \
  --subnet-name airflow-subnet \
  --subnet-prefix 10.30.1.0/24 az network nsg create --resource-group "$RG" --name airflow-nsg az network nsg rule create \
  --resource-group "$RG" --nsg-name airflow-nsg \
  --name allow-ssh-mgmt --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create \
  --resource-group "$RG" --nsg-name airflow-nsg \
  --name allow-airflow-ui --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 8080 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:airflow-3-2-ubuntu-22-04:default:latest
az vm create \
  --resource-group "$RG" \
  --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --size Standard_D2s_v3 \
  --admin-username "$ADMIN_USER" \
  --ssh-key-values "$SSH_KEY" \
  --vnet-name airflow-vnet --subnet airflow-subnet \
  --nsg airflow-nsg \
  --os-disk-size-gb 64

For a production deployment, replace <your-mgmt-cidr> with a tight management source range. The API server listens on 0.0.0.0:8080, so the NSG rule is the only control that stops arbitrary clients from reaching the UI.

3

Connect via SSH

After deployment, find the public or private IP address of the new virtual machine. From your management host, run:

ssh airflowops@<vm-ip>

The first login may take a few seconds while cloud-init finishes. Once you have a shell, systemd has started the API server, scheduler, triggerer and PostgreSQL, and the scheduler has parsed the example DAG.

4

Retrieve the Admin Credentials

The initial admin credentials are stored in a single file inside the image. Display it:

sudo cat /stage/scripts/airflow-credentials.log

The output looks similar to this:

# Apache Airflow 3.2 on Ubuntu 22.04 — initial credentials
# Generated on: 2026-04-17T09:45:00Z
#
# These credentials are shipped inside the captured image. All customers
# deploying from this image share the same initial admin password —
# ROTATE IT IMMEDIATELY on first login (Security -> List Users -> Edit).
#
# Web UI: http://<vm public ip>:8080
# Admin username: admin
# Admin password: 4f8aB2dQ9pK1xVnE7sT
#
# PostgreSQL metadata DB (localhost only, bound to 127.0.0.1:5432):
# database: airflow
# username: airflow
# password: 8Zc4pK1vXnE7sTuYrL6c
#
# Fernet key (also in /opt/airflow/airflow.cfg):
# qHFNMfFT0aMVg1n_Vw6LLsB7Jz3GQKxX4uEo9r2bIh8=

The password is set when the image is built, so every virtual machine launched from the same gallery image version starts with the same initial admin password. Rotate it immediately (see Section 12). After you rotate it, this file no longer reflects the live password. It remains only as a record of the shipped password.

5

Open the Web UI and Log In

Open the Airflow web UI in your browser:

http://<vm-ip>:8080

Log in with the username admin and the password from Step 4.

Rotate the password immediately after you log in:

  • Click the Security menu in the top right.
  • Choose List Users.
  • Click the edit (pencil) icon next to admin.
  • Enter a new password in both password fields.
  • Click Save.

Airflow stores the rotated password, hashed, in the ab_user table of the PostgreSQL metadata database. The shipped credentials file at /stage/scripts/airflow-credentials.log is not updated after rotation. Record the new password in your own secrets manager.

6

Explore the Example DAG

The image ships with one example DAG, example_hello_world, at /opt/airflow/dags/example_hello_world.py. In the web UI:

  • On the DAGs page, locate example_hello_world.
  • Toggle the pause switch on the left to unpause the DAG.
  • Click the play icon on the right and choose Trigger DAG.
  • Watch the grid view. The single task greet moves through queued → running → success within 10 seconds.

You can verify the same end-to-end pipeline from the command line without the UI. The airflow dags test subcommand runs the DAG in-process, without the scheduler. Run it as the airflow service user:

sudo -u airflow \
  env AIRFLOW_HOME=/opt/airflow \
  /opt/airflow/venv/bin/airflow dags test example_hello_world

The output includes the log line hello from SecureAnyCloud - Apache Airflow 3.2 on Ubuntu 22.04, and the command exits with status 0.

7

Author Your First DAG

DAGs live at /opt/airflow/dags. The scheduler watches the directory and picks up new files automatically, by default within 30 seconds. Add a Python file to that directory. The scheduler parses it, checks it for import errors and registers it in the metadata database.

sudo tee /opt/airflow/dags/my_first_dag.py >/dev/null <<'PY' from __future__ import annotations import logging from datetime import datetime from airflow.decorators import dag, task @dag( dag_id="my_first_dag", start_date=datetime(2026, 1, 1), schedule=None, catchup=False, tags=["sac", "example"], ) def my_first_dag(): @task def hello(name: str) -> str: msg = f"hello {name} from my first DAG" logging.info(msg) return msg hello("world") my_first_dag() PY
sudo chown airflow:airflow /opt/airflow/dags/my_first_dag.py
sudo chmod 0644 /opt/airflow/dags/my_first_dag.py

After about 30 seconds, refresh the DAGs page. my_first_dag appears in the list. If it does not, check for import errors:

sudo -u airflow \
  env AIRFLOW_HOME=/opt/airflow \
  /opt/airflow/venv/bin/airflow dags list-import-errors
8

Server Components

The deployed image contains the following components:

Component Version Purpose

Apache Airflow 3.2.0 Workflow orchestration: DAG authoring, scheduling and monitoring

Python 3.10 (Ubuntu default) Host interpreter backing the Airflow venv

PostgreSQL 14 Metadata backend, local-only (bound to 127.0.0.1)

Ubuntu 22.04 LTS Base operating system

systemd units airflow-apiserver, airflow-scheduler, airflow-triggerer, airflow-dag-processor, postgresql Process supervision: the API server serves the web UI and REST API, and the DAG processor parses DAG files

The Airflow processes run under the dedicated airflow system user. All four processes (API server, scheduler, triggerer and DAG processor) read their configuration from /opt/airflow/airflow.cfg and connect to PostgreSQL over a local socket on 127.0.0.1:5432.

9

Filesystem Layout

Path Owner Purpose

/opt/airflow/ airflow:airflow 0750 AIRFLOW_HOME: config, logs, DAGs, venv

/opt/airflow/airflow.cfg airflow:airflow 0640 Main Airflow configuration, including fernet_key and sql_alchemy_conn

/opt/airflow/venv/ airflow:airflow Python virtual environment with apache-airflow==3.2.0 installed

/opt/airflow/dags/ airflow:airflow 0750 DAG source files; the scheduler watches this directory

/opt/airflow/logs/ airflow:airflow 0750 Task and scheduler logs

/opt/airflow/plugins/ airflow:airflow 0750 Airflow plugins (empty on shipped image)

/home/airflow/ airflow:airflow 0750 Service user home, which holds the operator helper scripts

/home/airflow/setEnv.sh airflow:airflow 0750 Source this file to get an Airflow-ready shell (activates the venv, exports AIRFLOW_HOME)

/home/airflow/start_all.sh airflow:airflow 0750 Starts all three Airflow systemd units

/home/airflow/stop_all.sh airflow:airflow 0750 Stops all three Airflow systemd units (reverse order)

/etc/systemd/system/airflow-apiserver.service root:root 0644 API server unit (web UI + REST API on port 8080)

/etc/systemd/system/airflow-scheduler.service root:root 0644 Scheduler unit

/etc/systemd/system/airflow-triggerer.service root:root 0644 Triggerer unit

/etc/systemd/system/airflow-dag-processor.service root:root 0644 DAG processor unit (parses files in /opt/airflow/dags/)

/var/lib/postgresql/14/main/ postgres:postgres PostgreSQL data directory (contains the airflow database)

/stage/scripts/airflow-credentials.log root:root 0600 Shipped admin + PostgreSQL credentials

10

Managing Airflow

systemd starts the Airflow processes and PostgreSQL at boot. Manage them with these commands:

# Status of all Airflow units
sudo systemctl status airflow-apiserver airflow-scheduler airflow-triggerer airflow-dag-processor
# Stop (reverse dependency order)
sudo systemctl stop airflow-triggerer airflow-dag-processor airflow-scheduler airflow-apiserver
# Start
sudo systemctl start airflow-apiserver airflow-scheduler airflow-triggerer airflow-dag-processor
# Restart a single unit
sudo systemctl restart airflow-scheduler
# Tail live logs
sudo journalctl -u airflow-apiserver -f sudo journalctl -u airflow-scheduler -f sudo journalctl -u airflow-triggerer -f sudo journalctl -u airflow-dag-processor -f

You can also use the shipped helper scripts:

sudo /home/airflow/start_all.sh
sudo /home/airflow/stop_all.sh

To get an Airflow-ready shell for running airflow CLI subcommands ad hoc, source the environment helper:

sudo -u airflow bash source /home/airflow/setEnv.sh airflow dags list airflow tasks list example_hello_world

PostgreSQL runs under its own systemd unit:

sudo systemctl status postgresql
sudo systemctl restart postgresql
11

Troubleshooting

The web UI will not load. Check sudo systemctl status airflow-apiserver.service (the API server hosts the web UI and REST API on the same port, 8080). If the status is active (running), check the NSG rule on port 8080 and confirm that you are connecting from an allowed source IP. If the service is inactive, find the failure with sudo journalctl -u airflow-apiserver.service -n 200 --no-pager.

A DAG does not appear in the UI. First check for import errors:

sudo -u airflow env AIRFLOW_HOME=/opt/airflow /opt/airflow/venv/bin/airflow dags list-import-errors

If the DAG imports cleanly but still does not appear, confirm that the scheduler is running (sudo systemctl status airflow-scheduler) and wait up to 30 seconds. The scheduler parses DAG files in a background loop, so new files do not appear in the UI until the next parse cycle.

Task runs are stuck in "queued". The single-node image uses LocalExecutor. Confirm that executor = LocalExecutor is set in /opt/airflow/airflow.cfg and that the scheduler unit is running. Tasks run as subprocesses of the scheduler, so queued tasks never run while the scheduler is stopped.

The API server cannot connect to the metadata database. Check sudo systemctl status postgresql. If PostgreSQL is down, the API server loops during start-up. Restart both services with sudo systemctl restart postgresql && sudo systemctl restart airflow-apiserver. Then confirm that the airflow database exists and that the credentials in /opt/airflow/airflow.cfg match:

sudo -u postgres psql -d airflow -c "SELECT 1;"

Login fails with "Invalid login". The admin password stored in the metadata database is the source of truth. If you rotated the password (see Section 5) and then lost the new one, reset it from the command line:

sudo -u airflow env AIRFLOW_HOME=/opt/airflow /opt/airflow/venv/bin/airflow users reset-password --username admin
12

Rotating Credentials

Rotate the admin UI password in the web UI (Section 5) or with the CLI:

sudo -u airflow env AIRFLOW_HOME=/opt/airflow /opt/airflow/venv/bin/airflow users reset-password --username admin

The PostgreSQL password for the airflow role is set when the image is built. It is stored in two places: the sql_alchemy_conn line of /opt/airflow/airflow.cfg, and the airflow-credentials.log file in /stage/scripts. To rotate it, run:

# Stop Airflow processes so no connections linger
sudo systemctl stop airflow-triggerer airflow-scheduler airflow-apiserver
# Change the role password NEW_PG_PW="$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-20)" sudo -u postgres psql -c "ALTER USER airflow WITH PASSWORD '${NEW_PG_PW}';"
# Update airflow.cfg with the new connection string
sudo sed -i -E \
  "s|^sql_alchemy_conn\s*=.*|sql_alchemy_conn = postgresql+psycopg2://airflow:${NEW_PG_PW}@localhost/airflow|" \
  /opt/airflow/airflow.cfg
# Start Airflow back up
sudo systemctl start airflow-apiserver airflow-scheduler airflow-triggerer

The Fernet key encrypts Airflow connections and variables at rest. It is set when the image is built. Rotate it only if you have a reason to, because rotation requires decrypting all existing encrypted values with the old key and re-encrypting them with the new one. The Airflow documentation at https://airflow.apache.org/docs/apache-airflow/stable/security/secrets/fernet.html describes the procedure.

13

Security Recommendations

  • Rotate the admin password immediately after your first login (Section 5).
  • Restrict the network security group on port 8080 to the smallest possible source CIDR. Airflow ships with plain HTTP, and the admin role has full write access to the metadata database.
  • Add an HTTPS reverse proxy (nginx or Azure Application Gateway) in front of the API server before you expose it beyond a private management network.
  • Configure RBAC roles for team access. The shipped image has only the initial admin user. In the Airflow web UI, open Security, then List Roles, to create Viewer, User, Op and custom roles and assign them to named users.
  • Treat /opt/airflow/airflow.cfg as a secret. It contains the PostgreSQL password and the Fernet key, and only the airflow group can read it (mode 0640).
  • Take regular snapshots of /var/lib/postgresql/14/main with Azure Disk Snapshots, so that you can restore the metadata database (including DAG run history, connection records and user records).
  • Subscribe to the Apache Airflow security announce list at https://airflow.apache.org/community/ and apply security patches as they are published.
14

Support and Licensing

Apache Airflow is licensed under the Apache License 2.0. The full text is in /opt/airflow/venv/lib/python3.10/site-packages/apache_airflow-3.2.0.dist-info/LICENSE on the deployed image. SecureAnyCloud distributes the unmodified upstream apache-airflow==3.2.0 package, published to PyPI by the Apache Software Foundation and pinned with the Airflow project's official constraints file for Python 3.10. SecureAnyCloud adds its own configuration, systemd units, helper scripts and PostgreSQL backend on top.

For support with the SecureAnyCloud image itself, contact sales@secureanycloud.com or visit https://secureanycloud.com/support. For Apache Airflow questions outside the scope of the SecureAnyCloud packaging, see the Apache Airflow community resources at https://airflow.apache.org/community/.

Deploy on Azure

Launch Apache Airflow 3.2 on Ubuntu 22.04 on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure