There are a few pitfalls worth noting when using Ansible when the remote is an Arch machine. I know, a weird combination. It looks more and more like basically no-one uses Arch remote with Ansible. But hey, I like it, so learning (and documenting) a thing or two along the way might not be too bad. Also note that this post is quite specific for a docker-compose tool, so if you are not using it, you can safely skip the rest.

Getting started

First get a rootless Docker installed on a machine. It could be done by Ansible as well, but this is outside of the scope of this article. However, if you ever need help with that, just ping me (there is an email around the blog). I'll share my solution, or hopefully a whole post about it will be out by that time. Right now, you can gain some inspiration by looking up my previous article.

Next, we'll need two Ansible collection. The first is for pacman collection, referenced as community.general.pacman:

ansible-galaxy collection install community.general

The second one is for docker-compose collection, referenced as community.docker.docker_compose:

ansible-galaxy collection install community.docker

That should be it. Now let's get dirty.

Install docker-compose on the remote

Consider the most intuitive approach - install docker-compose and try to the service, as two Ansible tasks, assuming path/to/compose/project/docker-compose.yml file exists:

tasks:
  - name: Intall docker-compose
    become: yes
    community.general.pacman:
      state: present
      name:
        - docker-compose

  - name: Create and start the service
    community.docker.docker_compose:
      project_src: path/to/compose/project

When run as a playbook it fails:

TASK [Create and start the service] *********************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ModuleNotFoundError: No module named 'docker'
fatal: [X.X.X.X]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (Docker SDK for Python: docker above 5.0.0 (Python >= 3.6) or docker before 5.0.0 (Python 2.7) or docker-py (Python 2.6)) on vmi732184.contaboserver.net's Python /usr/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter, for example via `pip install docker` (Python >= 3.6) or `pip install docker==4.4.4` (Python 2.7) or `pip install docker-py` (Python 2.6). The error was: No module named 'docker'"}

Yeah, right. The collection requirements state the docker PyPi package to be present, or at least docker-py for older Python versions like 2.6.

python-docker via pacman

Alright, the preferred way to install python packages on Arch is to prefer pacman over pip when possible. Luckily, python-docker is available in the community repository. No big deal. Let's add it into the playlist and re-run it:

tasks:
  - name: Intall docker-compose
    become: yes
    community.general.pacman:
      state: present
      name:
        - docker-compose
        - python-docker

  - name: Create and start the service
    community.docker.docker_compose:
      project_src: path/to/compose/project

It fails again, with something along the lines of:

"msg": "Unable to load docker-compose. Try `pip install docker-compose`. Error: Traceback (most recent call last):\n  File \"/tmp/ansible_community.docker.docker_compose_payload_0elyc4fq/ansible_community.docker.docker_compose_payload.zip/ansible_collections/community/docker/plugins/modules/docker_compose.py\", line 497, in <module>\nModuleNotFoundError: No module named 'compose'\n"

Now, there is no package in the official repositories containing words python and compose. Following the recommendations above, there is nothing relevant in AUR either.

docker-compose via pip

Note that docker-compose Python package pulls its docker package as a dependency. So it is safe to not reference it with pacman in the playbooks here anymore. Since there is no compose package in repositories, it's time to resort to pip instead:

tasks:
  - name: Intall docker-compose
    become: yes
    community.general.pacman:
      state: present
      name:
        - docker-compose
        - python-pip

  - name: Install pip docker-compose
    ansible.builtin.pip:
      name: docker-compose

  - name: Create and start the service
    community.docker.docker_compose:
      project_src: path/to/compose/project

Now the Python part gets resolved. We can now independently confirm the docker PyPi package is a dependency of the docker-compose PyPi package:

pip show docker-compose | grep Requires | cut -d' ' -f2- | tr , '\n'

Resulting in the following with the version 1.29.2:

docker
dockerpty
texttable
jsonschema
websocket-client
python-dotenv
requests
docopt
PyYAML

The above playbook however fails for the last time, and it was a little hard for me to pinpoint the issue here. The problem now is Docker. Truncated error message follows:

fatal: [X.X.X.X]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            ...
            "dependencies": true,
            "docker_host": "unix://var/run/docker.sock",
            "env_file": null,
            ...
        }
    },
    "msg": "Error connecting: Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))"
}

The actual error is a little bit longer but contains mostly traceback. The problem is with the docker_host attribute. With a Docker rootless, the actual socket is located in the user space, for instance at unix:///run/user/1000/docker.sock, specified by either CLI parameter or the DOCKER_HOST environmental variable, more in the docs.

Wrapping up

Most guides recommend exporting the variable somewhere into .profile, .bashrc or .zshrc like so:

export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

But, since the Ansible opens a non-interactive shell, this variable exactly as it is exported will not be available to us. We have to construct it manually and hope no-one had changed it:

tasks:
  - name: Intall docker-compose
    become: yes
    community.general.pacman:
      state: present
      name:
        - docker-compose
        - python-pip

  - name: Install pip docker-compose
    ansible.builtin.pip:
      name: docker-compose

  - name: Create and start the service
    community.docker.docker_compose:
      docker_host: "unix://{{ ansible_env.XDG_RUNTIME_DIR }}/docker.sock"
      project_src: path/to/compose/project

Note that for ansible_env to be available, option gather_facts has to be kept enabled. Mention in the docs. Enjoy!