Running an Ansible command in multiple docker containers

I need to run a command against multiple running containers. For example, let's say my app needs to run a data structure update against a database after receiving code updates. To do this, we want to run docker build <project_dir> -t latest, then docker stop; docker rm; docker run. At this stage we can assume we have updated the container's core code, but we still need to run that database update using the app's own tooling.

Essentially, I need some way to get a list of running containers, filtered by some criteria, and register those container IDs with Ansible. Then for each container we run the command.

Like this:

- name: Get list of running containers
  docker:
      image: my-image:latest
      state: running
      register: container_ids

This task would store the list of running containers which use my-image:latest to container_ids. Then we exec the command:

- name: Exec the database update
  cmd: "docker exec -it {{ item }} my-app-db-update.sh"
  with: container_ids

Since we don't really want to use active containers for this type of operation, a better option would be to start a new single-use container acting on the same data instead:

- name: Run the database update
  cmd: "docker run --rm --volumes-from {{ item }} --link:mydb:db my-app sh -c 'my-app-db-update.sh'"
  with: container_ids

The above is only pseudo-code -- it won't actually run. How do I accomplish the task of storing a list of running docker containers which meet certain criteria, so that I can then apply a command to each container in the list using either docker exec or docker run?

There's surprisingly little about this online.

Given the following example output from docker ps:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7e21761c9c44        busybox             "top"                    22 minutes ago      Up 22 minutes                           agitated_yonath
7091d9c7cc56        nginx               "nginx -g 'daemon off"   23 minutes ago      Up 23 minutes       80/tcp, 443/tcp     fervent_blackwell

This playbook will give you an idea of how to capture the data you need and how to run actions iterating on the provided list. It's a very simple example, and you'll have to adapt it to your needs. That's on purpose:

---
- hosts: localhost
  gather_facts: no
  tasks:

  - name: gather list of containers
    shell: docker ps | awk '/{{ item }}/{print $1}'
    register: list_of_containers
    with_items:
      - busybox

  #- name: debug
  #  debug: msg="{{ list_of_containers }}"

  - name: run action in container(s)
    docker_container:
      name: temp-container
      image: busybox
      command: uptime
      cleanup: yes
      detach: yes
    register: result_of_action
    with_items:
      - list_of_containers.results.stdout_lines

The interesting parts are:

  1. Gather the list of containers in a given host (localhost in my example). I've used a plain shell invocation to be able to use awk to filter out the output. The result is stored in a register. Since the input is a list, this will have a direct consequence on how to retrieve the data back, more below. Uncomment the debug task in between to compare the data stored in the register with and without a list.

  2. Iterate over the register's results (container ID) and use the docker_container module to run an action (command parameter). You can use links and volumes_from in your docker_container invocation. Check the on-line documentation of the module for the details.

@thrig, this needs to be applied to containers running on one system, not multiple ones. If I needed to apply the command to multiple systems I would simply use an inventory to accomplish that.

@techraf – I’ve edited the question. It now includes a question mark.

So you are actually asking for a syntax of a pipe similar to docker ps | grep my-image: latest | cut -f 1?

No @techraf, it’s more efficient to use docker ps --filter in that instance. The question is about idempotency and best-practice, since running the above as shell commands is not idempotent. You’d have thought it would make more sense to use ansible’s own docker API to accomplish this, if able.

What does idempotency have to do with query? Any query. Please explain.

Idempotency is an important concept with ansible, @techraf. When running shell commands naively using ansible, we lose the ability to assess whether the system has met the playbook’s requirements (and skip the task) or not. That’s why it is always advisable to run ansible native API modules instead of shell. The ansible manual states, wrt to stdout_lines, not to use it often on this account.