Replacing Docker Compose with Ansible

Source Code: https://github.com/amard33p/ansible-docker

We have a simple docker compose file which defines 2 components - ui and api.

# docker-compose.yml
version: "3.5"

services:
  ui:
    build: ./ui
    ports:
      - "8080:8000"
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "8081:5000"

A common need during development is to mount the source code inside containers for faster iterations.
To solve this we generally create a compose override file.

# docker-compose.override.yml
version: "3.5"

services:
  ui:
    volumes:
      - ./ui/index.html:/app/index.html

The override file should be present in Dev environment but must be absent in Production as we would already have a tested pre-built image by then.

There are pre-processors like jsonnet that allows the ability to add “logic” to dumb YAML files. However, in my opinion, a simpler solution can be achieved using Ansible Playbooks.

Using the Ansible modules docker_network, docker_image and docker_container, the equivalent standalone ansible playbook for the above compose files would be:

---
- hosts: localhost
  gather_facts: no
  vars:
    # Compose creates entities based on the directory name
    curdir: "{{ playbook_dir.split('/')[-1] }}"

  tasks:
  # Get value of devenv from extra args
  - set_fact:
      devenv: "{{ devenv | default(false) }}"
  - debug: 
      msg: Dev Environment = {{ devenv }}

  - name: Creating bridge network {{ curdir }}_default
    docker_network:
      name: "{{ curdir }}_default"

  - name: Building image {{ curdir }}_api
    docker_image:
      build:
        path: ./api
      source: build
      name: "{{ curdir }}_api"
    when: devenv|bool

  - name: Starting container {{ curdir }}_api_1
    docker_container:
      name: "{{ curdir }}_api_1"
      image:  "{{ curdir }}_api"
      networks_cli_compatible: yes
      network_mode: "{{ curdir }}_default"
      networks:
      - name: "{{ curdir }}_default"
      ports: 
        - '8081:5000'

  - name: Building image {{ curdir }}_ui
    docker_image:
      build:
        path: ./ui
      source: build
      name: "{{ curdir }}_ui"
    when: devenv|bool

  - name: Starting container {{ curdir }}_ui_1
    docker_container:
      name: "{{ curdir }}_ui_1"
      image:  "{{ curdir }}_ui"
      networks_cli_compatible: yes
      network_mode: "{{ curdir }}_default"
      networks:
      - name: "{{ curdir }}_default"
      ports: 
        - '8080:8000'
      volumes:
        - "{{ './ui/index.html:/app/index.html' if devenv else omit }}"

We pass in a flag through extra-args to specify the environment and use the volume mount based on that:

volumes:
  - "{{ './ui/index.html:/app/index.html' if devenv else omit }}"

Run the playbook as follows:

  • In Dev: ansible-playbook -e "devenv=true" compose_playbook.yml
  • In Test/Prod: ansible-playbook compose_playbook.yml

Comments