https://github.com/puetzp/ansible-role-hcloud-vm
A small Ansible role to set up VMs in the Hetzner Cloud using the rescue system and debootstrap
https://github.com/puetzp/ansible-role-hcloud-vm
ansible debian debootstrap hcloud trixie
Last synced: 28 days ago
JSON representation
A small Ansible role to set up VMs in the Hetzner Cloud using the rescue system and debootstrap
- Host: GitHub
- URL: https://github.com/puetzp/ansible-role-hcloud-vm
- Owner: puetzp
- Created: 2026-04-30T14:39:36.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-03T13:59:05.000Z (about 1 month ago)
- Last Synced: 2026-05-03T14:23:08.204Z (about 1 month ago)
- Topics: ansible, debian, debootstrap, hcloud, trixie
- Language: Jinja
- Homepage:
- Size: 29.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
ansible-role-hcloud-vm
======================
This [Ansible](https://docs.ansible.com/) role uses the Hetzner Cloud (hcloud) rescue system to install a fresh Debian 13 (trixie) operating system on the first disk of a new virtual machine (VM). It might appeal to you if you want a minimal Debian 13 operating system that only includes the packages you need instead of using general purpose Cloud Images.
At the time of writing the rescue system runs Debian 12 (bookworm) in-memory and serves as a rich installation environment featuring all necessary tools to bootstrap Debian 13 on the primary disk using [`debootstrap`](https://manpages.debian.org/trixie/debootstrap/debootstrap.8.en.html). Hetzner also provides [package mirrors](https://docs.hetzner.com/robot/dedicated-server/operating-systems/hetzner-package-mirror) that are used during installation.
Since the role installs a fresh Debian 13 OS inside the rescue system of a new VM, it also goes further in configuring the new environment and sets up networking, the correct hostname etc. Cloud Images typically use `cloud-init` to achieve this, which is not needed in this setup, because Debian is set up from the ground up inside the VM it is supposed to run in, enabling the role to customize the environment in the same manner without extra tooling.
New VMs are created with a IPv6 address only. The role uses WireGuard to establish a VPN connection between the "deployment server" (from which the role is executed) and the VM. Inside the VPN IPv4 is used.
As you can see the role is quite opinionated and *not designed to be general-purpose*. If you are interested in trying it out, it is probably best to clone the repository and adjust it to your needs. Some of the specifics include:
- LVM on the secondary partition to use distinct mount points for some directories (e.g. `/var`)
- `ifupdown` for networking which assigns the VMs IPv6 address to the first interface
- a `wg0` interface for WireGuard using a private IPv4 address
- a hostname and correct `/etc/hosts` using the private IPv4 address
- separate user `ansible` for further configuration down the road
- initial IPv6 nameserver entry in `/etc/resolv.conf`
- Hetzner package mirrors in `/etc/apt/sources.list`
- secure `sshd_config` listening on the `wg0` IPv4 address only
The SSH host keys of the new host as well as the WireGuard public key are added to the deployment server. So after rebooting into the new OS you have a secure SSH connection (avoiding the trust-on-first-use problem) via WireGuard and no bots desperately trying to log in.
This role runs Ansible tasks on two types of nodes:
- the deployment server (`localhost`) that initiates the play and adjusts the local WireGuard interface configuration (`wghcloud`)
- the rescue system of a fresh VM that is created by the role and is used to bootstrap Debian 13 on the primary disk
Preparation
-----------
Install `hcloud-cli` as described in [setting up hcloud](https://github.com/hetznercloud/cli/blob/main/docs/tutorials/setup-hcloud-cli.md).
The context is set up by the Ansible role. You will have to create an Access Token however and pass it to Ansible via the inventory (see below).
VM Installation
---------------
The Ansible role runs in the context of the new host, but performs some tasks on `localhost` via `delegate_to`. The following example configuration assumes that your playbooks are located in `$HOME/ansible`.
```yml
# $HOME/ansible/host_vars/dns.hcloud.internal.yml
---
ansible_host: 172.16.1.2
hcloud_type: cx23
hcloud_location: nbg1
```
```yml
# $HOME/ansible/group_vars/hcloud.yml
---
ansible_connection: local
# Context used by hcloud-cli in which to interact with the API.
hcloud_context: default
# Token associated with this context used to authenticate to the API.
# Use `--ask-vault-pass` with `ansible-playbook` if you choose to
# encrypt this variable.
hcloud_token: !vault |
# The name of the SSH key that you added to your hcloud account.
# This is injected into the rescue system to bootstrap the VM via
# Ansible.
hcloud_ssh_key_name: me
# Your SSH public key to be injected into the bootstrapped VM.
hcloud_ssh_key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
# Common domain used to configure the fully-qualified domain names of all VMs.
hcloud_domain: hcloud.internal
# Encrypted password for user 'ansible'. For example this is the hash of
# the string `foobar`, generated via `openssl passwd -6`.
hcloud_password: $6$E9akWI2zpi.biv1g$YHGfQ2dzfmFsAbEpX/j7t22K5qqJTevm3XfslT4GiI9GYdsE8lZeFWpgal8D66sLr.r2Y/tppnhFNxFTc4o7V.
# Use quad9 DNS server to facilitate further bootstrapping, package
# installation etc. after the VM is ready.
hcloud_dns: 2620:fe::fe
# The IP address of your WireGuard client.
hcloud_wireguard_ip: 172.16.1.1/32
```
Use the following example playbook to install the VMs:
```yml
# $HOME/ansible/deploy-vms.yml
---
- hosts: hcloud
gather_facts: false
roles:
- role: ../ansible-role-hcloud-vm
```
```sh
ansible-playbook -K deploy-vms.yml
```
> Note that a sudo password might need to be passed to Ansible via the `-K` parameter, because the tasks running on `localhost` require root privileges to configure the local WireGuard interface `wghcloud`.
In this example the result of the play is a single VM accessible via SSH tunneled through WireGuard:
```sh
ssh ansible@172.16.1.2
```
Inspiration
-----------
- [FAI](https://fai-project.org/)
- [How to setup the Debian Linux image from scratch](https://mvallim.github.io/kubernetes-under-the-hood/documentation/create-linux-image.html)
Contribution
------------
This is a personal project and I enjoy sharing the work. However it is not meant to be a community project. You are welcome to fork it and make it your own!