diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/integration/functions.sh b/test/integration/functions.sh
new file mode 100644
index 0000000000000000000000000000000000000000..18dfc84df4d9e45a3193140f102bf4bc8b6fd653
--- /dev/null
+++ b/test/integration/functions.sh
@@ -0,0 +1,90 @@
+set -e
+
+TEST_PREFIX="$(cat /dev/urandom | tr -dc 'a-z0-9' | head -c 10)"
+TEST_LABEL="systemd-resolved-docker=test-${TEST_PREFIX}"
+
+trap "cleanup" EXIT
+
+cleanup() {
+  stop_systemd_resolved_docker
+  docker ps --all   --filter label=${TEST_LABEL} --format '{{ .ID }}' | xargs --no-run-if-empty docker rm --force > /dev/null
+  docker network ls --filter label=${TEST_LABEL} --format '{{ .ID }}' | xargs --no-run-if-empty docker network rm > /dev/null
+}
+
+start_systemd_resolved_docker() {
+  stop_systemd_resolved_docker
+
+  PYTHONPATH="$PWD/../../src:$PYTHONPATH" python -m systemd_resolved_docker.cli &
+  SYSTEMD_RESOLVED_DOCKER_PID=$!
+
+  sleep 2
+}
+
+stop_systemd_resolved_docker() {
+  if [ ! -z "$SYSTEMD_RESOLVED_DOCKER_PID" ];
+  then
+    kill $SYSTEMD_RESOLVED_DOCKER_PID || true
+    sleep 1
+
+    SYSTEMD_RESOLVED_DOCKER_PID=""
+  fi
+}
+
+docker_run() {
+  local name=$1
+  shift;
+
+  docker run --detach --label "${TEST_LABEL}" --name "${TEST_PREFIX}-$name" --interactive $@ alpine | cut -c -12
+}
+
+docker_ip() {
+  local container_id=$1
+  shift;
+
+  docker inspect --format '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container_id
+}
+
+docker_name() {
+  local container_id=$1
+  shift;
+
+  docker inspect --format '{{ .Name }}' $container_id | cut -c2-
+}
+
+query_ok() {
+  local query=$1
+  local ip=$2
+  shift; shift;
+
+  if resolvectl query $query | grep $ip;
+  then
+    true
+  else
+    >&2 echo "Failed to resolve $query to $ip"
+    false
+  fi
+}
+
+query_fail() {
+  local query=$1
+  shift;
+
+  if resolvectl query $query;
+  then
+    >&2 echo "Failing, resolved $query"
+    false
+  else
+    true
+  fi
+}
+
+test_query() {
+  local name=$1
+  local query=$2
+  shift; shift;
+
+  local id=$(docker_run $name $@)
+  local ip=$(docker_ip $id)
+
+  query_ok $query $ip
+}
diff --git a/test/integration/test_compose.sh b/test/integration/test_compose.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cd136a6afeeaf805609d9869a63128c78cbc77d5
--- /dev/null
+++ b/test/integration/test_compose.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+exec 10<<EOF
+version: "2.1"
+services:
+  webserver:
+    image: nginx
+    labels:
+     - $TEST_LABEL
+    networks:
+      - network
+  broker:
+    image: redis
+    labels:
+     - $TEST_LABEL
+    networks:
+      - network
+
+networks:
+  network:
+    driver: bridge
+    enable_ipv6: false
+    labels:
+     - $TEST_LABEL
+    ipam:
+      driver: default
+      config:
+        - subnet: 172.16.238.0/24
+          gateway: 172.16.238.1
+EOF
+
+ALLOWED_DOMAINS=.docker,.$TEST_PREFIX start_systemd_resolved_docker
+
+docker-compose --file /dev/fd/10 --project-name $TEST_PREFIX up --detach --scale webserver=2
+
+broker1_ip=$(docker_ip ${TEST_PREFIX}_broker_1)
+webserver1_ip=$(docker_ip ${TEST_PREFIX}_webserver_1)
+webserver2_ip=$(docker_ip ${TEST_PREFIX}_webserver_2)
+
+query_ok     broker.$TEST_PREFIX $broker1_ip
+query_ok   1.broker.$TEST_PREFIX $broker1_ip
+
+query_ok     webserver.$TEST_PREFIX $webserver1_ip
+query_ok     webserver.$TEST_PREFIX $webserver2_ip
+query_ok   1.webserver.$TEST_PREFIX $webserver1_ip
+query_ok   2.webserver.$TEST_PREFIX $webserver2_ip
diff --git a/test/integration/test_hostname.sh b/test/integration/test_hostname.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b0a52eb49188f8c1dc0d323fd2a2bdeaf29e22ad
--- /dev/null
+++ b/test/integration/test_hostname.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+ALLOWED_DOMAINS=.docker,host2,.domain2 start_systemd_resolved_docker
+
+test_query hostname1 host1.docker --hostname host1
+query_fail host1
+
+test_query hostname2 host2.docker --hostname host2
+query_fail host2
+
+test_query hostdomain1 host1.domain1.docker --hostname host1 --domainname domain1
+query_fail host1.domain1
+
+test_query hostdomain2 host2.domain2        --hostname host2 --domainname domain2
+query_fail host2.domain2.docker
\ No newline at end of file
diff --git a/test/integration/test_hostnet.sh b/test/integration/test_hostnet.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e87fbccc9837527effa9c0823012780c7dc16124
--- /dev/null
+++ b/test/integration/test_hostnet.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+container_id=$(docker_run hostnet1 --network host --hostname hostnet1)
+
+start_systemd_resolved_docker
+
+query_ok   $container_id.docker 127.0.0.1
+query_ok        hostnet1.docker 127.0.0.1
+query_ok   ${TEST_PREFIX}-hostnet1.host.docker 127.0.0.1
+
+DEFAULT_HOST_IP=1.2.3.4 start_systemd_resolved_docker
+
+query_ok   $container_id.docker 1.2.3.4
+query_ok        hostnet1.docker 1.2.3.4
+query_ok   ${TEST_PREFIX}-hostnet1.host.docker 1.2.3.4
diff --git a/test/integration/test_id_only.sh b/test/integration/test_id_only.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a6f24adf9ebd814f7975d505b35b74dca5e6a522
--- /dev/null
+++ b/test/integration/test_id_only.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+container1_id=$(docker_run id1)
+container1_ip=$(docker_ip ${container1_id})
+container2_id=$(docker_run id2)
+container2_ip=$(docker_ip ${container2_id})
+
+
+DEFAULT_DOMAIN=dockerx ALLOWED_DOMAINS=.dockerx start_systemd_resolved_docker
+query_ok ${container1_id}.dockerx $container1_ip
+query_ok ${container2_id}.dockerx $container2_ip
+
+query_fail ${container1_id}.docker
+query_fail ${container2_id}.docker
+
+
+DEFAULT_DOMAIN=test123 ALLOWED_DOMAINS=.docker start_systemd_resolved_docker
+query_ok ${container1_id}.test123 $container1_ip
+query_ok ${container2_id}.test123 $container2_ip
+
+query_fail ${container1_id}.docker
+query_fail ${container2_id}.docker
diff --git a/test/integration/test_network.sh b/test/integration/test_network.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90073a5035f941cb297a317801edb70f5b52eb39
--- /dev/null
+++ b/test/integration/test_network.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+NETWORK1=testnet1-$TEST_PREFIX
+NETWORK2=testnet2-$TEST_PREFIX
+
+docker network create --label $TEST_LABEL $NETWORK1 > /dev/null
+docker network create --label $TEST_LABEL $NETWORK2 > /dev/null
+
+ALLOWED_DOMAINS=.docker,.$NETWORK1 start_systemd_resolved_docker
+
+container1_id=$(docker_run network1 --network $NETWORK1)
+container1_ip=$(docker_ip ${container1_id})
+container1_name=$(docker_name ${container1_id})
+
+container2_id=$(docker_run network2 --name name2 --network $NETWORK1)
+container2_ip=$(docker_ip ${container2_id})
+container2_name=$(docker_name ${container2_id})
+
+container3_id=$(docker_run network3 --network $NETWORK2)
+container3_ip=$(docker_ip ${container3_id})
+container3_name=$(docker_name ${container3_id})
+
+container4_id=$(docker_run network4 --name name4 --network $NETWORK2)
+container4_ip=$(docker_ip ${container4_id})
+container4_name=$(docker_name ${container4_id})
+
+query_ok   $container1_name.$NETWORK1        $container1_ip
+query_fail $container1_name.$NETWORK1.docker $container1_ip
+
+query_ok   $container2_name.$NETWORK1        $container2_ip
+query_ok              name2.$NETWORK1        $container2_ip
+query_fail $container2_name.$NETWORK1.docker $container2_ip
+
+query_ok   $container3_name.$NETWORK2.docker $container3_ip
+query_fail $container3_name.$NETWORK2        $container3_ip
+
+query_ok   $container4_name.$NETWORK2.docker $container4_ip
+query_ok              name4.$NETWORK2.docker $container4_ip
+query_fail $container4_name.$NETWORK2        $container4_ip
diff --git a/test/integration/test_proxy.sh b/test/integration/test_proxy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..be3baf47c97dee67475d9cc52c8c2b3d167ace8b
--- /dev/null
+++ b/test/integration/test_proxy.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+start_systemd_resolved_docker
+
+NETWORK=testnet1-$TEST_PREFIX
+docker network create --label $TEST_LABEL $NETWORK > /dev/null
+
+container_id=$(docker_run resolvetest1 --hostname resolvetest1)
+container_ip=$(docker_ip ${container_id})
+
+dns_ip=$(docker network inspect bridge --format '{{ range .IPAM.Config }}{{ .Gateway }}{{ end }}')
+
+query_ok   resolvetest1.docker $container_ip
+
+# Case 1: generated domains are resolved in containers on the default network
+#         The DNS server is provided explicitly, since it was not provided to the daemon
+docker run --dns $dns_ip       --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
+
+# Case 2: generated domains are resolved in containers on other networks
+docker run  --network $NETWORK --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
diff --git a/test/integration/test_wildcard.sh b/test/integration/test_wildcard.sh
new file mode 100755
index 0000000000000000000000000000000000000000..562832ee6674e5822a2ec8458454b6e4f1176139
--- /dev/null
+++ b/test/integration/test_wildcard.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+. ./functions.sh
+
+ALLOWED_DOMAINS=.docker,.$TEST_PREFIX start_systemd_resolved_docker
+
+container_id=$(docker_run wildcard1 --hostname "*.$TEST_PREFIX")
+container_ip=$(docker_ip ${container_id})
+
+query_ok   anything.$TEST_PREFIX $container_ip
+query_ok otherthing.$TEST_PREFIX $container_ip
diff --git a/test/test_integration.py b/test/test_integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd73e6c3f946b248ff6939ed1d605294e8195e3b
--- /dev/null
+++ b/test/test_integration.py
@@ -0,0 +1,36 @@
+import os
+import pathlib
+import subprocess
+import unittest
+
+INTEGRATION_DIRECTORY = pathlib.Path(__file__).parent.resolve().joinpath("integration").as_posix()
+
+
+def generate_test(name):
+    def test(self):
+        try:
+            subprocess.check_output(
+                ['bash', '-c', 'cd {} && ./{}'.format(INTEGRATION_DIRECTORY, name)], stderr=subprocess.STDOUT,
+                text=True)
+        except subprocess.CalledProcessError as ex:
+            self.fail(ex.output)
+
+    return test
+
+
+def extend_testcase(cls):
+    for file in os.listdir(INTEGRATION_DIRECTORY):
+        if file.startswith("test") and file.endswith(".sh"):
+            name = file.removesuffix(".sh")
+            test = generate_test(file)
+            setattr(cls, name, test)
+
+
+class IntegrationTest(unittest.TestCase):
+    pass
+
+
+extend_testcase(IntegrationTest)
+
+if __name__ == '__main__':
+    unittest.main()