Skip to content

Deploying to a host

Deployment is a single pyinfra script that takes a bare Linux box to a running service. It's idempotent; re-running upgrades the code and restarts the service without regenerating the token or re-downloading images.

What it sets up

  • the virtualization stack (KVM, libvirt, QEMU, cloud-image-utils) and a cloudalone service user;
  • the br-cloud bridge + IPv6 forwarding (with accept_ra=2 so the host keeps its default route) carrying your routed prefix;
  • on Dedibox, a DHCPv6-PD client that keeps the prefix routed;
  • the app (uv sync --extra libvirt), config + token, the debian-13 base image, and the systemd unit;
  • optionally, Caddy as a TLS reverse proxy.

1. Prerequisites

  • A Linux box with KVM and SSH access.
  • An IPv6 prefix routed to it: see IPv6 networking for how to get one on your provider.
  • For Dedibox specifically: order the free /48 and enable Activate IPv6 SLAAC in the console before deploying.

2. Configure the inventory

# deploy/inventory.py
hosts = [
    ("mybox", {
        "ssh_user": "root",
        "ipv6_prefix": "2001:db8:abcd:100::/64",   # the routed prefix
        "dhcpv6_duid": "00:03:00:01:…",             # Dedibox only; omit otherwise
        "api_host": "api.example.com",              # optional, enables TLS
    }),
]

Per-host settings (uplink, ipv6_prefix, dhcpv6_duid, bridge, backend) come from host.data; the bridge gateway is derived as <prefix>::1.

3. Smoke-test, then deploy

Before touching a real box, run the whole deploy against a throwaway systemd container (no KVM, but it exercises everything else):

./deploy/test-in-docker.sh        # builds a container, runs the deploy, checks the API → PASS

Then deploy for real:

uv run pyinfra deploy/inventory.py deploy/deploy.py

4. Verify

ssh mybox '
  systemctl is-active cloudalone
  systemctl is-active cloudalone-dhcpv6        # Dedibox only
  ip -6 addr show br-cloud                       # expect <prefix>::1/…
  ping6 -c2 2001:4860:4860::8888'                # host reaches the v6 internet

Then create a VM and SSH in (see Your first server).

TLS endpoint

When api_host is set, the deploy installs Caddy as a TLS reverse proxy (https://<api_host>/v1127.0.0.1:8000, auto Let's Encrypt) and binds cloudalone to localhost behind it.

DNS first

Add an A record for <api_host> pointing at the box's public IPv4 (and open ports 80/443) before deploying, so Caddy can complete the ACME challenge.

Persistence

VMs are set to autostart and the service reconciles DB state against libvirt on startup, so a host reboot brings VMs back and won't leave actions stuck at running. The bridge and forwarding persist via systemd-networkd + sysctl.d.