Ansible supports variables which allows you to add a level of abstraction in an Ansible file and to manage it more efficiently in case of change: a change will be applied globally rather than having to change one by one the options.

This is the second post in a series of 3 articles about Docker and Ansible:
Part I: from Docker Compose to Ansible
Part II: using variables
Part III: using vault to encrypt sensitive information

Understand variables

Here is an example :

---
- hosts: localhost

  vars:
    var_1: variable

  tasks:

  - debug:
      msg: "This is a {{ var_1 }}."

Result:

PLAY [localhost] *************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is a variable."
}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

File without variables

We will use the file created in the previous post as a reference:

---
- hosts: localhost

  tasks:

  - name: Create network
    docker_network:
      name: network_app
      ipam_options:
        subnet: '172.16.98.0/24'
        
  - name: Run Nginx container
    docker_container:
      name: 'nginx'
      recreate: true
      restart_policy: unless-stopped
      image: 'nginx:latest'
      published_ports:
        - "80:80"
        - "443:443"
      volumes:
        - "./app.conf:/etc/nginx/conf.d/app.conf"
        - "php-app:/var/www/app"
      networks:
        - name: "network_app"        - 

  - name: Run PHP container
    docker_container:
      name: 'php'
      recreate: true
      restart_policy: unless-stopped
      image: 'php:7-fpm'
      volumes:
        - "php-app:/var/www/app"
      networks:
        - name: "network_app"
        
  - name: Run Percona container
    docker_container:
      name: 'percona'
      recreate: true
      restart_policy: unless-stopped
      image: 'percona:latest'
      volumes:
        - "percona:/var/lib/mysql"
      env:
        MYSQL_ROOT_PASSWORD: "secret_root"
        MYSQL_DATABASE: "db"
        MYSQL_USER: "db_user"
        MYSQL_PASSWORD: "secret_user"
      networks:
        - name: "network_app"

Play with variables

To add variables we will place a vars block above the tasks block.

---
- hosts: localhost

  vars:
    docker_network: network_app
    docker_network_backend: backend
    mysql_db_name: db
    mysql_db_user: ghost
    mysql_db_host: db
    mysql_db_password: secret_user
    mysql_db_root_password: secret_root
  tasks:
    [...]

And for include in the tasks part :

  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'

File with variables

Here an example with few variables :

---
- hosts: localhost

  vars:
    docker_network: network_app
    mysql_db_name: db
    mysql_db_user: ghost
    mysql_db_host: db
    mysql_db_password: secret_user
    mysql_db_root_password: secret_root

  tasks:

  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
        
  - name: Run Nginx container
    docker_container:
      name: 'nginx'
      recreate: true
      restart_policy: unless-stopped
      image: 'nginx:latest'
      published_ports:
        - "80:80"
        - "443:443"
      volumes:
        - "./app.conf:/etc/nginx/conf.d/app.conf"
        - "php-app:/var/www/app"
      networks:
        - name: "{{ docker_network }}"

  - name: Run PHP container
    docker_container:
      name: 'php'
      recreate: true
      restart_policy: unless-stopped
      image: 'php:7-fpm'
      volumes:
        - "php-app:/var/www/app"
      networks:
        - name: "{{ docker_network }}"
        
  - name: Run Percona container
    docker_container:
      name: 'percona'
      recreate: true
      restart_policy: unless-stopped
      image: 'percona:latest'
      volumes:
        - "percona:/var/lib/mysql"
      env:
        MYSQL_ROOT_PASSWORD: "{{ mysql_db_root_password }}"
        MYSQL_DATABASE: "{{ mysql_db_name }}"
        MYSQL_USER: "{{ mysql_db_user }}"
        MYSQL_PASSWORD: "{{ mysql_db_password }}"
      networks:
        - name: "{{ docker_network }}"

Multiples variables

Here are examples with multiples variables :

---
- hosts: localhost

  vars:
    var_1: "the first variable"
    var_2: "the second variable"

  tasks:

  - debug:
      msg: "This is {{ var_1 }} and this is {{ var_2 }}"

and the result that goes with it:

PLAY [localhost] *************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is the first variable and this is the second variable"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Variables are very flexible :

---
- hosts: localhost

  vars:
    var_1: app_
    var_2: network

  tasks:

  - debug:
      msg: "{{ var_1 }}{{ var_2 }}"

and the result :

PLAY [localhost] *************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "app_network"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Prefix in Docker Compose way

With variables you can prefix resources really easily like Docker Compose do on a deployment (for a stack named app all of the resources will be prefixed with the stack name (example : app_$VOLUME, app_$NETWORK, app_$CONTAINER. Simply add a variables and prefix the resources you want.

For example, you want to prefix with app_ :

---
- hosts: localhost

  vars:
    docker_network: network
    stack_prefix_name: app_

  tasks:

  - name: Create network
    docker_network:
      name: "{{ stack_name}}{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
  - name: Run Nginx container
    docker_container:
      name: '{{ stack_name}}nginx'
      recreate: true
      restart_policy: unless-stopped
      image: 'nginx:latest'
      published_ports:
        - "80:80"
        - "443:443"
      volumes:
        - "./app.conf:/etc/nginx/conf.d/app.conf"
        - "{{ stack_name }}_php-app:/var/www/app"
      networks:
        - name: "{{ stack_name}}{{ docker_network }}"

Docker Compose up and down

Now that we have a super functionnal Ansible may be we want to easily up and down the stack. It's possible with the state option.

For docker_network there's 2 state : absent and present. For docker_container there's 4 states : absent, present, stopped, started.

We'll set the default state with 2 variables : network_state (the force disconnect all containers from network when the network is deleted) and container_state and add the option to the different resources :

---
- hosts: localhost
  
  vars:
    container_state: started
    network_state: present
  
  tasks:
  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
      state: "{{ network_state }}"
      force: yes
      
[...]

  - name: Run PHP container
    docker_container:
      name: 'php'
      recreate: true
      restart_policy: unless-stopped
      image: 'php:7-fpm'
      volumes:
        - "php-app:/var/www/app"
      networks:
        - name: "{{ docker_network }}"
      state: "{{ container_state }}"

[...]

By default the stack will be created. If we want to delete it, just change the state with the --extra-vars or -e option :

$ ansible-playbook playbook.yml --extra-vars "container_state=absent network_state=absent"

Thanks to these option you can do whatever you want, for example just create the containers :

$ ansible-playbook playbook.yml --extra-vars "container_state=present"

Stop the containers :

$ ansible-playbook playbook.yml --extra-vars "container_state=stopped"

Or delete the containers but keep the network :

$ ansible-playbook playbook.yml --extra-vars "container_state=absent"

Resources :

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
https://docs.ansible.com/ansible/latest/modules/docker_network_module.html
https://docs.ansible.com/ansible/2.3/docker_container_module.html
https://docs.ansible.com/ansible/2.4/ansible-playbook.html

Feel free to correct me if you see any typo or if something seems wrong to you.