Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
4.5 KiB
🚀 Provisioning Servers with railiance-infra
This guide explains how adopted hosts and provider-managed Hetzner hosts share an inventory without sharing lifecycle behavior.
🚀 Fast Path: Using the Helper Script
Instead of manually editing inventory/servers.yaml and running make tf-apply, you can use the convenience script scripts/hcloud_new_server.sh.
This script will:
- Add the new host entry to
inventory/servers.yaml - Decrypt your Hetzner API token with SOPS
- Run Terraform init and plan
- Apply only when the operator supplies the explicit
--applyflag after reviewing the plan
Example:
scripts/hcloud_new_server.sh core-01 --type cpx11 --region nbg1 --role core
# after review and the required approval:
scripts/hcloud_new_server.sh core-01 --type cpx11 --region nbg1 --role core --apply
This will create a small cpx11 instance in the Nuremberg (nbg1) region, tagged with the role core. You can then connect directly:
ssh admin@<printed-ip>
👉 The script is optional. You can always manage servers by editing inventory/servers.yaml and running make tf-apply instead.
1) Where you define servers
All host identities live in inventory/servers.yaml. Run
make validate-inventory before Terraform or Ansible. Adopted hosts have a
provider address but no provisioning block. Provider-managed Hetzner hosts
have no committed IP and use this shape:
servers:
- name: core-01
provider: hetzner
lifecycle_mode: provider-managed
ssh_user: admin
baseline_profile: ufw-managed
provisioning:
server_type: cpx21
region: nbg1
image: ubuntu-24.04
role: core
labels: [core, wireguard, git]
Tip: Keep names stable. Renaming a server in this file makes Terraform think the old one was destroyed and a new one should be created.
2) Two ways to add a server
A) Edit YAML by hand (simple)
Open inventory/servers.yaml, add a new entry, save, commit.
B) Use the helper script (safe & quick)
# requires scripts/new_host.py
make new-host NAME=web-01 TYPE=cpx21 REGION=nbg1 ROLE=web
# or directly:
python3 scripts/new_host.py --name web-01 --type cpx21 --region nbg1 --role web
You can also do add + provision in one step:
scripts/hcloud_new_server.sh web-01 --type cpx21 --region nbg1 --role web
3) How Terraform uses your declaration
The module at terraform/hetzner/:
- Selects only records with
provider: hetznerandlifecycle_mode: provider-managed - Registers your SSH key from
keys/admin_ssh.pub - Injects cloud-init that sets up the
adminuser and basic hardening - Creates/updates/destroys servers to match the YAML
Outputs include a map of server names → IPv4 addresses.
4) Provision (create/update)
The Hetzner API token is SOPS-decryptable at
secrets/hetzner-token.yaml, field hetzner.token. It is decrypted only into
the invoking process environment.
Then run either:
# plan and apply in separate steps
make tf-plan
APPROVE_TF_APPLY=YES make tf-apply
or the end-to-end convenience:
APPROVE_TF_APPLY=YES make apply # terraform apply + Ansible bootstrap
If you used the one-shot script:
scripts/hcloud_new_server.sh web-01 --type cpx21 --region nbg1 --role web
Terraform will print the new servers’ IPv4 addresses at the end.
5) Connect & converge
Connect via SSH:
ssh admin@<server-ip>
Run Ansible base bootstrap (if not using make apply):
make ansible-bootstrap
6) Destroy (tear down)
To remove all servers managed by this repo:
APPROVE_TF_DESTROY=DESTROY-MANAGED-HETZNER make tf-destroy
To remove one provider-managed Hetzner server, delete its entry from
inventory/servers.yaml, review the plan, obtain the required approval, then:
APPROVE_TF_APPLY=YES make tf-apply
Terraform will destroy the missing managed Hetzner server and leave others intact. Adopted Host Europe records are never Terraform resources.
7) Notes & conventions
- Idempotent: You can run
make applyrepeatedly; Terraform converges infra, Ansible converges config. - SSH keys: Ensure
keys/admin_ssh.pubexists before provisioning. - Secret token: The Hetzner API token is
secrets/hetzner-token.yaml, fieldhetzner.token, and must remain SOPS-encrypted. - Cloud-init delay: Allow ~30–60s after creation for first-boot tasks before first SSH.
- Labels & role:
labelsare freeform tags;rolecan drive Ansible plays as you grow.