Build That Ansible x Docker Setup — DevOps Role Secured 🐳

Build That Ansible x Docker Setup — DevOps Role Secured 🐳


A quick story how you can build interesting project and gain both - Docker and Ansible experience!🐳

Intro

Some time ago, I opened my terminal, spun up three EC2 instances, and started my Ansible. I was motivated to start writing playbooks, roles and … I was looking for something new and refreshing to me.

And I got stuck. What to code? What to automate? I was blank on ideas. Then, I put on my DevOps Engineer’s hat and realized that the best projects I always thrive in are the ones where I integrate tools together.

Quickly, I started to look for ideas on what I could integrate Ansible with… Turns out our IaC has fantastic Docker modules that allow smooth and easy integration of both tools.

Without much thinking, I decided to write an Ansible project that involves Docker containers. I documented my project here so you can also practice :)

1️⃣ Project’s Requirements

Press enter or click to view image in full size


Ok, first things first. Let’s define the requirements for our Docker × Ansible integration:

✅ Use Ansible to install docker.io (we’ll assume we're working with a clean Linux server)

✅ Use the docker_image module to pull the service image (in my case, I wanted to start Grafana)

✅ Use the docker_container module to create the container from the image

✅ Ensure you can connect to Grafana's port (3000)

✅ Verify the service is running (I wanted to receive an HTTP 200 from the server)

✅ Debug Docker logs using Ansible

So, we have a few clear requirements. How do we approach this?
Simple: Google Ansible’s Docker modules! 😄
Red Hat’s tool comes with many fantastic Docker modules. One great example is docker_image, which allows you to manage images remotely from your Ansible control node—just like in the code below:

- name: Pull an image  docker_image:    name: pacur/centos-7    source: pull
- name: Pull an image  docker_image:    name: pacur/centos-7    source: pull
- name: Pull an image  docker_image:    name: pacur/centos-7    source: pull

All documentation for Docker Image module is below:

https://docs.ansible.com/ansible/latest/collections/community/docker/docker_image_module.html#ansible-collections-community-docker-docker-image-module

Another example is docker_container . This one also offers a wide array of the different possibilities to build our container.

- name: Re-create a redis container  docker_container:    name: myredis    image: redis    command: redis-server --appendonly yes    state: present    recreate: yes    exposed_ports:      - 6379    volumes_from:      - mydata
- name: Re-create a redis container  docker_container:    name: myredis    image: redis    command: redis-server --appendonly yes    state: present    recreate: yes    exposed_ports:      - 6379    volumes_from:      - mydata
- name: Re-create a redis container  docker_container:    name: myredis    image: redis    command: redis-server --appendonly yes    state: present    recreate: yes    exposed_ports:      - 6379    volumes_from:      - mydata

Btw, I also wrote an article dedicated to the 5 most fun — but also educational — projects in Ansible. You can check it here:

https://medium.com/@becomedevops1/how-to-integrate-security-into-ansible-e9bdced9916d

Great! So now when we have our requirements for the project nailed down, let’s write the deployment playbook.

2️⃣ Writing Deployment Playbook

First of all, I created dedicated role with:

ansible-galaxy init docker_role
ansible-galaxy init docker_role
ansible-galaxy init docker_role

The core development was focused on the tasks/main.yml file within the role. I began by installing and enabling Docker, as shown in the first two tasks below.

- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started
- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started
- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started

Next, I moved on to the Docker-related tasks. I created the /var/lib/grafana directory and pulled the Docker image, with the image name defined as a variable. After that, I launched the Docker container, specifying parameters such as the container name, image, state, ports, mounted volumes, and restart policy.

- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana
- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana
- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana

At this stage, we should be able to pull the image and successfully build a Docker container from it. To ensure the service is accessible, I need to make sure that port 3000 is open. For this, I will use the ufw module in Ansible to allow traffic on that port.

- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp
- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp
- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp

To verify that Grafana is running and accessible, I added a set of validation and debugging tasks.

First, I use the command module to perform an HTTP request to Grafana’s health endpoint on port 3000. This checks whether the service is responding correctly. The response is stored in the grafana_http_status variable:

- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status
- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status
- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status

I include a debug message to output the HTTP status code returned by Grafana. A successful response should return 200, indicating that the service is up and reachable:

- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."
- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."
- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."

The whole deployment playbook is below:

- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp- name: Getting Public IP  shell: curl -s ifconfig.me  register: public_ip_result  changed_when: false- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."- name: Docker Container Logs  shell: "docker logs grafana | tail"  register: container_logs- name: Debug Container Logs  debug:    msg: "{{ container_logs.stdout }}"
- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp- name: Getting Public IP  shell: curl -s ifconfig.me  register: public_ip_result  changed_when: false- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."- name: Docker Container Logs  shell: "docker logs grafana | tail"  register: container_logs- name: Debug Container Logs  debug:    msg: "{{ container_logs.stdout }}"
- name: Install Docker  apt:    name: docker.io    state: present- name: Start Docker Service  service:    name: docker    state: started- name: Ensure /var/lib/grafana directory exists  file:    path: /var/lib/grafana    state: directory    mode: '0755'- name: Pull Grafana Docker Image  docker_image:    name: "{{ image_name }}"    source: pull- name: Run Docker Container  docker_container:    name: "{{ container_name }}"    image: "{{ image_name }}"    state: started    ports:      - "3000:3000"    restart_policy: always    volumes:      - grafana-storage:/var/lib/grafana- name: Open port 3000  ufw:    rule: allow    port: 3000    proto: tcp- name: Getting Public IP  shell: curl -s ifconfig.me  register: public_ip_result  changed_when: false- name: Check if Grafana is accessible via HTTP  command: curl -s -o /dev/null -w "%{http_code}" http://{{ public_ip_result.stdout }}:3000/api/health  register: grafana_http_status- name: Verify Grafana HTTP response code is 200  debug:    msg: "The system is online with code: {{ grafana_http_status.stdout }}."- name: Docker Container Logs  shell: "docker logs grafana | tail"  register: container_logs- name: Debug Container Logs  debug:    msg: "{{ container_logs.stdout }}"

Of course as part of my role, I also define the variables in the /vars file.


Finally calling the role from the deployment.yml playbook.


3️⃣ Deploying Changes

Once the code was written, it is time to run the playbook with:

ansible-playbook playbooks/deployment
ansible-playbook playbooks/deployment
ansible-playbook playbooks/deployment

In my case, the changes have been already implemented but we see everything works and tasks are successful.


The second part of the logs with debuging docker container logs:


In the end we see the service is accessible and available:

Press enter or click to view image in full size


And that’s all for today. I hope you enjoyed!

Test us in action. Book a free
consultation for your team

We don't do hard sales pitches. Let's spend 30 minutes discussing your current DevOps bottlenecks, and we'll help you map out the most efficient upskilling path for your team.

Test us in action. Book a free consultation for your team

We don't do hard sales pitches. Let's spend 30 minutes discussing your current DevOps bottlenecks, and we'll help you map out the most efficient upskilling path for your team.