Automating Real Projects with Ansible and Docker (No VirtualBox!)
A Step-by-Step Guide to Practicing Ansible Using Docker Instead of Virtual Machines
Most Ansible tutorials use VirtualBox and Vagrant to simulate infrastructure. But if you're using a MacBook with an M1/M2 chip, like I do, VirtualBox often doesn't work well. So instead, I created a real Ansible lab using Docker containers. This article will walk you step by step through that setup.
Why Docker Instead of Virtual Machines?
VirtualBox doesn't work well on Apple Silicon (M1/M2)
Docker is lightweight and faster to spin up
Easier to automate and reset
Perfect for Ansible practice without needing real cloud servers
Goal of This Project
We'll simulate three remote servers using Docker containers and automate the following using Ansible:
SSH setup
Ping test
Install Nginx
Load balancing
Monitoring setup
I assume that you already installed docker in your machine if you don’t please check this website and follow accordingly.
Also I assume that you are already familiar with basic docker
Let’s start
1. Create the Project Structure
First, create a folder named ansible-docker-lab
in your home directory. Inside this folder, create a Dockerfile
:
File: ~/ansible-docker-lab/Dockerfile
FROM ubuntu:22.04
# Install requirements with clean up
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
openssh-server \
sudo \
python3 \
python3-apt \
&& rm -rf /var/lib/apt/lists/*
# Configure ansible user with passwordless sudo
RUN useradd -m -s /bin/bash ansible && \
echo "ansible:ansible" | chpasswd && \
usermod -aG sudo ansible && \
echo "ansible ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ansible && \
chmod 440 /etc/sudoers.d/ansible
# SSH setup
RUN mkdir -p /var/run/sshd && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
then build the docker image using following command:
docker build -t ansible-node .
next, make sure that the docker image exists try to run the following command:
docker images | grep ansible-node
Create Containers with a Custom Network
We’ll use a custom network for load balancing:
docker network create ansible-net # we need this for load balancing
Why did I create a custom network ?
docker by default use bridge network
which
Docker’s default bridge network allows containers to access the internet but not resolve each other’s hostnames (e.g.,
node2
,node3
).A custom network provides an internal DNS service, allowing containers to communicate by name—essential for load balancing.
Using custom network:
Creates an internal DNS service for that network.
allows containers on that network to talk to each other by name.
then lets create three containers within the custom networks, to do that run the following commands:
docker run -d --network ansible-net --name node1 -p 2221:22 ansible-node
docker run -d --network ansible-net --name node2 -p 2222:22 ansible-node
docker run -d --network ansible-net --name node3 -p 2223:22 ansible-node
again, it is time to check if the docker container is running, run the following command in your terminal
docker ps --format "table {{.Image}}\t{{.Command}}\t{{.Ports}}"
Expected output:
IMAGE COMMAND PORTS
ansible-node "/usr/sbin/sshd -D" 0.0.0.0:2223->22/tcp
ansible-node "/usr/sbin/sshd -D" 0.0.0.0:2222->22/tcp
ansible-node "/usr/sbin/sshd -D" 0.0.0.0:2221->22/tcp
Perfect, we have three containers running.
Setup SSH
It is time to setup SSH. normally we can access docker container without ssh through command line, for the sake of this tutorials as you see we have add user as ansible and password as ansible in docker file to setup SSH access to normal user and practice ansible.
we can do it manually like creating ssh-key and copy one by one to each container, but I would rather prefer to create an script and copy the public key to each container.
create an setup-ssh.sh
file inside of your ansible-docker-lab
directory
ssh-keygen -t rsa -f ansible_key -q -N ""
for port in 2221 2222 2223; do
sshpass -p "ansible" ssh-copy-id -i ansible_key.pub -o StrictHostKeyChecking=no -p $port ansible@localhost
done
then make it executable and run the following command
chmod +x setup-ssh.sh
./setup-ssh.sh
when you run above command it ask are you sure you want to connecting ? just say yes then type the ansible
password which we configure it in docker file.
what it does that it copy the public ssh to each container for future access via ansible, it need to connect to host without typing password through ssh key.
what it does it create two file at your current directly which are `ansible_key` and `ansible_key.pub` so bear in mind that if you want to push your the project to github repository add to .gitignore.
check if you can connect to container host with ssh key.
cd ~/ansible-docker-lab/ && ssh -i ansible_key -p 2221 ansible@localhost
So far, all is good, we have setup we have created the Dockerfile, create and docker image, run the docker image, and create ssh for each host.
It is time to start with working on Ansible
so first thing first,
what is Ansible ?
Ansible is an automation tools that helps you control and configure multiple server from one place without logging into each one manually.
why it become to so popular ?
Before Ansible:
Sysadmins had to log into hunderds of servers one by one to run the same command again and again,
It was slow, boring and likely to cause mistake or fail often.
After Ansible:
You write one script and the rest will handble by ansible accross all servers.
Install Ansible
pipx install --include-deps ansible
I am not going to deep dive into Ansible, but I will explain the basic in simple way.
so we have
Inventory: where to put and organize all of our hosts
Host: a single machine where ansible can control in above docker container
node1, node2, node3
are hostsPlaybook: Defines tasks to run on hosts.
Enough theory :D , let’s get into crafting.
lets create inventory, inside this file we put all of our server that we suppose to mange, either we group by different name or all in one group. for now I am going to put all of my server in one group call nodes
first lets create the ansible.cfg
config file and add some repetitive task inside of that config file
ansible.cfg
[defaults]
inventory = inventory.ini # Path to your inventory file
private_key_file = ansible_key # SSH private key for authentication
host_key_checking = False # Disables SSH host key verification (for testing)
inventory.ini
[node]
node1 ansible_host=localhost ansible_port=2221 ansible_user=ansible ansible_ssh_private_key_file=ansible_key
node2 ansible_host=localhost ansible_port=2222 ansible_user=ansible ansible_ssh_private_key_file=ansible_key
node3 ansible_host=localhost ansible_port=2223 ansible_user=ansible ansible_ssh_private_key_file=ansible_key
lets create out playbooks(hosts) task to be done on each server
playbook.yaml
cat playbook.yaml
- name: Test ping
hosts: node
gather_facts: false
tasks:
- name: Ping all nodes
ansible.builtin.ping:
lets see if we can ping all our nodes.
so far everything is working.
lets add another task to install nginx and another task to check inf nginx is runinng.
remember each task should one doing one task, we should not put both installing nginx and checking nginx status in one tasks. that is not good practice.
playbook.yaml
- name: Install and configure Nginx
hosts: node
become: true
gather_facts: false
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: yes
- name: Ensure Nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: yes
and run again:
Set Up Load Balancing:
We will configure node1 to act as reverse proxy for node2 and node3. lets create another file
nginx.conf.j2
:
events {}
http {
upstream backend {
server node2:80;
server node3:80;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
add these tasks to playbook.yaml
- name: Copy Nginx reverse proxy config to node1
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
when: inventory_hostname == 'node1'
- name: Restart Nginx
service:
name: nginx
state: restarted
when: inventory_hostname == 'node1'
Run the playbook again to apply the changes.
Conclusion
You’ve now set up a fully functional Ansible lab using Docker—no VirtualBox required! This setup is lightweight, scalable, and perfect for practicing automation.
Next steps:
Add monitoring (e.g., Prometheus).
Experiment with more Ansible modules.
Happy automating! 🚀