Date: 2026-05-11
Time: 15:51
This is a comprehensive example script demonstrating how to use FTL2 to provision and configure a complete web application stack on Hetzner Cloud. It orchestrates cloud infrastructure (SSH keys, networks, firewalls, servers) using the hetzner.hcloud Ansible collection through FTL2's Python API, then configures the provisioned server with nginx via SSH — all in a single async script. It serves as a reference for the "dynamic provisioning workflow" pattern where FTL2 creates cloud resources and immediately configures them.
The script is invoked directly with optional flags:
uv run python example_hetzner_web_stack.py # Full provision + configure
uv run python example_hetzner_web_stack.py --check # Dry run (no changes)
uv run python example_hetzner_web_stack.py --teardown # Delete all resources
The core pattern is async with automation(...) as ftl: to get an FTL2 session, then chaining module calls via attribute access on ftl:
# Cloud resource modules via collection namespace
await ftl.hetzner.hcloud.server(name="...", state="present", ...)
# Host-targeted commands after add_host()
await ftl[SERVER_NAME].command(cmd="apt-get install -y nginx")
await ftl[SERVER_NAME].ansible.builtin.service(name="nginx", state="started")
The provision-then-configure pattern is key: after creating a server, the script reads back its IP, registers it as a host with ftl.addhost(), waits for SSH with ftl.waitfor_ssh(), then runs configuration modules against it — all within the same session.
automation() context manager parameters used:
autoinstalldeps=True — automatically install missing Ansible collectionscheck_mode=bool — dry-run mode, no actual changesverbose=True — detailed outputsecretbindings={"hetzner.hcloud.*": {"apitoken": "HCLOUD_TOKEN"}} — maps env vars to module parameters by collection glob patternstate_file=".ftl2-state-hetzner.json" — persistent state file across runsEnvironment variables:
HCLOUDTOKEN (required) — Hetzner Cloud API token, injected via secretbindingsHETZNERSSHPUBKEYFILE (optional, default ~/.ssh/ided25519.pub) — SSH public key pathHardcoded config constants: SERVERNAME, SERVERTYPE (cx22), IMAGE (ubuntu-24.04), LOCATION (nbg1), network/subnet CIDRs, firewall and SSH key names.
"hetzner.hcloud.*" automatically injects the HCLOUDTOKEN env var as apitoken to all modules in that collection namespace — no manual token passing per call.ftl.state.add() persists server metadata (IP, provider, etc.) to the state file; ftl.state.remove() cleans it up on teardown. This enables future runs to reference provisioned resources.ftl.addhost() registers a just-created server as an inventory host mid-run, with connection parameters (ansiblehost, ansible_user, group membership).ftl.waitforssh() blocks until the new server accepts SSH connections before attempting configuration.ftl.results collects all operation outcomes; ftl.failed and ftl.errors provide error introspection with module name and error message.check_mode=True, the script skips post-provision configuration (SSH wait, apt-get, nginx setup) since the server doesn't actually exist.state parameter: state="present" for creation, state="absent" for deletion — standard Ansible idempotency semantics.ftl2.automation — the main FTL2 async context managerhetzner.hcloud — installed via ansible-galaxy, used for all cloud resource modules (sshkey, network, subnetwork, firewall, server, servernetwork, server_info)command (for apt-get), ansible.builtin.service (for nginx) — used for server configuration after provisioning.ftl2-state-hetzner.json — persists provisioned resource metadata between runs