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/grafanaAt 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:- name: Check if Grafana is accessible via HTTP command: curl -s -o /dev/null -w "%{http_code}" http:- name: Check if Grafana is accessible via HTTP command: curl -s -o /dev/null -w "%{http_code}" http: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:- 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:- 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: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!