BLOG POSTS
    MangoHost Blog / How to Use Ansible to Install and Set Up Docker on Ubuntu 24
How to Use Ansible to Install and Set Up Docker on Ubuntu 24

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.

Leave a reply

Your email address will not be published. Required fields are marked