diff --git a/README.md b/README.md
index d0af5e054d0b36c35129b17999cee273d2b72c3c..31552c192b5f6a1c2032f8b606062d54c55a41f7 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,67 @@ hepto -iface eth0 -name myfull -info > cluster-info.yaml
 helm install hepto ./helm -f cluster-info.yaml
 ```
 
+## Deploying a cluster on many nodes using Ansible
+
+This repository provides an ansible role for deploying hepto on a node. Start with an
+inventory file listing your nodes, and providing some variables, for instance:
+
+```
+---
+nodes:
+  hosts:
+    # Each host gets an entry
+    riri:
+      # Override the host IP if the node name does not resolve
+      ansible_host: "2a01:dead:beef::1"
+      # Provide an explicit node IP address (-ip)
+      node_ip: "2a01:dead:beef::101/64"
+    fifi:
+      ansible_host: "2a01:dead:beef::2"
+    loulou:
+      ansible_host: "2a01:dead:beef::3"
+      # Default role is node, explicitely set the master role here
+      node_role: master
+  vars:
+    # Copy the static node ip of any stable node (usually the master, but any node
+    # will do)
+    cluster_anchor: "2a01:dead:beef::101"
+    # This must be 64 hex randomly generated
+    cluster_key: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+    # You may specify any option for all nodes, here a network gateway
+    node_gw: "fe80::1"
+```
+
+Then run Ansible:
+
+```
+ansible-playbook -i inventory.yaml ansible/deploy.yaml
+```
+
+Or if you wish to use cloud provisioning for deploying the nodes in the first place
+(currently supporting Hetzner only):
+
+```
+---
+all:
+  vars:
+    nodes:
+      riri:
+      fifi:
+      loulou:
+        node_role: master
+    cluster_anchor: loulou
+    cluster_key: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+    hcloud_token: YourHCloudToken
+    hcloud_ssh_key: yourkey@host
+```
+
+Then run Ansible:
+
+```
+ansible-playbook -i inventory.yaml ansible/cloud.yaml
+```
+
 ## Development
 
 Hepto is being developped as part of an ongoing effort to provide decent
diff --git a/ansible/cloud.yaml b/ansible/cloud.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f6f0d03cc81e5ac33f3eb96196b2f4675f3aceb9
--- /dev/null
+++ b/ansible/cloud.yaml
@@ -0,0 +1,7 @@
+---
+- hosts: localhost
+  roles:
+  - hetzner
+- hosts: nodes
+  roles:
+  - hepto
diff --git a/ansible/deploy.yaml b/ansible/deploy.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..866b1d3a51ac2b3cda07c08c3064824ee3024617
--- /dev/null
+++ b/ansible/deploy.yaml
@@ -0,0 +1,4 @@
+---
+- hosts: all
+  roles:
+  - hepto
diff --git a/ansible/roles/hepto/defaults/main.yaml b/ansible/roles/hepto/defaults/main.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0fe4639aa910fdb540a54f13ee946cd40e83278f
--- /dev/null
+++ b/ansible/roles/hepto/defaults/main.yaml
@@ -0,0 +1,15 @@
+# Hepto install
+version: 78680
+hepto_url: "https://forge.tedomum.net/acides/hepto/-/jobs/{{ version }}/artifacts/raw/hepto"
+hepto_bin: /usr/local/bin/hepto.{{ version }}
+
+# Hepto general config
+systemd_dir: /etc/systemd/system
+storage_dir: /var/lib
+cluster_name: hepto
+cluster_anchor: "::1"
+node_name: "{{ inventory_hostname }}"
+node_iface: eth0
+node_role: node
+config_file: "/etc/{{ cluster_name }}/{{ node_name }}"
+
diff --git a/ansible/roles/hepto/tasks/main.yaml b/ansible/roles/hepto/tasks/main.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..274fba62caa8eefe7b4a1dc0acd030a328abe914
--- /dev/null
+++ b/ansible/roles/hepto/tasks/main.yaml
@@ -0,0 +1,31 @@
+---
+- name: Create required directories
+  file:
+    path: "{{ item }}"
+    state: directory
+  with_items:
+    - "/etc/{{ cluster_name }}"
+    - "/usr/local/bin"
+- name: Download hepto binary for amd64
+  get_url:
+    url: "{{ hepto_url }}"
+    dest: "{{ hepto_bin }}"
+    owner: root
+    group: root
+    mode: 755
+- name: Install hepto service file
+  template:
+    src: service.j2
+    dest: "{{ systemd_dir }}/hepto-{{ node_name }}.service"
+- name: Install hepto config file
+  template:
+    src: config.j2
+    dest: "{{ config_file }}"
+- name: Enable hepto service
+  systemd:
+    name: "hepto-{{ node_name }}"
+    daemon_reload: yes
+    state: restarted
+    enabled: yes
+
+
diff --git a/ansible/roles/hepto/templates/config.j2 b/ansible/roles/hepto/templates/config.j2
new file mode 100644
index 0000000000000000000000000000000000000000..4a44eefc940af613c32ab8abd052c6daa1945aef
--- /dev/null
+++ b/ansible/roles/hepto/templates/config.j2
@@ -0,0 +1,15 @@
+HEPTO_CLUSTER={{ cluster_name }}
+HEPTO_KEY={{ cluster_key }}
+HEPTO_IFACE={{ node_iface }}
+HEPTO_ROLE={{ node_role }}
+{% if cluster_anchor in hostvars %}
+HEPTO_ANCHOR={{ hostvars[cluster_anchor]['node_ip'] | ansible.utils.ipaddr('address') }}
+{% else %}
+HEPTO_ANCHOR={{ cluster_anchor }}
+{% endif %}
+{% if node_ip is defined %}
+HEPTO_IP={{ node_ip }}
+{% endif %}
+{% if node_gw is defined %}
+HEPTO_GW={{ node_gw }}
+{% endif %}
diff --git a/ansible/roles/hepto/templates/service.j2 b/ansible/roles/hepto/templates/service.j2
new file mode 100644
index 0000000000000000000000000000000000000000..f11ab0eb59da958682e3291d3748abb72657847e
--- /dev/null
+++ b/ansible/roles/hepto/templates/service.j2
@@ -0,0 +1,17 @@
+[Unit]
+Description=hepto node {{ node_name }}
+Documentation=https://acides.org
+Wants=network-online.target
+
+[Install]
+WantedBy=multi-user.target
+
+[Service]
+EnvironmentFile={{ config_file }}
+Delegate=yes
+LimitNOFILE=infinity
+LimitNPROC=infinity
+LimitCORE=infinity
+TasksMax=infinity
+Type=exec
+ExecStart={{ hepto_bin }} -name {{ node_name }}
\ No newline at end of file
diff --git a/ansible/roles/hetzner/defaults/main.yaml b/ansible/roles/hetzner/defaults/main.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..50c2ea72101811b0639ed2722cc85682ed8408b5
--- /dev/null
+++ b/ansible/roles/hetzner/defaults/main.yaml
@@ -0,0 +1,4 @@
+server_type: cx11
+server_image: debian-12
+server_location: eu-central
+
diff --git a/ansible/roles/hetzner/tasks/main.yaml b/ansible/roles/hetzner/tasks/main.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..959a3c98cca84f704269044d48ec7916ec74a48f
--- /dev/null
+++ b/ansible/roles/hetzner/tasks/main.yaml
@@ -0,0 +1,39 @@
+- name: "Create nodes"
+  hcloud_server:
+    api_token: "{{ hcloud_token }}"
+    name: "node-{{ item.key }}"
+    server_type: "{{ server_type }}"
+    image: "{{ server_image }}"
+    location: "{{ server_location }}"
+    ssh_keys:
+    - "{{ hcloud_ssh_key }}"
+    enable_ipv6: true
+    enable_ipv4: false
+    state: started
+  register: servers
+  with_dict: "{{ nodes }}"
+
+- name: Wait for nodes to be ready
+  ansible.builtin.wait_for:
+    port: 22
+    host: "{{ item.hcloud_server.ipv6 | split('/') | first }}1"
+    delay: 2
+  with_items: "{{  servers.results }}"
+
+# This is required as an intermediary step to compute the node
+# configuration to be added to inventory
+- name: "Prepare node configs"
+  set_fact:
+    name: "{{ item.item.key }}"
+    groups: nodes
+    ansible_host: "{{ item.hcloud_server.ipv6 | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('1') | ansible.utils.ipaddr('address') }}"
+    ansible_user: root
+    node_gw: "fe80::1"
+    node_ip: "{{ item.hcloud_server.ipv6 | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('2') }}"
+    ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
+  register: configs
+  with_items: "{{ servers.results }}"
+
+- name: "Add nodes to inventory"
+  add_host: "{{ item.ansible_facts | combine(nodes[item.ansible_facts.name]) }}"
+  with_items: "{{ configs.results }}"