diff --git a/infra/image/build-inventory b/infra/image/build-inventory
new file mode 100644
index 0000000000000000000000000000000000000000..41f5feb608926a10d00ea456810a2acd982a5549
--- /dev/null
+++ b/infra/image/build-inventory
@@ -0,0 +1,15 @@
+[ipaserver]
+ansible-freeipa-image-builder ansible_connection=podman
+
+[ipaserver:vars]
+ipaadmin_password=SomeADMINpassword
+ipadm_password=SomeDMpassword
+ipaserver_domain=test.local
+ipaserver_realm=TEST.LOCAL
+ipaserver_setup_dns=true
+ipaserver_auto_forwarders=true
+ipaserver_no_dnssec_validation=true
+ipaserver_auto_reverse=true
+ipaserver_setup_kra=true
+ipaserver_setup_firewalld=false
+ipaclient_no_ntp=true
diff --git a/infra/image/build.sh b/infra/image/build.sh
index b16021740b3443fcd91c548da0a2259df2f25827..6da8179191b519aad1bda828b347b428bdb3c6b1 100755
--- a/infra/image/build.sh
+++ b/infra/image/build.sh
@@ -3,6 +3,9 @@
 BASEDIR="$(readlink -f "$(dirname "$0")")"
 TOPDIR="$(readlink -f "${BASEDIR}/../..")"
 
+# shellcheck disable=SC1091
+. "${BASEDIR}/shcontainer"
+# shellcheck disable=SC1091
 . "${TOPDIR}/utils/shfun"
 
 valid_distro() {
@@ -12,7 +15,7 @@ valid_distro() {
 usage() {
     local prog="${0##*/}"
     cat << EOF
-usage: ${prog} [-h] [i] distro
+usage: ${prog} [-h] [-s] distro
     ${prog} build a container image to test ansible-freeipa.
 EOF
 }
@@ -35,7 +38,7 @@ name="ansible-freeipa-image-builder"
 hostname="ipaserver.test.local"
 # Number of cpus is not available in usptream CI (Ubuntu 22.04).
 # cpus="2"
-memory="4g"
+memory="3g"
 quayname="quay.io/ansible-freeipa/upstream-tests"
 deploy_server="N"
 
@@ -56,7 +59,7 @@ distro=${1:-}
 [ -f "${BASEDIR}/dockerfile/${distro}" ] \
   || die "${distro} is not a valid distro target.\nUse one of: $(valid_distro)"
 
-[ -n "$(command -v "podman")" ] || die "podman is required."
+container_check
 
 if [ "${deploy_server}" == "Y" ]
 then
@@ -65,87 +68,50 @@ then
     deploy_playbook="${TOPDIR}/playbooks/install-server.yml"
     [ -f "${deploy_playbook}" ] || die "Can't find playbook '${deploy_playbook}'"
 
-    inventory_file="${BASEDIR}/inventory"
+    inventory_file="${BASEDIR}/build-inventory"
     [ -f "${inventory_file}" ] || die "Can't find inventory '${inventory_file}'"
 fi
 
-container_state="$(podman ps -q --all --format "{{.State}}" --filter "name=${name}")"
+container_state=$(container_get_state "${name}")
 
 tag="${distro}-base"
 server_tag="${distro}-server"
 
-# in older (as in Ubuntu 22.04) podman versions,
-# 'podman image rm --force' fails if the image
-# does not exist.
-remove_image_if_exists()
-{
-    local tag_to_remove
-    tag_to_remove="${1}"
-    if podman image exists "${tag_to_remove}"
-    then
-        log info "= Cleanup ${tag_to_remove} ="
-        podman image rm "${tag_to_remove}" --force
-        echo
-    fi
-}
-
-remove_image_if_exists "${tag}"
-[ "${deploy_server}" == "Y" ] && remove_image_if_exists "${server_tag}"
-
-
-log info "= Building ${tag} ="
-podman build -t "${tag}" -f "${BASEDIR}/dockerfile/${distro}" \
-       "${BASEDIR}"
-echo
+container_remove_image_if_exists "${tag}"
+[ "${deploy_server}" == "Y" ] && \
+    container_remove_image_if_exists "${server_tag}"
 
-log info "= Creating ${name} ="
-podman create --privileged --name "${name}" --hostname "${hostname}" \
-    --network bridge:interface_name=eth0 --systemd true \
-    --memory "${memory}" --memory-swap -1 --no-hosts \
-    --replace "${tag}"
-echo
-
-log info "= Committing \"${quayname}:${tag}\" ="
-podman commit "${name}" "${quayname}:${tag}"
-echo
+container_build "${tag}" "${BASEDIR}/dockerfile/${distro}" "${BASEDIR}"
+container_create "${name}" "${tag}" "${hostname}" "${memory}"
+container_commit "${name}" "${quayname}:${tag}"
 
 if [ "${deploy_server}" == "Y" ]
 then
     deployed=false
 
-    log info "= Starting ${name} ="
-    [ "${container_state}" == "running" ] || podman start "${name}"
-    echo
+    [ "${container_state}" != "running" ] && container_start "${name}"
+
+    container_wait_for_journald "${name}"
 
     log info "= Deploying IPA ="
-    if ansible-playbook -i "${inventory_file}" "${deploy_playbook}"
+    if ansible-playbook -u root -i "${inventory_file}" "${deploy_playbook}"
     then
         deployed=true
     fi
     echo
 
     if $deployed; then
-        log info "= Enabling additional services ="
-        podman exec "${name}" systemctl enable fixnet
-        podman exec "${name}" systemctl enable fixipaip
+        log info "= Enabling services ="
+        container_exec "${name}" systemctl enable fixnet
+        container_exec "${name}" systemctl enable fixipaip
         echo
     fi
     
-    log info "= Stopping container ${name} ="
-    podman stop "${name}"
-    echo
+    container_stop "${name}"
 
     $deployed || die "Deployment failed"
 
-    log info "= Committing \"${quayname}:${server_tag}\" ="
-    podman commit "${name}" "${quayname}:${server_tag}"
-    echo
+    container_commit "${name}" "${quayname}:${server_tag}"
 fi
 
 log info "= DONE: Image created. ="
-
-# For tests:
-# podman start "${name}"
-# while [ -n "$(podman exec ansible-test systemctl list-jobs | grep -vi "no jobs running")" ]; do echo "waiting.."; sleep 5; done
-# # Run tests
-# podman stop "${name}"
diff --git a/infra/image/dockerfile/c10s b/infra/image/dockerfile/c10s
index 622098f36b7cb84b2772ba6dfa17810c6720bf01..3710cdc0269c4d260db4d71c51fb9f2fa54e941d 100644
--- a/infra/image/dockerfile/c10s
+++ b/infra/image/dockerfile/c10s
@@ -4,7 +4,6 @@ ENV container=podman
 RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
 dnf makecache; \
 dnf --assumeyes install \
-    /usr/bin/python3 \
     /usr/bin/dnf-3 \
     sudo \
     bash \
@@ -13,6 +12,19 @@ dnf --assumeyes install \
     iproute; \
 rm -rf /var/cache/dnf/;
 
+RUN (cd /lib/systemd/system/; \
+    if [ -e dbus-broker.service ] && [ ! -e dbus.service ]; then \
+       ln -s dbus-broker.service dbus.service; \
+    fi \
+)
+COPY system-service/container-ipa.target /lib/systemd/system/
+RUN systemctl set-default container-ipa.target
+RUN (cd /etc/systemd/system/; \
+    rm -rf multi-user.target.wants \
+	&& mkdir container-ipa.target.wants \
+	&& ln -s container-ipa.target.wants multi-user.target.wants \
+)
+
 COPY system-service/fixnet.sh /root/
 COPY system-service/fixipaip.sh /root/
 COPY system-service/fixnet.service /etc/systemd/system/
diff --git a/infra/image/dockerfile/c8s b/infra/image/dockerfile/c8s
index 87a5b82e685258dab30c53ebbd5283adab7b7adc..3cf629a044bed33c15a91a2ba6979454b9295fc7 100644
--- a/infra/image/dockerfile/c8s
+++ b/infra/image/dockerfile/c8s
@@ -7,8 +7,6 @@ sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo; \
 sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo; \
 dnf makecache; \
 dnf --assumeyes install \
-    /usr/bin/python3 \
-    /usr/bin/python3-config \
     /usr/bin/dnf-3 \
     sudo \
     bash \
@@ -18,6 +16,19 @@ dnf --assumeyes install \
 dnf clean all; \
 rm -rf /var/cache/dnf/;
 
+RUN (cd /lib/systemd/system/; \
+    if [ -e dbus-broker.service ] && [ ! -e dbus.service ]; then \
+       ln -s dbus-broker.service dbus.service; \
+    fi \
+)
+COPY system-service/container-ipa.target /lib/systemd/system/
+RUN systemctl set-default container-ipa.target
+RUN (cd /etc/systemd/system/; \
+    rm -rf multi-user.target.wants \
+	&& mkdir container-ipa.target.wants \
+	&& ln -s container-ipa.target.wants multi-user.target.wants \
+)
+
 COPY system-service/fixnet.sh /root/
 COPY system-service/fixipaip.sh /root/
 COPY system-service/fixnet.service /etc/systemd/system/
diff --git a/infra/image/dockerfile/c9s b/infra/image/dockerfile/c9s
index 5fe77d926732c871a71e134cf0adfa3fb8883c14..daf181c40f2296da585cdf75bdfd86f01526f49c 100644
--- a/infra/image/dockerfile/c9s
+++ b/infra/image/dockerfile/c9s
@@ -4,7 +4,6 @@ ENV container=podman
 RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
 dnf makecache; \
 dnf --assumeyes install \
-    /usr/bin/python3 \
     /usr/bin/dnf-3 \
     sudo \
     bash \
@@ -13,6 +12,19 @@ dnf --assumeyes install \
     iproute; \
 rm -rf /var/cache/dnf/;
 
+RUN (cd /lib/systemd/system/; \
+    if [ -e dbus-broker.service ] && [ ! -e dbus.service ]; then \
+       ln -s dbus-broker.service dbus.service; \
+    fi \
+)
+COPY system-service/container-ipa.target /lib/systemd/system/
+RUN systemctl set-default container-ipa.target
+RUN (cd /etc/systemd/system/; \
+    rm -rf multi-user.target.wants \
+	&& mkdir container-ipa.target.wants \
+	&& ln -s container-ipa.target.wants multi-user.target.wants \
+)
+
 COPY system-service/fixnet.sh /root/
 COPY system-service/fixipaip.sh /root/
 COPY system-service/fixnet.service /etc/systemd/system/
diff --git a/infra/image/dockerfile/fedora-latest b/infra/image/dockerfile/fedora-latest
index aadcffb7506c1edac65f7ae3a3db5d3e5c8d391d..f286f9f9e894a9ce1c2af59677a535f0e72a4ba1 100644
--- a/infra/image/dockerfile/fedora-latest
+++ b/infra/image/dockerfile/fedora-latest
@@ -15,6 +15,19 @@ dnf --assumeyes install \
 dnf clean all; \
 rm -rf /var/cache/dnf/;
 
+RUN (cd /lib/systemd/system/; \
+    if [ -e dbus-broker.service ] && [ ! -e dbus.service ]; then \
+       ln -s dbus-broker.service dbus.service; \
+    fi \
+)
+COPY system-service/container-ipa.target /lib/systemd/system/
+RUN systemctl set-default container-ipa.target
+RUN (cd /etc/systemd/system/; \
+    rm -rf multi-user.target.wants \
+	&& mkdir container-ipa.target.wants \
+	&& ln -s container-ipa.target.wants multi-user.target.wants \
+)
+
 COPY system-service/fixnet.sh /root/
 COPY system-service/fixipaip.sh /root/
 COPY system-service/fixnet.service /etc/systemd/system/
diff --git a/infra/image/dockerfile/fedora-rawhide b/infra/image/dockerfile/fedora-rawhide
index 5a1aa005cdfa4f19bdcc176fb96a02f24667da54..b726489efadb5974ea69e834b44e198c45cf876f 100644
--- a/infra/image/dockerfile/fedora-rawhide
+++ b/infra/image/dockerfile/fedora-rawhide
@@ -16,6 +16,19 @@ dnf --assumeyes install \
 dnf clean all; \
 rm -rf /var/cache/dnf/;
 
+RUN (cd /lib/systemd/system/; \
+    if [ -e dbus-broker.service ] && [ ! -e dbus.service ]; then \
+       ln -s dbus-broker.service dbus.service; \
+    fi \
+)
+COPY system-service/container-ipa.target /lib/systemd/system/
+RUN systemctl set-default container-ipa.target
+RUN (cd /etc/systemd/system/; \
+    rm -rf multi-user.target.wants \
+	&& mkdir container-ipa.target.wants \
+	&& ln -s container-ipa.target.wants multi-user.target.wants \
+)
+
 COPY system-service/fixnet.sh /root/
 COPY system-service/fixipaip.sh /root/
 COPY system-service/fixnet.service /etc/systemd/system/
diff --git a/infra/image/inventory b/infra/image/inventory
index 4a83eb75ccf7e07e6b5efd2acdbbd2c082b5a5a5..043fab0d0697d8a64bd48550dbb750282ec1c3ac 100644
--- a/infra/image/inventory
+++ b/infra/image/inventory
@@ -1,15 +1,6 @@
 [ipaserver]
-ansible-freeipa-image-builder ansible_connection=podman ansible_python_interpreter=/usr/bin/python3
+ansible-freeipa-tests ansible_connection=podman
 
 [ipaserver:vars]
 ipaadmin_password=SomeADMINpassword
 ipadm_password=SomeDMpassword
-ipaserver_domain=test.local
-ipaserver_realm=TEST.LOCAL
-ipaserver_setup_dns=true
-ipaserver_auto_forwarders=true
-ipaserver_no_dnssec_validation=true
-ipaserver_auto_reverse=true
-ipaserver_setup_kra=true
-ipaserver_setup_firewalld=false
-ipaclient_no_ntp=true
diff --git a/infra/image/shcontainer b/infra/image/shcontainer
new file mode 100644
index 0000000000000000000000000000000000000000..6f4e8a85272b91c51394cc3f222c23c0e3ebdd59
--- /dev/null
+++ b/infra/image/shcontainer
@@ -0,0 +1,166 @@
+#!/bin/bash -eu
+# This file is meant to be source'd by other scripts
+
+SCRIPTDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
+TOPDIR="$(readlink -f "${SCRIPTDIR}/../..")"
+
+. "${TOPDIR}/utils/shfun"
+
+container_create() {
+    local name=${1}
+    local image=${2}
+    local hostname=${3}
+    local memory=${4:-"3g"}
+    local cpus=${5:-"2"}
+
+    [ -n "${hostname}" ] || die "No hostname given"
+
+    log info "= Creating ${name} ="
+    podman create \
+           --security-opt label=disable \
+           --name "${name}" \
+           --hostname "${hostname}" \
+           --network bridge:interface_name=eth0 \
+           --systemd true \
+           --cpus "${cpus}" \
+           --memory "${memory}" \
+           --memory-swap -1 \
+           --no-hosts \
+           --replace \
+           "${image}"
+    echo
+}
+
+container_start() {
+    local name="${1}"
+
+    log info "= Starting ${name} ="
+    podman start "${name}"
+    echo
+}
+
+container_stop() {
+    local name="${1}"
+
+    log info "= Stopping ${name} ="
+    podman stop "${name}"
+    echo
+}
+
+container_wait_for_journald() {
+    local name=${1}
+
+    log info "= Waiting till systemd-journald is running ="
+    max=20
+    wait=2
+    count=0
+    while ! podman exec "${name}" ps -x | grep -q "systemd-journald"
+    do
+        if [ $count -ge $max ]; then
+            die "Timeout: systemd-journald is not starting up"
+        fi
+        count=$((count+1))
+        log info "Waiting ${wait} seconds .."
+        sleep ${wait}
+    done
+    log info "done"
+    echo
+}
+
+container_wait_up() {
+    local name="${1}"
+
+    log info "= Waiting till all services are started ="
+    max=20
+    wait=15
+    count=0
+    while podman exec "${name}" systemctl list-jobs | \
+        grep -qvi "no jobs running"
+    do
+        if [ $count -ge $max ]; then
+            die "Timeout: Services are not starting up"
+        fi
+        count=$((count+1))
+        log info "Waiting ${wait} seconds .."
+        sleep ${wait}
+    done
+    log info "done"
+    echo
+}
+
+container_build() {
+    local tag="${1}"
+    local file="${2}"
+    local dir="${3}"
+
+    log info "= Building ${tag} ="
+    podman build -t "${tag}" -f "${file}" "${dir}"
+    echo
+}
+
+container_commit() {
+    local name="${1}"
+    local image="${2}"
+
+    log info "= Committing \"${image}\" ="
+    podman commit "${name}" "${image}"
+    echo
+}
+
+container_exec() {
+    local name="${1}"
+    shift 1
+
+    # "@Q" is only needed for the log output, the exec command is properly
+    # working without also for args containing spaces.
+    log info "= Executing \"${*@Q}\" ="
+    podman exec -t "${name}" "${@}"
+    echo
+}
+
+container_remove_image_if_exists()
+{
+    # In older (as in Ubuntu 22.04) podman versions,
+    # 'podman image rm --force' fails if the image
+    # does not exist.
+    local tag_to_remove="${1}"
+
+    if podman image exists "${tag_to_remove}"
+    then
+        log info "= Cleanup ${tag_to_remove} ="
+        podman image rm "${tag_to_remove}" --force
+        echo
+    fi
+}
+
+container_get_state()
+{
+    local name="${1}"
+
+    state=$(podman ps -q --all --format "{{.State}}" --filter "name=${name}")
+    echo "${state}"
+}
+
+container_pull() {
+    local source="${1}"
+
+    image=$(podman pull "${source}")
+    echo "${image}"
+}
+
+container_image_list() {
+    local source="${1}"
+
+    # Append "$" for an exact match if the source does not end with ":" to
+    # search for the repo only.
+    if [[ ${source} != *: ]]; then
+        source="${source}$"
+    fi
+    image=$(podman image list --format "{{ .Repository }}:{{ .Tag }}" | \
+                grep "^${source}")
+    echo "${image}"
+}
+
+container_check() {
+    [ -n "$(command -v "podman")" ] || die "podman is required."
+}
diff --git a/infra/image/start.sh b/infra/image/start.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c5a7a342863cd882f62d3b6dca313568b8ce7606
--- /dev/null
+++ b/infra/image/start.sh
@@ -0,0 +1,87 @@
+#!/bin/bash -eu
+
+BASEDIR="$(readlink -f "$(dirname "$0")")"
+TOPDIR="$(readlink -f "${BASEDIR}/../..")"
+
+# shellcheck disable=SC1091
+. "${BASEDIR}/shcontainer"
+# shellcheck disable=SC1091
+. "${TOPDIR}/utils/shfun"
+
+usage() {
+    local prog="${0##*/}"
+    cat << EOF
+usage: ${prog} [-h] [-l] image
+    ${prog} start a prebuilt ansible-freeipa test container image.
+EOF
+}
+
+help() {
+    cat << EOF
+positional arguments:
+
+    image    The image to start, leave empty to get list of images
+
+optional arguments:
+
+    -l    Try to use local image first, if not found download.
+
+EOF
+}
+
+list_images() {
+    local quay_api="https://quay.io/api/v1/repository/ansible-freeipa/upstream-tests/tag"
+    log info "Available images on quay:"
+    curl --silent -L "${quay_api}" | jq '.tags[]|.name' | tr -d '"'| sort | uniq | sed "s/.*/    &/"
+    echo
+    log info "Local images (use -l):"
+    local_image=$(container_image_list "${repo}:")
+    echo "${local_image}" | sed -e "s/.*://" | sed "s/.*/    &/"
+    echo
+}
+
+repo="quay.io/ansible-freeipa/upstream-tests"
+name="ansible-freeipa-tests"
+hostname="ipaserver.test.local"
+try_local_first="N"
+
+while getopts ":hl" option
+do
+    case "${option}" in
+        h) help && exit 0 ;;
+        l) try_local_first="Y" ;;
+        *) die -u "Invalid option: ${option}" ;;
+    esac
+done
+
+shift $((OPTIND - 1))
+image=${1:-}
+
+container_check
+
+if [ -z "${image}" ]; then
+    list_images
+    exit 0
+fi
+
+local_image=
+if [ "${try_local_first}" == "Y" ]; then
+    log info "= Trying to use local image first ="
+    local_image=$(container_image_list "${repo}:${image}")
+    [ -n "${local_image}" ] && log info "Found ${local_image}"
+    echo
+fi
+if [ -z "${local_image}" ]; then
+    log info "= Downloading from quay ="
+    local_image=$(container_pull "${repo}:${image}")
+    echo
+fi
+
+[ -z "${local_image}" ] && die "Image '${image}' is not valid"
+
+container_create "${name}" "${local_image}" "${hostname}"
+container_start "${name}"
+container_wait_for_journald "${name}"
+container_wait_up "${name}"
+
+log info "Container ${name} is ready to be used."
diff --git a/infra/image/system-service/container-ipa.target b/infra/image/system-service/container-ipa.target
new file mode 100644
index 0000000000000000000000000000000000000000..c8538814fb9e60c6d94239f2cb85b31f86f78de7
--- /dev/null
+++ b/infra/image/system-service/container-ipa.target
@@ -0,0 +1,6 @@
+[Unit]
+Description=Minimal target for containerized FreeIPA server
+DefaultDependencies=false
+AllowIsolate=yes
+Requires=systemd-tmpfiles-setup.service systemd-journald.service dbus.service
+After=systemd-tmpfiles-setup.service systemd-journald.service dbus.service
diff --git a/infra/image/system-service/fixipaip.service b/infra/image/system-service/fixipaip.service
index 95db1180825ddc93b60d65fbf6778587cb36b6c4..ec56c0d44fae4b9405d3726e583f2c1639701596 100644
--- a/infra/image/system-service/fixipaip.service
+++ b/infra/image/system-service/fixipaip.service
@@ -1,6 +1,6 @@
 [Unit]
 Description=Fix IPA server IP in IPA Server
-After=multi-user.target
+After=ipa.service
 
 [Service]
 Type=oneshot
diff --git a/infra/image/system-service/fixipaip.sh b/infra/image/system-service/fixipaip.sh
index f7053e0292334e6861244034cfa88f2b87215fde..ed11a2b6a4a36c8f88f15926f139c2170d2a4235 100755
--- a/infra/image/system-service/fixipaip.sh
+++ b/infra/image/system-service/fixipaip.sh
@@ -73,16 +73,16 @@ for zone in ${ZONES}; do
             echo "ERROR: Failed to get old PTR from '${zone}': '${OLD_PTR}'"
         else
             ipa dnsrecord-mod "${zone}" "${OLD_PTR}" --ptr-rec="${HOSTNAME}." \
-                --rename="${PTR}"
+                --rename="${PTR}" || true
         fi
     else
         echo "Fixing forward zone ${zone}:"
-        ipa dnsrecord-mod test.local "${HOSTNAME%%.*}" --a-rec="$IP"
-        ipa dnsrecord-mod test.local ipa-ca --a-rec="$IP"
+        ipa dnsrecord-mod test.local "${HOSTNAME%%.*}" --a-rec="$IP" || true
+        ipa dnsrecord-mod test.local ipa-ca --a-rec="$IP" || true
     fi
 done
 
-ipa dnsserver-mod "${HOSTNAME}" --forwarder="${FORWARDER}"
+ipa dnsserver-mod "${HOSTNAME}" --forwarder="${FORWARDER}" || true
 
 kdestroy -c "${KRB5CCNAME}" -A