diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b88527bc42b9163e0265bf05cebb842bf765326e..fdd013414fe837347159cafede7f562cc68c538a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -71,4 +71,18 @@ test:
   script:
     - make test
 
+test-ha-pgsql:
+  image: registry.ubicast.net/mediaserver/envsetup:root
+  stage: test
+  tags:
+    - docker
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "web"'
+    - if: '$CI_PIPELINE_SOURCE == "merge_requests"'
+    - if: '$CI_PIPELINE_SOURCE == "push"'
+      changes:
+        - ansible/**/*
+  script:
+    - make test ha-pgsql=1
+
 ...
diff --git a/Makefile b/Makefile
index d095bdfb9a6d9ec642b3e7c66c0468aeeaa3bc29..dfba16e865d1a440423fe0d8366ca66a8918bfc3 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,9 @@ endif
 ifdef keep
 	MOLECULE_TEST_FLAGS += --destroy=never --parallel
 endif
+ifdef ha-pgsql
+	MOLECULE_TEST_FLAGS += --scenario-name ha-pgsql
+endif
 
 .PHONY: all
 ## TARGET: DESCRIPTION: ARGS
@@ -58,7 +61,7 @@ lint:
 	ANSIBLE_CONFIG=$(ANSIBLE_CONFIG) $(ANSIBLE_LINT_BIN) ansible/playbooks/site.yml
 
 .PHONY: test
-## test: Run development tests on the project : debug=1, keep=1, SKYREACH_SYSTEM_KEY=<xxx>
+## test: Run development tests on the project : debug=1, keep=1, SKYREACH_SYSTEM_KEY=<xxx>, ha-pgsql=1
 test:
 ifndef SKYREACH_SYSTEM_KEY
 	$(error SKYREACH_SYSTEM_KEY is undefined)
diff --git a/ansible/molecule/default/molecule.yml b/ansible/molecule/default/molecule.yml
index 97d2cd3968781fdcfb75965dbc11c8cb63ce69d4..d63f4715dfff22585692d6ea5e9dd98cfb712f22 100644
--- a/ansible/molecule/default/molecule.yml
+++ b/ansible/molecule/default/molecule.yml
@@ -24,6 +24,8 @@ platforms:
       - netcapture
 provisioner:
   name: ansible
+  options:
+    D: true
   env:
     ANSIBLE_ROLES_PATH: ../../roles
     ANSIBLE_LIBRARY: ../../library
diff --git a/ansible/molecule/ha-pgsql/converge.yml b/ansible/molecule/ha-pgsql/converge.yml
new file mode 100644
index 0000000000000000000000000000000000000000..58ea8718716da92a4a5e63aa39d7ef016c1bc3bd
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/converge.yml
@@ -0,0 +1,40 @@
+#!/usr/bin/env ansible-playbook
+---
+
+- name: PYTHON
+  hosts: all
+  gather_facts: false
+  tasks:
+    - name: ensure python3 is installed
+      register: python_install
+      changed_when: "'es_pyinstall' in python_install.stdout_lines"
+      raw: command -v python3 || echo es_pyinstall && apt update && apt install -y python3-minimal python3-apt
+
+- name: Converge
+  hosts: postgres
+  pre_tasks:
+    - name: check running in a docker container
+      register: check_if_docker
+      stat:
+        path: /.dockerenv
+    - name: set docker flag variable
+      set_fact:
+        in_docker: "{{ check_if_docker.stat.exists | d(false) }}"
+  roles:
+    - base
+    - postgres-ha
+  post_tasks:
+    - name: deploy letsencrypt certificate
+      when: letsencrypt_enabled | d(false)
+      include_role:
+        name: letsencrypt
+    - name: configure network
+      when: network_apply | d(false)
+      include_role:
+        name: network
+    - name: configure proxy
+      when: proxy_apply | d(false)
+      include_role:
+        name: proxy
+
+...
diff --git a/ansible/molecule/ha-pgsql/molecule.yml b/ansible/molecule/ha-pgsql/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7dc2309583092056804d3cba5f3df7b27f962096
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/molecule.yml
@@ -0,0 +1,56 @@
+---
+driver:
+  name: docker
+platforms:
+  - name: db0-default
+    image: registry.ubicast.net/docker/debian-systemd:buster
+    command: /lib/systemd/systemd
+    privileged: true
+    volumes:
+      - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    tmpfs:
+      - /tmp
+      - /run
+    groups:
+      - postgres
+  - name: db1-default
+    image: registry.ubicast.net/docker/debian-systemd:buster
+    command: /lib/systemd/systemd
+    privileged: true
+    volumes:
+      - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    tmpfs:
+      - /tmp
+      - /run
+    groups:
+      - postgres
+  - name: db2-default
+    image: registry.ubicast.net/docker/debian-systemd:buster
+    command: /lib/systemd/systemd
+    privileged: true
+    volumes:
+      - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    tmpfs:
+      - /tmp
+      - /run
+    groups:
+      - postgres
+provisioner:
+  name: ansible
+  options:
+    D: true
+  inventory:
+    group_vars:
+      postgres:
+        repmgr_password: "testrepmgr"
+  env:
+    ANSIBLE_ROLES_PATH: ../../roles
+    ANSIBLE_LIBRARY: ../../library
+    ANSIBLE_ACTION_PLUGINS: ../../plugins/action
+    ANSIBLE_PYTHON_INTERPRETER: /usr/bin/python3
+    SKYREACH_SYSTEM_KEY: s1121eb6e7593525bf3e0302586c82d2
+
+verifier:
+  name: testinfra
+  options:
+    verbose: true
diff --git a/ansible/molecule/ha-pgsql/tests/commons.py b/ansible/molecule/ha-pgsql/tests/commons.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7318b13789ba836201d1f683dc0aed4f4fc9937
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/commons.py
@@ -0,0 +1,9 @@
+import socket
+
+
+def get_status(host):
+    ip = host.interface('eth0').addresses[0]
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect((ip, 8543))
+    data = s.recv(1024)
+    return data.rstrip().decode('utf-8')
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_a_setup.py b/ansible/molecule/ha-pgsql/tests/test_postgres_a_setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a2adcbbbac8685d89bbce661941943250336bb6
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_a_setup.py
@@ -0,0 +1,39 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+# /!\ This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
+    os.environ["MOLECULE_INVENTORY_FILE"]
+).get_hosts("postgres")
+
+
+def test_psycopg2_is_installed(host):
+    p = host.package("python3-psycopg2")
+
+    assert p.is_installed
+
+
+def test_postgres_is_installed(host):
+    p = host.package("postgresql-11")
+
+    assert p.is_installed
+    assert p.version.startswith("11")
+
+
+def test_postgres_user(host):
+    u = host.user("postgres")
+
+    assert u.name == "postgres"
+
+
+def test_postgres_service(host):
+    s = host.service("postgresql@11-main")
+
+    assert s.is_running
+
+
+def test_postgresql_socket(host):
+    s = host.socket("tcp://127.0.0.1:5432")
+
+    assert s.is_listening
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_b_cluster_status.py b/ansible/molecule/ha-pgsql/tests/test_postgres_b_cluster_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7de4bd3aad37a485af73f44f98109d661886d32
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_b_cluster_status.py
@@ -0,0 +1,22 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+import commons
+
+# This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("postgres")
+
+
+def test_postgresql_check_repmgr_status(host):
+    ''' check if repmgr is working correctly on each node'''
+
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db0-default"):
+        data = commons.get_status(host)
+        assert data == "primary"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db1-default"):
+        data = commons.get_status(host)
+        assert data == "standby"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db2-default"):
+        data = commons.get_status(host)
+        assert data == "witness"
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_c_test_cluster_primary.py b/ansible/molecule/ha-pgsql/tests/test_postgres_c_test_cluster_primary.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9a391fb77277cb549867ba0db5c8a65e62b62bf
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_c_test_cluster_primary.py
@@ -0,0 +1,27 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+# This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("db0-default")
+
+
+def test_postgresql_create_db(host):
+    ''' check if we can only create db on the primary node of the cluster '''
+
+    s = host.ansible("postgresql_db", "name=test", become=True, check=False, become_user='postgres')
+    assert s["changed"]
+
+
+def test_postgresql_create_table(host):
+    ''' check if we can only create a table on the primary node of the cluster '''
+
+    s = host.ansible("postgresql_query", "db=test query='CREATE TABLE test_ha (id SERIAL PRIMARY KEY, name VARCHAR(100) );'", become=True, check=False, become_user='postgres')
+    assert s["changed"]
+
+
+def test_postgresql_insert(host):
+    ''' check if we can only write to the primary node of the cluster '''
+
+    s = host.ansible("postgresql_query", "db=test query='INSERT INTO test_ha (name) VALUES (\'test\');'", become=True, check=False, become_user='postgres')
+    assert s["changed"]
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_d_test_cluster_secondary.py b/ansible/molecule/ha-pgsql/tests/test_postgres_d_test_cluster_secondary.py
new file mode 100644
index 0000000000000000000000000000000000000000..981b98be92ad96bc1575914b11d969f9eb71f7a9
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_d_test_cluster_secondary.py
@@ -0,0 +1,27 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+# This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("db1-default")
+
+
+def test_postgresql_create_db(host):
+    ''' check if we can only create db on the primary node of the cluster '''
+
+    s = host.ansible("postgresql_db", "name=test", become=True, check=False, become_user='postgres')
+    assert not s["changed"]
+
+
+def test_postgresql_create_table(host):
+    ''' check if we can only create a table on the primary node of the cluster '''
+
+    s = host.ansible("postgresql_query", "db=test query='CREATE TABLE test_ha (id SERIAL PRIMARY KEY, name VARCHAR(100) );'", become=True, check=False, become_user='postgres')
+    assert not s["changed"]
+
+
+def test_postgresql_insert(host):
+    ''' check if we can only write to the primary node of the cluster '''
+
+    s = host.ansible("postgresql_query", "db=test query='INSERT INTO test_ha (name) VALUES (\'test\');'", become=True, check=False, become_user='postgres')
+    assert not s["changed"]
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_e_shutdown_primary.py b/ansible/molecule/ha-pgsql/tests/test_postgres_e_shutdown_primary.py
new file mode 100644
index 0000000000000000000000000000000000000000..df5b3a51d096463ac4259b7d907374dfd9916788
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_e_shutdown_primary.py
@@ -0,0 +1,20 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+import time
+
+
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("db0-default")
+
+
+def test_postgresql_check_shutdown_primary(host):
+    ''' Shutdown the primary server '''
+
+    s = host.ansible("command", "systemctl stop postgresql", become=True, check=False)
+    assert s['changed']
+
+    time.sleep(40)
+
+    s = host.socket("tcp://127.0.0.1:5432")
+    assert not s.is_listening
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_f_new_primary_cluster_status.py b/ansible/molecule/ha-pgsql/tests/test_postgres_f_new_primary_cluster_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f9c8edb95a0391f2703739aad596b9042e2c391
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_f_new_primary_cluster_status.py
@@ -0,0 +1,22 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+import commons
+
+# /!\ This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("postgres")
+
+
+def test_postgresql_check_repmgr_new_master(host):
+    ''' check repmgr status for each node after new master election '''
+
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db0-default"):
+        data = commons.get_status(host)
+        assert data == "fenced"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db1-default"):
+        data = commons.get_status(host)
+        assert data == "primary"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db2-default"):
+        data = commons.get_status(host)
+        assert data == "witness"
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_g_new_primary_write.py b/ansible/molecule/ha-pgsql/tests/test_postgres_g_new_primary_write.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ff31ac0b54b59129843c42a055af19c957170db
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_g_new_primary_write.py
@@ -0,0 +1,11 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("db1-default")
+
+
+def test_postgresql_insert_new_master(host):
+    s = host.ansible("postgresql_query", "db=test query='INSERT INTO test_ha (name) VALUES (\'test2\');'", become=True, check=False, become_user='postgres')
+    assert s["changed"]
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_h_reintegrate_server.py b/ansible/molecule/ha-pgsql/tests/test_postgres_h_reintegrate_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..e61ff3dcd483e86d6ef8e36fb0ed5841627e8476
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_h_reintegrate_server.py
@@ -0,0 +1,39 @@
+import os
+
+import testinfra.utils.ansible_runner
+import time
+
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("db0-default")
+
+
+def test_postgresql_delete_data(host):
+    ''' delete data directory '''
+
+    s = host.ansible("command", "rm -rf /var/lib/postgresql/11/main/", become=True, check=False)
+    assert s['changed']
+
+
+def test_postgresql_launch_repmgr_sync(host):
+    ''' sync data with primary server using repmgr '''
+
+    current_master = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_host("db1-default")
+    current_master_ip = current_master.interface('eth0').addresses[0]
+    rep_mgr_command = "repmgr -f /etc/postgresql/11/main/repmgr.conf --force --verbose standby clone -h " + current_master_ip + " -d repmgr -U repmgr -c"
+    s = host.ansible("command", rep_mgr_command, become=True, become_user='postgres', check=False)
+    assert s['changed']
+
+
+def test_postgresql_start_postgresql(host):
+    ''' start postgresql '''
+
+    s = host.ansible("command", "systemctl start postgresql", become=True, check=False)
+    time.sleep(20)
+    assert s['changed']
+
+
+def test_pogresql_register_as_standby(host):
+    ''' register server as standby in repmgr '''
+
+    s = host.ansible("command", "repmgr -f /etc/postgresql/11/main/repmgr.conf --force --verbose standby register", become=True, become_user='postgres', check=False)
+    time.sleep(20)
+    assert s['changed']
diff --git a/ansible/molecule/ha-pgsql/tests/test_postgres_i_final_cluster_status.py b/ansible/molecule/ha-pgsql/tests/test_postgres_i_final_cluster_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..1956ddf37fc56ed9363c9862eb0a8f71bcc3c14a
--- /dev/null
+++ b/ansible/molecule/ha-pgsql/tests/test_postgres_i_final_cluster_status.py
@@ -0,0 +1,22 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+import commons
+
+# /!\ This test run accross all servers
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(os.environ["MOLECULE_INVENTORY_FILE"]).get_hosts("postgres")
+
+
+def test_postgresql_check_status_after_shutdown(host):
+    ''' check repmgr status accross server after primary change and server reintegration '''
+
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db0-default"):
+        data = commons.get_status(host)
+        assert data == "standby"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db1-default"):
+        data = commons.get_status(host)
+        assert data == "primary"
+    if host.ansible.get_variables()["inventory_hostname"].startswith("db2-default"):
+        data = commons.get_status(host)
+        assert data == "witness"
diff --git a/ansible/playbooks/postgres-maintenance.yml b/ansible/playbooks/postgres-maintenance.yml
new file mode 100755
index 0000000000000000000000000000000000000000..80ae73554800a8ef472cb2bde533dd6f5631648f
--- /dev/null
+++ b/ansible/playbooks/postgres-maintenance.yml
@@ -0,0 +1,70 @@
+#!/usr/bin/env ansible-playbook
+---
+
+- name: GATHER FACTS
+  hosts: postgres_primary:postgres_standby:postgres_fenced
+  tags: always
+  tasks:
+    - name: get cluster state
+      command: "rephacheck"
+      register: rephacheck
+    - name: show status for each node
+      debug:
+        msg: "Current node {{ ansible_hostname }} status {{ rephacheck['stdout'] }}"
+      when: rephacheck['stdout'] != ""
+
+- name: POSTGRESQL SWITCH CURRENT STANDBY TO PRIMARY
+  hosts: postgres_standby
+  tags: [ 'never', 'standby-to-primary' ]
+  tasks:
+    - name: fail if node status if not standby
+      fail:
+        msg: "Current status {{ rephacheck['stdout'] }} must be standby."
+      when: rephacheck['stdout'] != "standby"
+    - name: check if node is currently in standby
+      command: "repmgr standby switchover -f /etc/postgresql/11/main/repmgr.conf --siblings-follow --dry-run"
+      become: yes
+      become_user: postgres
+      when: rephacheck['stdout'] == "standby"
+      register: standby_dry_run
+    - name: switch standby node to primary
+      command: "repmgr standby switchover -f /etc/postgresql/11/main/repmgr.conf --siblings-follow"
+      become: yes
+      become_user: postgres
+      when:
+        - standby_dry_run is succeeded
+        - rephacheck['stdout'] == "standby"
+
+- name: POSTGRESQL SWITCH CURRENT FENCED TO STANDBY
+  hosts: postgres_fenced
+  tags: [ 'never' , 'fenced-to-standby' ]
+  tasks:
+    - name: fail if node status if not fenced
+      fail:
+        msg: "Current status {{ rephacheck['stdout'] }} must be fenced."
+      when: rephacheck['stdout'] != "fenced"
+    - name: stop postgresql
+      systemd:
+        name: postgresql
+        state: stopped
+    - name: delete postgresql data directory
+      file:
+        path: /var/lib/postgresql/11/main/
+        state: absent
+        force: yes
+    - name: copy data from primary
+      command: "repmgr -f /etc/postgresql/11/main/repmgr.conf --force --verbose standby clone -h {{ hostvars[groups['postgres_primary'][0]]['ansible_default_ipv4']['address'] }} -d repmgr -U repmgr -c"
+      become: yes
+      become_user: postgres
+      register: copy_from_primary
+    - name: start postgresql
+      systemd:
+        name: postgresql
+        state: started
+      when: copy_from_primary is succeeded
+    - name: register node as standby
+      command: "repmgr -f /etc/postgresql/11/main/repmgr.conf --force --verbose standby register"
+      become: yes
+      become_user: postgres
+      when: copy_from_primary is succeeded
+...
diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml
index e86b5c07195cb3df11c576dcc2fca3d9de5b8273..64c4184a7e39c53e35b8cb4e647b50ae0f9355d8 100755
--- a/ansible/playbooks/site.yml
+++ b/ansible/playbooks/site.yml
@@ -8,7 +8,7 @@
     - name: ensure python3 is installed
       register: python_install
       changed_when: "'es_pyinstall' in python_install.stdout_lines"
-      raw: command -v python3 || echo es_pyinstall && apt update && apt install -y python3-minimal python3-apt
+      raw: command -v python3 || echo es_pyinstall && apt update && apt install -y python3-minimal python3-apt iproute2
       tags: always
 - import_playbook: "{{ 'postgres-ha' if groups['postgres']|d('') | length > 1 else 'postgres' }}.yml"
   tags: postgres
diff --git a/ansible/playbooks/site_docker.yml b/ansible/playbooks/site_docker.yml
new file mode 100644
index 0000000000000000000000000000000000000000..daf8889e44443733721831ac6b3cea5486668a80
--- /dev/null
+++ b/ansible/playbooks/site_docker.yml
@@ -0,0 +1,33 @@
+---
+- name: DOCKER CONTAINERS PROVISIONING
+  hosts: localhost
+  connection: local
+  tags: always
+  tasks:
+    - name: Create docker containers from inventory
+      docker_container:
+        name: "{{ item }}"
+        image: registry.ubicast.net/docker/debian-systemd:buster
+        privileged: true
+        command: /lib/systemd/systemd
+        state: started
+        volumes:
+          - /sys/fs/cgroup:/sys/fs/cgroup:ro
+        tmpfs:
+          - /tmp
+          - /run
+      with_inventory_hostnames:
+        - all:!localhost
+
+    - name: add host to inventory
+      add_host:
+        name: "{{ item }}"
+        ansible_host: "{{ item }}"
+        ansible_connection: docker
+        ansible_python_interpreter: /usr/bin/python3
+      with_inventory_hostnames:
+        - all:!localhost
+
+- import_playbook: site.yml
+
+...
diff --git a/ansible/requirements.dev.in b/ansible/requirements.dev.in
index a5e18ddbce783e11e723c9821e1a20695325550d..b07ede02b94ad7945f410b6d164a552c4e79caf1 100644
--- a/ansible/requirements.dev.in
+++ b/ansible/requirements.dev.in
@@ -1,7 +1,8 @@
 -r requirements.in
 ansible-lint
 flake8
-molecule[docker]
+git+git://github.com/atmaniak/molecule@e03437923b302fca1bd7b4f6030c6956ad00367a#egg=molecule[docker]
+#molecule[docker]
 pip-tools
 testinfra
 yamllint
diff --git a/ansible/requirements.dev.txt b/ansible/requirements.dev.txt
index 85866dba3d6dcc80ba49a9a4caada2cfb177a211..77d9cc012d5dae6357589f23c492f55561819cbb 100644
--- a/ansible/requirements.dev.txt
+++ b/ansible/requirements.dev.txt
@@ -27,12 +27,11 @@ fasteners==0.15           # via python-gilt
 flake8==3.7.9             # via -r requirements.dev.in
 future==0.18.2            # via cookiecutter
 idna==2.9                 # via requests
-importlib-metadata==1.6.0  # via pluggy, pytest
 jinja2-time==0.2.0        # via cookiecutter
 jinja2==2.11.2            # via ansible, click-completion, cookiecutter, jinja2-time, molecule
 markupsafe==1.1.1         # via jinja2
 mccabe==0.6.1             # via flake8
-molecule[docker]==3.0.3   # via -r requirements.dev.in
+git+git://github.com/atmaniak/molecule@e03437923b302fca1bd7b4f6030c6956ad00367a#egg=molecule[docker]  # via -r requirements.dev.in
 monotonic==1.5            # via fasteners
 more-itertools==8.2.0     # via pytest
 netaddr==0.7.19           # via -r requirements.in
@@ -58,7 +57,7 @@ requests==2.23.0          # via cookiecutter, docker
 ruamel.yaml.clib==0.2.0   # via ruamel.yaml
 ruamel.yaml==0.16.10      # via ansible-lint
 selinux==0.2.1            # via molecule
-sh==1.12.14               # via molecule, python-gilt
+sh==1.13.1                # via molecule, python-gilt
 shellingham==1.3.2        # via click-completion
 six==1.14.0               # via ansible-lint, bcrypt, click-completion, cryptography, docker, fasteners, packaging, pip-tools, pynacl, python-dateutil, websocket-client
 tabulate==0.8.7           # via molecule
@@ -69,7 +68,6 @@ wcwidth==0.1.9            # via pytest
 websocket-client==0.57.0  # via docker
 whichcraft==0.6.1         # via cookiecutter
 yamllint==1.22.1          # via -r requirements.dev.in, molecule
-zipp==3.1.0               # via importlib-metadata
 
 # The following packages are considered to be unsafe in a requirements file:
 # pip
diff --git a/ansible/roles/ferm-configure/handlers/main.yml b/ansible/roles/ferm-configure/handlers/main.yml
index 1df71e19ab322054f7a1de2cf01b075a5a4749a8..920efd0e606e42d3ad642e2cc27a7f93fa5d29f6 100644
--- a/ansible/roles/ferm-configure/handlers/main.yml
+++ b/ansible/roles/ferm-configure/handlers/main.yml
@@ -1,9 +1,9 @@
 ---
 
-- name: reload ferm
+- name: restart ferm
   when: ansible_facts.services['ferm.service'] is defined
   systemd:
     name: ferm
-    state: reloaded
+    state: restarted
 
 ...
diff --git a/ansible/roles/ferm-configure/tasks/main.yml b/ansible/roles/ferm-configure/tasks/main.yml
index c09fe079df02e741b4523e19065903b2e0eb44e4..ac28a7e94ee161bd67b4bb6a330569f487740f15 100644
--- a/ansible/roles/ferm-configure/tasks/main.yml
+++ b/ansible/roles/ferm-configure/tasks/main.yml
@@ -15,28 +15,28 @@
 
 - name: global
   when: ferm_global_settings | d(false)
-  notify: reload ferm
+  notify: restart ferm
   copy:
     dest: /etc/ferm/ferm.d/{{ ferm_rules_filename }}.conf
     content: "{{ ferm_global_settings }}"
 
 - name: input
   when: ferm_input_rules | length > 0
-  notify: reload ferm
+  notify: restart ferm
   template:
     src: ferm_rules_input.conf.j2
     dest: /etc/ferm/input.d/{{ ferm_rules_filename }}.conf
 
 - name: output
   when: ferm_output_rules | length > 0
-  notify: reload ferm
+  notify: restart ferm
   template:
     src: ferm_rules_output.conf.j2
     dest: /etc/ferm/output.d/{{ ferm_rules_filename }}.conf
 
 - name: forward
   when: ferm_forward_rules | length > 0
-  notify: reload ferm
+  notify: restart ferm
   template:
     src: ferm_rules_forward.conf.j2
     dest: /etc/ferm/forward.d/{{ ferm_rules_filename }}.conf
diff --git a/ansible/roles/postgres-ha/defaults/main.yml b/ansible/roles/postgres-ha/defaults/main.yml
index 5def4111a6ede281683ac58f854befd3371456ad..038debef0316c504031f3fd4cffb37f6bf28aac9 100644
--- a/ansible/roles/postgres-ha/defaults/main.yml
+++ b/ansible/roles/postgres-ha/defaults/main.yml
@@ -18,11 +18,11 @@ repmgr_password:
 repmgr_db: repmgr
 repmgr_roles: LOGIN,REPLICATION,SUPERUSER
 
-repmgr_primary_node:
+repmgr_primary_node: "{{ hostvars[groups['postgres'][0]]['ansible_default_ipv4']['address'] }}"
 
 repmgr_timeout: 5
 
-repmgr_node_id:
+repmgr_node_id: "{{ (groups['postgres'].index(inventory_hostname))+1|int }}"
 repmgr_node_name: "{{ ansible_hostname }}"
 repmgr_conninfo: host={{ ansible_default_ipv4.address }} dbname={{ repmgr_db }} user={{ repmgr_user }} connect_timeout={{ repmgr_timeout }}
 
diff --git a/ansible/roles/postgres-ha/tasks/main.yml b/ansible/roles/postgres-ha/tasks/main.yml
index c7f55bbd5255cd02d1b90470959a7f75ae113ba6..3ef4b24dffbe9485c28192d0ca952d4954fb8660 100644
--- a/ansible/roles/postgres-ha/tasks/main.yml
+++ b/ansible/roles/postgres-ha/tasks/main.yml
@@ -138,7 +138,7 @@
 # REGISTER PRIMARY
 
 - name: setup primary
-  when: db_role == "primary"
+  when: (db_role is defined and db_role == "primary") or (db_role is undefined and inventory_hostname == groups['postgres'][0])
   block:
 
     - name: check if primary already joined
@@ -162,7 +162,7 @@
 # REGISTER STANDBY
 
 - name: setup standby
-  when: db_role == "standby"
+  when: (db_role is defined and db_role == "standby") or (db_role is undefined and inventory_hostname == groups['postgres'][1])
   block:
 
     - name: check if standby already joined
@@ -244,7 +244,7 @@
 # REGISTER WITNESS
 
 - name: setup witness
-  when: db_role == "witness"
+  when: (db_role is defined and db_role == "witness") or (db_role is undefined and inventory_hostname == groups['postgres'][2])
   block:
 
     - name: check if witness already joined
diff --git a/doc/deploy-ha.md b/doc/deploy-ha.md
index 3613f00f680984e6d134292ea1c062cf7fb190fc..837e333a419d499774575f474c34a29e933bf696 100644
--- a/doc/deploy-ha.md
+++ b/doc/deploy-ha.md
@@ -82,3 +82,28 @@ You can edit the skyreach configuration manual with the following command :
 ansible -i inventories/<client-ha> -m shell -a "sed -i \"s/'PORT': .*/'PORT': '54321'/g\" /home/skyreach/skyreach_data/private/settings_override.py" ms1
 ```
 and then deploy again as described in the previous section
+
+
+# Database maintenance
+
+Main documentation about repmgrd is here : https://redmine.ubicast.net/projects/ubicloud/wiki/Gestion_du_cluster_de_base_de_donn%C3%A9es
+
+Before using `postgres-maintenance.yml`, you need to add 3 groups to your inventory :
+```sh
+[postgres_primary]
+db1
+
+[postgres_standby]
+db2
+
+[postgres_fenced]
+db1
+```
+
+The playbook `postgres-maintenance.yml` can help in 3 scenarios :
+If you want to know the state of every node in the cluster, just launch the playbook without any tags
+
+If you want to promote a standby node (specified in postgres_standby group) to primary, launch the playbook with the tag `standby-to-primary`
+
+If you want a fenced (failed, specified in postgres_fenced group) node to join the cluster as standby node, launch the playbook with the tag `fenced-to-standby`
+
diff --git a/doc/deploy.md b/doc/deploy.md
index 26561c626315dd6b3446686180a45d05a61646d6..3eaab4d00095bd4241526f6bc22f686073df981b 100644
--- a/doc/deploy.md
+++ b/doc/deploy.md
@@ -117,6 +117,16 @@ echo 'conf_repo_version: mycustombranch' > /root/envsetup/inventories/local-medi
 make deploy i=inventories/local-mediaimport -l=mediaimport
 ```
 
+## Test setup with docker
+
+You can deploy everything specified in the inventory directly to local docker containers by calling the `site_docker.yml` playbook.
+
+```
+ansible-playbook -i inventory/example-ha site_docker.yml
+```
+
+This playbook will parse the inventory and create a docker container for each server, then launch the site.yml playbook as usual.
+
 ## Known issues
 
 - Proxy