diff --git a/tests/azure/deployment.yml b/tests/azure/deployment.yml
new file mode 100644
index 0000000000..2e83b8672c
--- /dev/null
+++ b/tests/azure/deployment.yml
@@ -0,0 +1,53 @@
+---
+schedules:
+- cron: "0 19 * * *"
+  displayName: Nightly Builds
+  branches:
+    include:
+    - master
+  always: true
+
+trigger: none
+
+pool:
+  vmImage: 'ubuntu-22.04'
+
+stages:
+
+# Fedora
+
+- stage: FedoraLatest_Ansible_Core_2_13
+  dependsOn: []
+  jobs:
+  - template: templates/deployment_tests.yml
+    parameters:
+      build_number: $(Build.BuildNumber)
+      scenario: fedora-latest
+      ansible_version: "-core >=2.13,<2.14"
+
+- stage: FedoraLatest_Ansible_Core_2_14
+  dependsOn: []
+  jobs:
+  - template: templates/deployment_tests.yml
+    parameters:
+      build_number: $(Build.BuildNumber)
+      scenario: fedora-latest
+      ansible_version: "-core >=2.14,<2.15"
+
+- stage: FedoraLatest_Ansible_Core_2_15
+  dependsOn: []
+  jobs:
+  - template: templates/deployment_tests.yml
+    parameters:
+      build_number: $(Build.BuildNumber)
+      scenario: fedora-latest
+      ansible_version: "-core >=2.15,<2.16"
+
+- stage: FedoraLatest_Ansible_latest
+  dependsOn: []
+  jobs:
+  - template: templates/deployment_tests.yml
+    parameters:
+      build_number: $(Build.BuildNumber)
+      scenario: fedora-latest
+      ansible_version: ""
diff --git a/tests/azure/templates/deployment_tests.yml b/tests/azure/templates/deployment_tests.yml
new file mode 100644
index 0000000000..018a943f86
--- /dev/null
+++ b/tests/azure/templates/deployment_tests.yml
@@ -0,0 +1,65 @@
+---
+parameters:
+  - name: scenario
+    type: string
+    default: fedora-latest
+  - name: ansible_version
+    type: string
+    default: ""
+  - name: python_version
+    type: string
+    default: 3.x
+  - name: build_number
+    type: string
+
+jobs:
+- job: Test_Deployment
+  displayName: Run deployment tests ${{ parameters.scenario }}
+  timeoutInMinutes: 240
+  steps:
+  - task: UsePythonVersion@0
+    inputs:
+      versionSpec: '${{ parameters.python_version }}'
+
+  - script: |
+      pip install "ansible${{ parameters.ansible_version }}"
+    retryCountOnTaskFailure: 5
+    displayName: Install Ansible
+
+  - script: ansible-galaxy collection install community.docker ansible.posix
+    retryCountOnTaskFailure: 5
+    displayName: Install Ansible collections
+
+  - script: pip install -r requirements-tests.txt
+    retryCountOnTaskFailure: 5
+    displayName: Install dependencies
+
+  - script: |
+      rm -rf ~/.ansible
+      mkdir -p ~/.ansible/roles ~/.ansible/library ~/.ansible/module_utils
+      cp -a roles/* ~/.ansible/roles
+      cp -a plugins/modules/* ~/.ansible/library
+      cp -a plugins/module_utils/* ~/.ansible/module_utils
+      docker pull ${SCENARIO_IMAGE}
+    env:
+      SCENARIO_IMAGE: quay.io/ansible-freeipa/upstream-tests:raw-${{ parameters.scenario }}
+    retryCountOnTaskFailure: 5
+    displayName: Setup test environment
+
+  - script: |
+      docker rm --force ${{ parameters.scenario }}
+      tests/server_role/inventory.py > inventory.yml
+      docker run -d --privileged --name=${{ parameters.scenario }} --hostname ${IPA_HOSTNAME} ${SCENARIO_IMAGE}
+      ansible-playbook -i inventory.yml playbooks/install-server.yml
+      docker stop ${{ parameters.scenario }}
+      docker rm ${{ parameters.scenario }}
+    displayName: Run deployment tests
+    env:
+      SCENARIO_IMAGE: quay.io/ansible-freeipa/upstream-tests:raw-${{ parameters.scenario }}
+      IPA_HOSTNAME: ipaserver.test.local
+
+  - task: PublishTestResults@2
+    inputs:
+      mergeTestResults: true
+      testRunTitle: DeploymentTests-Build${{ parameters.build_number }}
+    condition: succeededOrFailed()
diff --git a/tests/server_role/README.md b/tests/server_role/README.md
new file mode 100644
index 0000000000..1355eb1c4c
--- /dev/null
+++ b/tests/server_role/README.md
@@ -0,0 +1 @@
+This file should include how to test ipaserver.
diff --git a/tests/server_role/inventory.py b/tests/server_role/inventory.py
new file mode 100755
index 0000000000..1aaaa7421f
--- /dev/null
+++ b/tests/server_role/inventory.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+"""Dynamic inventory to test ipaserver role."""
+
+import os
+import sys
+from collections import namedtuple
+
+try:
+    import yaml
+
+    inventory_to_string = yaml.dump
+except ImportError:
+    import json
+
+    inventory_to_string = json.dumps
+
+Config = namedtuple(
+    "Config",
+    """
+    engine
+    container
+    hostname
+    ipa_domain
+    ipa_realm
+    setup_kra
+    setup_dns
+    dns_no_forwarders
+    dns_auto_reverse
+    setup_adtrust
+    ipa_netbios_name
+""",
+)
+
+
+def to_boolean(value):
+    return value.lower() == "true"
+
+
+def get_inventory_data(config):
+    """Generate inventory based on given configuration."""
+    return {
+        "all": {
+            "children": {
+                "ipaserver": {
+                    "hosts": {
+                        "ipa_server": {
+                            "ansible_connection": config.engine,
+                            "ansible_host": config.container,
+                        },
+                    },
+                    "vars": {
+                        # KRA
+                        "ipaserver_setup_kra": config.setup_kra,
+                        # DNS
+                        "ipaserver_setup_dns": config.setup_dns,
+                        "ipaserver_no_forwarders": config.dns_no_forwarders,
+                        "ipaserver_auto_reverse": config.dns_auto_reverse,
+                        # AD Trust
+                        "ipaserver_setup_adtrust": config.setup_adtrust,
+                        "ipaserver_netbios_name": config.ipa_netbios_name,
+                        # adjtimex fails on container, so do not set ntp
+                        "ipaclient_no_ntp": True,
+                        # server configuration
+                        "ipaserver_hostname": config.hostname,
+                    },
+                },
+            },
+            "vars": {
+                # server/realm
+                "ipaserver_domain": config.ipa_domain,
+                "ipaserver_realm": config.ipa_realm,
+                # passwords
+                "ipaadmin_password": "SomeADMINpassword",
+                "ipadm_password": "SomeDMpassword",
+            },
+        },
+    }
+
+
+def gen_default_inventory():
+    default_hostname = "ipaserver.test.local"
+    ipa_hostname = os.environ.get("IPA_HOSTNAME", default_hostname).split(".")
+
+    setup_dns = to_boolean(os.environ.get("SETUP_DNS", "False"))
+
+    config = Config(
+        engine="podman" if "--podman" in sys.argv else "docker",
+        container=os.environ.get("IPA_CONTAINER", "ipaserver_test_container"),
+        hostname=".".join(ipa_hostname),
+        ipa_domain=os.environ.get("IPA_DOMAIN", ".".join(ipa_hostname[1:])),
+        ipa_realm=os.environ.get(
+            "IPA_REALM", ".".join(ipa_hostname[1:]).upper()
+        ),
+        setup_kra=to_boolean(os.environ.get("SETUP_KRA", "False")),
+        setup_dns=setup_dns,
+        dns_no_forwarders=os.environ.get("DNS_NO_FORWARDERS", setup_dns),
+        dns_auto_reverse=os.environ.get("DNS_AUTO_REVERSE", setup_dns),
+        setup_adtrust=to_boolean(os.environ.get("SETUP_ADTRUST", "False")),
+        ipa_netbios_name=os.environ.get("IPA_NETBIOS_NAME", "IPA"),
+    )
+    print(inventory_to_string(get_inventory_data(config)))
+
+
+if "--matrix" in sys.argv:  # pylint: disable=no-else-raise
+    raise NotImplementedError("Test matrix not implemented yet.")
+else:
+    gen_default_inventory()