diff --git a/group_vars/dhcp.yml b/group_vars/dhcp.yml
index 409226f1d957a337317ce42b8488df8312c0b005..a05f810a747e8fa73e74d1cac40a51ca87a147d1 100644
--- a/group_vars/dhcp.yml
+++ b/group_vars/dhcp.yml
@@ -63,12 +63,23 @@ glob_dhcp:
           max: 100.65.255.254
       options: []
 
-glob_re2o_services:
-  server: re2o.adm.crans.org
-  service:
+glob_service_dhcp:
+  re2o:
+    hostname: "{{ query('ldap', 'ip', 're2o', 'adm') | ipv4 | first }}"
     user: services
     password: "{{ vault.re2o_service_password }}"
-  mail_server: "{{ glob_smtp }}"
-
-glob_re2o_dhcp:
-  uri: "https://gitlab.adm.crans.org/nounous/dhcp.git"
+  name: dhcp
+  install_dir: /var/local/services/dhcp
+  generated: yes
+  frequency: "*/2 * * * *"
+  options: -q
+  dependencies:
+    - python3-jinja2
+  git:
+    remote: https://gitlab.adm.crans.org/nounous/dhcp.git
+    version: master
+  config:
+    extensions:
+      - adh.crans.org
+      - adh-nat.crans.org
+      - infra.crans.org
diff --git a/plays/dhcp.yml b/plays/dhcp.yml
index 8426d0e5a28cf04ef1fc1f4c7e455736c621e33d..0edcea959a91846c572851604f919c9cd9af7272 100755
--- a/plays/dhcp.yml
+++ b/plays/dhcp.yml
@@ -4,9 +4,7 @@
 - hosts: dhcp
   vars:
     dhcp: "{{ glob_dhcp | default({}) | combine(loc_dhcp | default({})) }}"
-    re2o_services: "{{ glob_re2o_services | default({}) | combine(loc_re2o_services | default({})) }}"
-    re2o_dhcp: "{{ glob_re2o_dhcp | default({}) | combine(loc_re2o_dhcp | default({})) }}"
+    service: "{{ glob_service_dhcp | default({}) | combine(loc_service_dhcp | default({})) }}"
   roles:
     - isc-dhcp-server
-    - re2o-services
-    - re2o-dhcp
+    - service
diff --git a/roles/re2o-dhcp/tasks/main.yml b/roles/re2o-dhcp/tasks/main.yml
deleted file mode 100644
index 90f44669ebfcffa119c4c1187602789af10a79b7..0000000000000000000000000000000000000000
--- a/roles/re2o-dhcp/tasks/main.yml
+++ /dev/null
@@ -1,47 +0,0 @@
----
-- name: Create re2o-dhcp directory
-  file:
-    path: /var/local/re2o-services/dhcp
-    state: directory
-    mode: '2775'
-    owner: root
-    group: _nounou
-
-- name: Set ACL for re2o-dhcp directory
-  acl:
-    path: /var/local/re2o-services/dhcp
-    default: true
-    entity: _nounou
-    etype: group
-    permissions: rwx
-    state: query
-  when: not ansible_check_mode
-
-- name: Clone re2o-dhcp repository
-  git:
-    repo: "{{ re2o_dhcp.uri }}"
-    dest: /var/local/re2o-services/dhcp
-    version: crans
-    umask: '002'
-
-- name: Create symbolic link to configuration
-  file:
-    src: /var/local/re2o-services/config.ini
-    dest: /var/local/re2o-services/dhcp/config.ini
-    owner: root
-    group: root
-    state: link
-    force: yes
-
-- name: Create generated directory
-  file:
-    path: /var/local/re2o-services/dhcp/generated
-    state: directory
-    mode: 0755
-    owner: root
-    group: root
-
-- name: Deploy cron for re2o-dhcp
-  template:
-    src: cron.d/re2o-services-dhcp.j2
-    dest: /etc/cron.d/re2o-services-dhcp
diff --git a/roles/re2o-dhcp/templates/cron.d/re2o-services-dhcp.j2 b/roles/re2o-dhcp/templates/cron.d/re2o-services-dhcp.j2
deleted file mode 100644
index 0f47c5c9f4743ab0250b9d807a859eba6ca9ea91..0000000000000000000000000000000000000000
--- a/roles/re2o-dhcp/templates/cron.d/re2o-services-dhcp.j2
+++ /dev/null
@@ -1,2 +0,0 @@
-{{ ansible_header | comment }}
-* * * * * root /usr/bin/python3 /var/local/re2o-services/dhcp/main.py --force
diff --git a/roles/service/tasks/main.yml b/roles/service/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ec31e7de7e6004df105b4e92fa0ca51c1f6128de
--- /dev/null
+++ b/roles/service/tasks/main.yml
@@ -0,0 +1,62 @@
+---
+- name: Install service dependencies
+  apt:
+    update_cache: true
+    install_recommends: false
+    name: "{{ service.dependencies }}"
+  register: apt_result
+  retries: 3
+  until: apt_result is succeeded
+
+- name: Create service directory
+  file:
+    path: "{{ service.install_dir }}"
+    state: directory
+    mode: '2775'
+    owner: root
+    group: _nounou
+
+- name: Set ACL for service directory
+  acl:
+    path: "{{ service.install_dir }}"
+    default: true
+    entity: _nounou
+    etype: group
+    permissions: rwx
+    state: query
+
+- name: Clone service repository
+  git:
+    repo: "{{ service.git.remote }}"
+    version: "{{ service.git.version }}"
+    dest: "{{ service.install_dir }}"
+    umask: '002'
+
+- name: Create generated directory
+  file:
+    path: "{{ service.install_dir }}/generated"
+    state: directory
+    mode: '2770'
+    owner: root
+    group: _nounou
+  when: service.generated is defined and service.generated
+
+- name: Deploy re2o config
+  template:
+    src: service/re2o-config.ini.j2
+    dest: "{{ service.install_dir }}/re2o-config.ini"
+    mode: 0600
+    owner: root
+    group: root
+  when: service.re2o is defined
+
+- name: Deploy cron for service
+  template:
+    src: cron.d/service.j2
+    dest: "/etc/cron.d/services-{{ service.name }}"
+
+- name: Deploy service configuration
+  template:
+    src: service/config.json
+    dest: "{{ service.install_dir }}/{{ service.name }}.json"
+  when: service.config is defined
diff --git a/roles/service/templates/cron.d/service.j2 b/roles/service/templates/cron.d/service.j2
new file mode 100644
index 0000000000000000000000000000000000000000..eb5ae7b33af9b306f17e3e647772056c0cbd3e0e
--- /dev/null
+++ b/roles/service/templates/cron.d/service.j2
@@ -0,0 +1,2 @@
+{{ ansible_header | comment }}
+{{ service.frequency }} root /usr/bin/python3 {{ service.install_dir }}/{{ service.name }}.py {{ service.options }}
diff --git a/roles/service/templates/service/config.json b/roles/service/templates/service/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..8283990085f43d719e922468e073f66242a26bf0
--- /dev/null
+++ b/roles/service/templates/service/config.json
@@ -0,0 +1 @@
+{{ service.config | to_nice_json }}
diff --git a/roles/service/templates/service/re2o-config.ini.j2 b/roles/service/templates/service/re2o-config.ini.j2
new file mode 100644
index 0000000000000000000000000000000000000000..96962c2bf8c7365d03930b747a1b01fb4ccd3676
--- /dev/null
+++ b/roles/service/templates/service/re2o-config.ini.j2
@@ -0,0 +1,5 @@
+{{ ansible_header | comment(decoration='; ') }}
+[Re2o]
+hostname = {{ service.re2o.hostname }}
+username = {{ service.re2o.userĂ‚ }}
+password = {{ service.re2o.password }}