
How to Use Ansible to Install and Set Up Docker on Ubuntu 24
Managing server infrastructure at scale has never been more critical, and automation tools like Ansible are game-changers for DevOps workflows. Setting up Docker on Ubuntu 24 manually across multiple servers is tedious and error-prone, but with Ansible, you can automate the entire process consistently and reliably. This guide walks you through creating Ansible playbooks to install Docker, configure it properly, and handle common deployment scenarios you’ll encounter in production environments.
Why Ansible for Docker Installation
Ansible’s agentless architecture makes it perfect for Docker deployment scenarios. Unlike other configuration management tools, you don’t need to install anything on target machines except SSH access. When you’re managing dozens or hundreds of Ubuntu servers, this approach scales beautifully.
The real power comes from Ansible’s idempotent nature – you can run the same playbook multiple times without breaking anything. This is crucial when dealing with Docker installations that might fail halfway through due to network issues or package conflicts.
Prerequisites and Environment Setup
Before diving into playbooks, ensure your control machine has Ansible installed and can SSH into your Ubuntu 24 targets. You’ll need sudo privileges on the target machines and basic inventory configuration.
# Install Ansible on control machine (Ubuntu/Debian)
sudo apt update
sudo apt install ansible python3-pip
pip3 install docker
# Verify installation
ansible --version
Create a basic inventory file to define your target hosts:
# inventory.ini
[docker_servers]
server1 ansible_host=192.168.1.10 ansible_user=ubuntu
server2 ansible_host=192.168.1.11 ansible_user=ubuntu
server3 ansible_host=192.168.1.12 ansible_user=ubuntu
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_become=yes
ansible_become_method=sudo
Complete Docker Installation Playbook
Here’s a comprehensive playbook that handles the full Docker installation process on Ubuntu 24, including repository setup, package installation, and service configuration:
# docker-install.yml
---
- name: Install and configure Docker on Ubuntu 24
hosts: docker_servers
become: yes
vars:
docker_users:
- ubuntu
- deploy
docker_daemon_config:
log-driver: "json-file"
log-opts:
max-size: "10m"
max-file: "3"
storage-driver: "overlay2"
tasks:
- name: Update apt package cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages for Docker repository
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- software-properties-common
state: present
- name: Create directory for Docker GPG key
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
keyring: /etc/apt/keyrings/docker.gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Update apt cache after adding repository
apt:
update_cache: yes
- name: Install Docker packages
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
- name: Create Docker daemon configuration
copy:
content: "{{ docker_daemon_config | to_nice_json }}"
dest: /etc/docker/daemon.json
mode: '0644'
notify: restart docker
- name: Start and enable Docker service
systemd:
name: docker
state: started
enabled: yes
- name: Add users to docker group
user:
name: "{{ item }}"
groups: docker
append: yes
loop: "{{ docker_users }}"
when: docker_users is defined
- name: Verify Docker installation
command: docker --version
register: docker_version
changed_when: false
- name: Display Docker version
debug:
msg: "Docker installed successfully: {{ docker_version.stdout }}"
handlers:
- name: restart docker
systemd:
name: docker
state: restarted
Advanced Configuration Options
The basic installation gets Docker running, but production environments need additional configuration. Here’s an enhanced playbook section that handles security hardening and performance optimization:
# advanced-docker-config.yml
---
- name: Advanced Docker configuration
hosts: docker_servers
become: yes
vars:
docker_storage_driver: overlay2
docker_log_max_size: 50m
docker_log_max_file: 5
enable_docker_metrics: true
docker_registry_mirrors:
- "https://mirror.gcr.io"
tasks:
- name: Configure Docker daemon with advanced settings
template:
src: daemon.json.j2
dest: /etc/docker/daemon.json
backup: yes
notify: restart docker
- name: Set up Docker log rotation
copy:
content: |
/var/lib/docker/containers/*/*.log {
rotate 5
daily
compress
size=50M
missingok
delaycompress
copytruncate
}
dest: /etc/logrotate.d/docker-containers
- name: Configure Docker systemd service limits
ini_file:
path: /etc/systemd/system/docker.service.d/override.conf
section: Service
option: "{{ item.option }}"
value: "{{ item.value }}"
backup: yes
loop:
- { option: 'LimitNOFILE', value: '1048576' }
- { option: 'LimitNPROC', value: '1048576' }
- { option: 'LimitCORE', value: 'infinity' }
notify:
- reload systemd
- restart docker
- name: Enable Docker content trust (optional)
lineinfile:
path: /etc/environment
line: "DOCKER_CONTENT_TRUST=1"
create: yes
when: enable_content_trust | default(false)
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart docker
systemd:
name: docker
state: restarted
Template Configuration Files
Create a Jinja2 template for flexible Docker daemon configuration:
# templates/daemon.json.j2
{
"log-driver": "json-file",
"log-opts": {
"max-size": "{{ docker_log_max_size | default('10m') }}",
"max-file": "{{ docker_log_max_file | default('3') }}"
},
"storage-driver": "{{ docker_storage_driver | default('overlay2') }}",
"storage-opts": [
"overlay2.override_kernel_check=true"
],
{% if docker_registry_mirrors is defined %}
"registry-mirrors": {{ docker_registry_mirrors | to_json }},
{% endif %}
{% if enable_docker_metrics %}
"metrics-addr": "127.0.0.1:9323",
"experimental": true,
{% endif %}
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true
}
Running and Testing the Playbooks
Execute the playbooks with proper error handling and verification steps:
# Run the basic installation
ansible-playbook -i inventory.ini docker-install.yml --check
# Execute without dry-run
ansible-playbook -i inventory.ini docker-install.yml
# Run advanced configuration
ansible-playbook -i inventory.ini advanced-docker-config.yml
# Test Docker functionality across all hosts
ansible docker_servers -i inventory.ini -m shell -a "docker run --rm hello-world"
# Check Docker service status
ansible docker_servers -i inventory.ini -m systemd -a "name=docker state=started"
Common Issues and Troubleshooting
Docker installations can fail for various reasons. Here are the most frequent issues and their Ansible-based solutions:
Issue | Symptoms | Ansible Solution |
---|---|---|
GPG key verification fails | Repository authentication errors | Use apt_key module with proper keyring path |
Package conflicts | Installation fails with dependency errors | Remove old Docker packages first using apt module |
Service won’t start | Docker daemon fails to initialize | Check daemon.json syntax and storage driver compatibility |
Permission denied | Users can’t run Docker commands | Ensure users are added to docker group and re-login |
Here’s a troubleshooting playbook that addresses common issues:
# troubleshoot-docker.yml
---
- name: Troubleshoot Docker installation issues
hosts: docker_servers
become: yes
tasks:
- name: Remove conflicting packages
apt:
name:
- docker.io
- docker-doc
- docker-compose
- podman-docker
- containerd
- runc
state: absent
- name: Clean up old Docker data (WARNING: destructive)
file:
path: "{{ item }}"
state: absent
loop:
- /var/lib/docker
- /var/lib/containerd
when: clean_docker_data | default(false)
- name: Check disk space
shell: df -h /var/lib/docker
register: disk_space
ignore_errors: yes
- name: Display disk space
debug:
msg: "Docker storage location: {{ disk_space.stdout_lines }}"
- name: Validate daemon.json syntax
shell: python3 -m json.tool /etc/docker/daemon.json
register: json_validation
ignore_errors: yes
- name: Show JSON validation results
debug:
msg: "daemon.json is {{ 'valid' if json_validation.rc == 0 else 'invalid' }}"
Performance Optimization and Monitoring
Once Docker is installed, you’ll want to optimize it for your workload. This playbook sets up monitoring and performance tuning:
# optimize-docker.yml
---
- name: Optimize Docker performance
hosts: docker_servers
become: yes
tasks:
- name: Configure kernel parameters for containers
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: yes
loop:
- { name: 'vm.max_map_count', value: '262144' }
- { name: 'fs.file-max', value: '65536' }
- { name: 'net.core.somaxconn', value: '65535' }
- name: Install Docker monitoring tools
pip:
name:
- docker-py
- psutil
state: present
- name: Create Docker monitoring script
copy:
content: |
#!/bin/bash
echo "=== Docker System Info ==="
docker system df
echo "=== Running Containers ==="
docker stats --no-stream
echo "=== Docker Events (last 10) ==="
docker events --since 1h --until 1s | tail -10
dest: /usr/local/bin/docker-monitor.sh
mode: '0755'
- name: Set up cron job for Docker cleanup
cron:
name: "Docker system prune"
minute: "0"
hour: "2"
job: "/usr/bin/docker system prune -f --volumes"
Real-World Use Cases and Examples
Here are practical scenarios where this Ansible approach shines:
- Multi-environment deployments: Use inventory groups to separate staging, production, and development Docker hosts with different configurations
- Auto-scaling infrastructure: Integrate with cloud providers to automatically configure Docker on newly provisioned instances
- Compliance and auditing: Ensure consistent Docker configurations across all servers for security compliance
- Disaster recovery: Quickly rebuild Docker environments from scratch using your tested playbooks
For high-availability setups, you might want to integrate this with managed VPS services or scale to dedicated servers for resource-intensive containerized applications.
Integration with Docker Compose and Swarm
Extend your Ansible playbooks to set up Docker Compose and Swarm clustering:
# docker-compose-setup.yml
---
- name: Install Docker Compose and set up Swarm
hosts: docker_servers
become: yes
vars:
docker_compose_version: "2.21.0"
swarm_manager: "{{ groups['docker_servers'][0] }}"
tasks:
- name: Install Docker Compose
get_url:
url: "https://github.com/docker/compose/releases/download/v{{ docker_compose_version }}/docker-compose-linux-x86_64"
dest: /usr/local/bin/docker-compose
mode: '0755'
- name: Initialize Docker Swarm on manager
docker_swarm:
state: present
advertise_addr: "{{ ansible_default_ipv4.address }}"
when: inventory_hostname == swarm_manager
register: swarm_info
- name: Get Swarm join token
command: docker swarm join-token worker -q
register: swarm_token
when: inventory_hostname == swarm_manager
changed_when: false
- name: Join Swarm as worker
docker_swarm:
state: join
join_token: "{{ hostvars[swarm_manager]['swarm_token']['stdout'] }}"
remote_addrs: "{{ hostvars[swarm_manager]['ansible_default_ipv4']['address'] }}:2377"
when: inventory_hostname != swarm_manager
Security Hardening Best Practices
Production Docker installations require security hardening. This playbook implements essential security measures:
# secure-docker.yml
---
- name: Harden Docker security
hosts: docker_servers
become: yes
tasks:
- name: Configure Docker to use non-root user namespace
lineinfile:
path: /etc/docker/daemon.json
insertafter: '{'
line: ' "userns-remap": "default",'
- name: Create Docker TLS certificates directory
file:
path: /etc/docker/certs.d
state: directory
mode: '0700'
- name: Configure AppArmor for Docker
apt:
name: apparmor-utils
state: present
- name: Set up Docker socket permissions
file:
path: /var/run/docker.sock
mode: '0660'
group: docker
- name: Configure firewall rules for Docker
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "2376" # Docker daemon secure port
- "2377" # Swarm manager
- "7946" # Swarm overlay networking
when: configure_firewall | default(true)
The Ansible approach to Docker installation provides consistency, repeatability, and scalability that manual installations simply can’t match. Whether you’re managing a handful of development servers or hundreds of production nodes, these playbooks give you the foundation for reliable container infrastructure. The key is starting with basic installation, then layering on security, monitoring, and optimization as your needs grow.
For additional resources, check the official Docker installation documentation and Ansible Docker modules for more advanced use cases and configuration options.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.