diff --git a/app/commands.py b/app/commands.py
index 6b8b5e6ce3ee8f2521b0e01a77d0e88259267bf5..fd1c33f5ea9acc350b8edda6152938dc5c45b041 100644
--- a/app/commands.py
+++ b/app/commands.py
@@ -16,9 +16,8 @@ import sqlalchemy as sa
 from flask import current_app
 from .extensions import db, ldap_manager
 from .defaults import defaults
-from .models import User
 from .tasks import TaskWorker
-from . import utils, tokens
+from . import models, utils, tokens
 
 
 def sync_user(connection, user):
@@ -64,7 +63,7 @@ def sync_users():
     except ldap3.core.exceptions.LDAPException as e:
         current_app.logger.warning(f"Failed to connect to the LDAP server: {e}")
         return
-    for user in User.query.all():
+    for user in models.User.query.all():
         sync_user(connection, user)
     db.session.commit()
 
diff --git a/app/models.py b/app/models.py
index e91ebcd089a1deb6b4e0f2c298aa496461310bef..bfa25575d33e78a33d78c837712d4c7809e66d1f 100644
--- a/app/models.py
+++ b/app/models.py
@@ -798,6 +798,7 @@ class AnsibleGroupType(Enum):
 
 
 class AnsibleGroup(CreatedMixin, db.Model):
+    __versioned__ = {}
     __tablename__ = "ansible_group"
     # Define id here so that it can be used in the primary and secondary join
     id = db.Column(db.Integer, primary_key=True)
@@ -875,6 +876,10 @@ class AnsibleGroup(CreatedMixin, db.Model):
 
 
 class Host(CreatedMixin, db.Model):
+    __versioned__ = {}
+
+    # id shall be defined here to be used by SQLAlchemy-Continuum
+    id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.Text, nullable=False, unique=True)
     description = db.Column(db.Text)
     device_type_id = db.Column(
diff --git a/app/templates/network/view_group.html b/app/templates/network/view_group.html
index 31e73e7a624260a4dfac80dcc80ff9318cdefb52..1e41b74518a69645997a7a7f2fb82ff67cd47ed6 100644
--- a/app/templates/network/view_group.html
+++ b/app/templates/network/view_group.html
@@ -41,4 +41,31 @@
       </dl>
     </div>
   </div>
+  <h3>History</h3>
+  <table id="group_version_table" class="table table-hover table-sm">
+    <thead>
+      <tr>
+        <th>Updated at</th>
+        <th>Updated by</th>
+        <th>Name</th>
+        <th>Variables</th>
+        <th>Type</th>
+        <th>Children</th>
+        <th>Hosts</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for version in group.versions | reverse %}
+      <tr>
+        <td>{{ version.transaction.issued_at | datetimeformat }}</td>
+        <td>{{ version.transaction.user }}</td>
+        <td>{{ version.name }}</td>
+        <td><pre>{{ version.vars | toyaml }}</pre></td>
+        <td>{{ version.type }}</td>
+        <td>{{ link_to_ansible_groups(version.children) }}</td>
+        <td>{{ link_to_hosts(version._hosts) }}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
 {%- endblock %}
diff --git a/app/templates/network/view_host.html b/app/templates/network/view_host.html
index 589e9247f72d313ac0918856dd4df43c8684a113..908d2af140c50c0ad86bab085349cb2613d25054 100644
--- a/app/templates/network/view_host.html
+++ b/app/templates/network/view_host.html
@@ -108,4 +108,29 @@
       {% endfor %}
     </tbody>
   </table>
+  <h3>History</h3>
+  <table id="host_version_table" class="table table-hover table-sm">
+    <thead>
+      <tr>
+        <th>Updated at</th>
+        <th>Updated by</th>
+        <th>Name</th>
+        <th>Device Type</th>
+        <th>Ansible variables</th>
+        <th>Ansible groups</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for version in host.versions | reverse %}
+      <tr>
+        <td>{{ version.transaction.issued_at | datetimeformat }}</td>
+        <td>{{ version.transaction.user }}</td>
+        <td>{{ version.name }}</td>
+        <td>{{ version.device_type }}</td>
+        <td><pre>{{ version.ansible_vars | toyaml }}</pre></td>
+        <td>{{ link_to_ansible_groups(version.ansible_groups) }}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
 {%- endblock %}
diff --git a/migrations/versions/8f9b5c5ed49f_add_versioning_on_host_and_ansiblegroup.py b/migrations/versions/8f9b5c5ed49f_add_versioning_on_host_and_ansiblegroup.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc74a8bac90b69d57a17c43073f2c2881807bddd
--- /dev/null
+++ b/migrations/versions/8f9b5c5ed49f_add_versioning_on_host_and_ansiblegroup.py
@@ -0,0 +1,252 @@
+"""Add versioning on Host and AnsibleGroup
+
+Revision ID: 8f9b5c5ed49f
+Revises: 5698c505d70e
+Create Date: 2018-08-31 14:45:59.768159
+
+"""
+import citext
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = "8f9b5c5ed49f"
+down_revision = "5698c505d70e"
+branch_labels = None
+depends_on = None
+
+
+def create_transaction():
+    """Create a new transaction and return the id"""
+    conn = op.get_bind()
+    conn.execute("INSERT INTO transaction (issued_at) values (now())")
+    result = conn.execute("SELECT id FROM transaction ORDER BY issued_at DESC LIMIT 1")
+    return result.fetchone()[0]
+
+
+def create_initial_version(version_table, transaction_id):
+    conn = op.get_bind()
+    # Get all values from the source table
+    source_table = version_table.name.replace("_version", "")
+    result = conn.execute(f"SELECT * from {source_table}")
+    values = [{key: value for key, value in row.items()} for row in result]
+    # Add the transaction_id and operation_type
+    for d in values:
+        d["transaction_id"] = transaction_id
+        # INSERT = 0 (sqlalchemy_continuum/operation.py)
+        d["operation_type"] = 0
+    op.bulk_insert(version_table, values)
+
+
+def upgrade():
+    ansible_group_type = postgresql.ENUM(
+        "STATIC",
+        "NETWORK_SCOPE",
+        "NETWORK",
+        "DEVICE_TYPE",
+        name="ansible_group_type",
+        create_type=False,
+    )
+    ansible_group_version = op.create_table(
+        "ansible_group_version",
+        sa.Column("created_at", sa.DateTime(), autoincrement=False, nullable=True),
+        sa.Column("updated_at", sa.DateTime(), autoincrement=False, nullable=True),
+        sa.Column("id", sa.Integer(), autoincrement=False, nullable=False),
+        sa.Column("name", citext.CIText(), autoincrement=False, nullable=True),
+        sa.Column(
+            "vars",
+            postgresql.JSONB(astext_type=sa.Text()),
+            autoincrement=False,
+            nullable=True,
+        ),
+        sa.Column("type", ansible_group_type, autoincrement=False, nullable=True),
+        sa.Column("user_id", sa.Integer(), autoincrement=False, nullable=True),
+        sa.Column(
+            "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
+        ),
+        sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
+        sa.Column("operation_type", sa.SmallInteger(), nullable=False),
+        sa.PrimaryKeyConstraint(
+            "id", "transaction_id", name=op.f("pk_ansible_group_version")
+        ),
+    )
+    op.create_index(
+        op.f("ix_ansible_group_version_end_transaction_id"),
+        "ansible_group_version",
+        ["end_transaction_id"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_group_version_operation_type"),
+        "ansible_group_version",
+        ["operation_type"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_group_version_transaction_id"),
+        "ansible_group_version",
+        ["transaction_id"],
+        unique=False,
+    )
+    ansible_groups_hosts_version = op.create_table(
+        "ansible_groups_hosts_version",
+        sa.Column(
+            "ansible_group_id", sa.Integer(), autoincrement=False, nullable=False
+        ),
+        sa.Column("host_id", sa.Integer(), autoincrement=False, nullable=False),
+        sa.Column(
+            "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
+        ),
+        sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
+        sa.Column("operation_type", sa.SmallInteger(), nullable=False),
+        sa.PrimaryKeyConstraint(
+            "ansible_group_id",
+            "host_id",
+            "transaction_id",
+            name=op.f("pk_ansible_groups_hosts_version"),
+        ),
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_hosts_version_end_transaction_id"),
+        "ansible_groups_hosts_version",
+        ["end_transaction_id"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_hosts_version_operation_type"),
+        "ansible_groups_hosts_version",
+        ["operation_type"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_hosts_version_transaction_id"),
+        "ansible_groups_hosts_version",
+        ["transaction_id"],
+        unique=False,
+    )
+    ansible_groups_parent_child_version = op.create_table(
+        "ansible_groups_parent_child_version",
+        sa.Column("parent_group_id", sa.Integer(), autoincrement=False, nullable=False),
+        sa.Column("child_group_id", sa.Integer(), autoincrement=False, nullable=False),
+        sa.Column(
+            "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
+        ),
+        sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
+        sa.Column("operation_type", sa.SmallInteger(), nullable=False),
+        sa.PrimaryKeyConstraint(
+            "parent_group_id",
+            "child_group_id",
+            "transaction_id",
+            name=op.f("pk_ansible_groups_parent_child_version"),
+        ),
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_parent_child_version_end_transaction_id"),
+        "ansible_groups_parent_child_version",
+        ["end_transaction_id"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_parent_child_version_operation_type"),
+        "ansible_groups_parent_child_version",
+        ["operation_type"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_ansible_groups_parent_child_version_transaction_id"),
+        "ansible_groups_parent_child_version",
+        ["transaction_id"],
+        unique=False,
+    )
+    host_version = op.create_table(
+        "host_version",
+        sa.Column("created_at", sa.DateTime(), autoincrement=False, nullable=True),
+        sa.Column("updated_at", sa.DateTime(), autoincrement=False, nullable=True),
+        sa.Column("id", sa.Integer(), autoincrement=False, nullable=False),
+        sa.Column("name", sa.Text(), autoincrement=False, nullable=True),
+        sa.Column("description", sa.Text(), autoincrement=False, nullable=True),
+        sa.Column("device_type_id", sa.Integer(), autoincrement=False, nullable=True),
+        sa.Column(
+            "ansible_vars",
+            postgresql.JSONB(astext_type=sa.Text()),
+            autoincrement=False,
+            nullable=True,
+        ),
+        sa.Column("user_id", sa.Integer(), autoincrement=False, nullable=True),
+        sa.Column(
+            "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
+        ),
+        sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
+        sa.Column("operation_type", sa.SmallInteger(), nullable=False),
+        sa.PrimaryKeyConstraint("id", "transaction_id", name=op.f("pk_host_version")),
+    )
+    op.create_index(
+        op.f("ix_host_version_end_transaction_id"),
+        "host_version",
+        ["end_transaction_id"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_host_version_operation_type"),
+        "host_version",
+        ["operation_type"],
+        unique=False,
+    )
+    op.create_index(
+        op.f("ix_host_version_transaction_id"),
+        "host_version",
+        ["transaction_id"],
+        unique=False,
+    )
+    transaction_id = create_transaction()
+    create_initial_version(ansible_group_version, transaction_id)
+    create_initial_version(ansible_groups_hosts_version, transaction_id)
+    create_initial_version(ansible_groups_parent_child_version, transaction_id)
+    create_initial_version(host_version, transaction_id)
+
+
+def downgrade():
+    op.drop_index(op.f("ix_host_version_transaction_id"), table_name="host_version")
+    op.drop_index(op.f("ix_host_version_operation_type"), table_name="host_version")
+    op.drop_index(op.f("ix_host_version_end_transaction_id"), table_name="host_version")
+    op.drop_table("host_version")
+    op.drop_index(
+        op.f("ix_ansible_groups_parent_child_version_transaction_id"),
+        table_name="ansible_groups_parent_child_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_groups_parent_child_version_operation_type"),
+        table_name="ansible_groups_parent_child_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_groups_parent_child_version_end_transaction_id"),
+        table_name="ansible_groups_parent_child_version",
+    )
+    op.drop_table("ansible_groups_parent_child_version")
+    op.drop_index(
+        op.f("ix_ansible_groups_hosts_version_transaction_id"),
+        table_name="ansible_groups_hosts_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_groups_hosts_version_operation_type"),
+        table_name="ansible_groups_hosts_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_groups_hosts_version_end_transaction_id"),
+        table_name="ansible_groups_hosts_version",
+    )
+    op.drop_table("ansible_groups_hosts_version")
+    op.drop_index(
+        op.f("ix_ansible_group_version_transaction_id"),
+        table_name="ansible_group_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_group_version_operation_type"),
+        table_name="ansible_group_version",
+    )
+    op.drop_index(
+        op.f("ix_ansible_group_version_end_transaction_id"),
+        table_name="ansible_group_version",
+    )
+    op.drop_table("ansible_group_version")