The Error
Your Ansible playbook uses the systemd module to start, stop, or enable a service. On real VMs or bare-metal hosts it works fine. Target a Docker or LXC container, and you hit this:
TASK [Enable and start nginx] **************************
fatal: [container-host]: FAILED! => {
"changed": false,
"msg": "Failed to connect to bus: No such file or directory"
}
The task dies immediately. Nothing else in the playbook runs.
Why This Happens
Ansible's systemd module talks to systemd over D-Bus. On a normal Linux host, the D-Bus daemon listens at /run/dbus/system_bus_socket and everything connects cleanly.
Containers don't have that socket. Most container images ship without systemd or D-Bus โ they're built to run a single process, not a full init stack. When Ansible goes looking for /run/dbus/system_bus_socket, the file simply isn't there. Hence: No such file or directory.
You'll run into this most often when:
- You're using Docker containers as Ansible inventory targets for Molecule testing or CI pipelines
- Your container image is a minimal distro build (e.g.,
ubuntu:22.04, not a purpose-built Ansible image) - The container was started without
--privilegedor without the cgroup filesystem mounted - You're running Molecule with the Docker driver against a stock base image
Fix 1: Use service Instead of systemd
No systemd-specific features? Just switch modules. The service module probes whatever init system is actually running and delegates to it:
# Before โ breaks in containers
- name: Enable and start nginx
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
# After โ works in containers and on VMs
- name: Enable and start nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
Inside a container with no init system, service falls back to calling the service binary directly or SysV init scripts. No D-Bus needed. This is the one-line fix for most cases.
Fix 2: Silence the Error with ignore_errors (Stopgap Only)
Sometimes you just need the playbook to not explode during a daemon_reload step that doesn't actually matter in a container. Slapping ignore_errors: true on it buys time:
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
ignore_errors: true
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
Don't use this in production playbooks. It masks real failures. It's only useful when you're iterating on a role locally and you know the daemon_reload is irrelevant in your test environment.
Fix 3: Detect Containers and Skip Systemd Tasks Conditionally
For roles that run against both VMs and containers, the right move is to detect the environment at runtime and gate your systemd tasks behind a when: condition.
- name: Check if running inside a container
ansible.builtin.stat:
path: /.dockerenv
register: dockerenv_file
- name: Set container fact
ansible.builtin.set_fact:
is_container: "{{ dockerenv_file.stat.exists or ansible_virtualization_type in ['docker', 'lxc', 'container'] }}"
- name: Enable and start nginx (skip in containers)
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
daemon_reload: true
when: not is_container
LXC containers won't have /.dockerenv, so check ansible_virtualization_type as well. Gather the virtual subset first if you haven't already:
- name: Gather virtualization facts
ansible.builtin.setup:
gather_subset:
- virtual
- name: Set is_container fact
ansible.builtin.set_fact:
is_container: "{{ ansible_virtualization_type in ['docker', 'lxc', 'podman', 'container', 'VirtualPC'] }}"
This approach keeps your role portable without forking it into separate container and VM versions.
Fix 4: Run a Systemd-Capable Container (Molecule / CI Testing)
Writing a role that genuinely manages services? Test it against a container that actually runs systemd. Jeff Geerling's geerlingguy/docker-ubuntu2204-ansible image is the standard choice here โ it ships with systemd configured and ready.
# docker run with systemd support
docker run -d \
--name test-container \
--privileged \
--volume /sys/fs/cgroup:/sys/fs/cgroup:rw \
--cgroupns host \
geerlingguy/docker-ubuntu2204-ansible \
/lib/systemd/systemd
For Molecule, wire this up in molecule/default/molecule.yml:
driver:
name: docker
platforms:
- name: instance
image: geerlingguy/docker-ubuntu2204-ansible
command: /lib/systemd/systemd
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
pre_build_image: true
With systemd running as PID 1, D-Bus starts automatically. The Ansible systemd module connects normally โ no workarounds needed.
Fix 5: Override Service Manager Per Inventory Group
Mixed inventory โ some VMs, some containers โ and you don't want to scatter when: conditions through your roles? Set ansible_service_mgr at the group level:
# inventory/hosts
[containers]
container-01 ansible_connection=docker
[containers:vars]
ansible_service_mgr=sysvinit
Then branch on it in your role tasks:
- name: Manage nginx service (systemd hosts)
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
when: ansible_service_mgr == 'systemd'
- name: Manage nginx service (non-systemd hosts)
ansible.builtin.service:
name: nginx
state: started
when: ansible_service_mgr != 'systemd'
Verify the Fix
Re-run with -v and limit to the container host to confirm the bus error is gone:
ansible-playbook -i inventory site.yml -v --limit container-01
Clean output looks like this:
TASK [Enable and start nginx] **************************
ok: [container-01]
PLAY RECAP *********************************************
container-01 : ok=5 changed=1 unreachable=0 failed=0
If you went with Fix 3 (container detection), add a quick debug task to confirm the fact resolved correctly:
- name: Debug container detection
ansible.builtin.debug:
msg: "is_container={{ is_container }}, service_mgr={{ ansible_service_mgr }}"
Quick Reference
- Fastest fix: swap
ansible.builtin.systemdโansible.builtin.service - CI/Molecule: use
geerlingguy/docker-ubuntu2204-ansiblewith--privileged+ cgroup mount - Mixed inventory: detect container via
/.dockerenvoransible_virtualization_type, gate withwhen: - Root cause: D-Bus socket
/run/dbus/system_bus_socketdoesn't exist in containers that don't run an init system

