Docker & Container Management

What is Docker Management with Ansible? Ansible provides comprehensive modules for managing Docker containers, images, networks, volumes, and Docker Compose deployments. This enables Infrastructure as Code for containerized applications with full lifecycle management.

Understanding Docker Automation

Why Automate Docker with Ansible?

Ansible simplifies container management:

  • Declarative Configuration: Define desired container state
  • Multi-Host Management: Manage containers across many hosts
  • Idempotency: Safe to run repeatedly
  • Integration: Combine containers with traditional infra
  • No Agents: Manage Docker via API or SSH

Use Cases

  • Deploy and manage containerized applications
  • Configure Docker hosts and registries
  • Orchestrate multi-container applications
  • Manage container lifecycle (start, stop, update)
  • Build and push custom images

Prerequisites

Install Docker Collection

# Install community.docker collection
ansible-galaxy collection install community.docker

# Install on target hosts: Docker Python SDK
pip install docker

Install Docker on Target Hosts

---
- name: Install Docker
  hosts: docker_hosts
  become: true
  tasks:
    - name: Install Docker (Ubuntu/Debian)
      ansible.builtin.apt:
        name:
          - docker.io
          - python3-docker
        state: present
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: Install Docker (RHEL/CentOS)
      ansible.builtin.yum:
        name:
          - docker
          - python3-docker
        state: present
      when: ansible_os_family == "RedHat"

    - name: Start Docker service
      ansible.builtin.service:
        name: docker
        state: started
        enabled: true

    - name: Add user to docker group
      ansible.builtin.user:
        name: "{{ ansible_user }}"
        groups: docker
        append: yes

Container Management

Running Containers

---
- name: Manage Docker containers
  hosts: docker_hosts
  tasks:
    - name: Run nginx container
      community.docker.docker_container:
        name: my_nginx
        image: nginx:latest
        state: started
        ports:
          - "8080:80"
        env:
          NGINX_HOST: example.com
          NGINX_PORT: "80"

    - name: Run container with volumes
      community.docker.docker_container:
        name: webapp
        image: myapp:v1.0
        state: started
        volumes:
          - /host/data:/container/data
          - /host/logs:/var/log/app
        restart_policy: unless-stopped

    - name: Run container with custom network
      community.docker.docker_container:
        name: database
        image: postgres:15
        state: started
        networks:
          - name: app_network
        env:
          POSTGRES_PASSWORD: "{{ db_password }}"
          POSTGRES_DB: myapp

Container Lifecycle Management

---
- name: Container lifecycle operations
  hosts: docker_hosts
  tasks:
    - name: Ensure container is running
      community.docker.docker_container:
        name: myapp
        image: myapp:latest
        state: started

    - name: Stop container
      community.docker.docker_container:
        name: myapp
        state: stopped

    - name: Restart container
      community.docker.docker_container:
        name: myapp
        state: started
        restart: yes

    - name: Remove container
      community.docker.docker_container:
        name: myapp
        state: absent

    - name: Update container (pull new image and recreate)
      community.docker.docker_container:
        name: myapp
        image: myapp:v2.0
        state: started
        recreate: yes
        pull: yes

Advanced Container Configuration

---
- name: Advanced container settings
  hosts: docker_hosts
  tasks:
    - name: Container with resource limits
      community.docker.docker_container:
        name: resource_limited
        image: myapp:latest
        state: started
        memory: 512m
        memory_reservation: 256m
        cpus: 1.5
        cpu_shares: 1024

    - name: Container with health check
      community.docker.docker_container:
        name: monitored_app
        image: myapp:latest
        state: started
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost/health"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 40s

    - name: Container with labels
      community.docker.docker_container:
        name: labeled_container
        image: nginx:latest
        state: started
        labels:
          environment: production
          app: frontend
          version: "1.0"

    - name: Privileged container
      community.docker.docker_container:
        name: privileged_app
        image: special:latest
        state: started
        privileged: yes
        devices:
          - /dev/sda:/dev/xvda:rwm

Image Management

Pulling and Managing Images

---
- name: Manage Docker images
  hosts: docker_hosts
  tasks:
    - name: Pull image
      community.docker.docker_image:
        name: nginx
        tag: latest
        source: pull

    - name: Pull image from private registry
      community.docker.docker_image:
        name: registry.example.com/myapp
        tag: v1.0
        source: pull
        registry_url: registry.example.com
        registry_username: "{{ registry_user }}"
        registry_password: "{{ registry_pass }}"

    - name: Remove unused images
      community.docker.docker_prune:
        images: yes
        images_filters:
          dangling: true

    - name: Remove specific image
      community.docker.docker_image:
        name: old_image
        tag: deprecated
        state: absent

Building Images

---
- name: Build Docker images
  hosts: docker_hosts
  tasks:
    - name: Build image from Dockerfile
      community.docker.docker_image:
        name: myapp
        tag: v1.0
        source: build
        build:
          path: /path/to/docker/context
          dockerfile: Dockerfile
          pull: yes
          args:
            BUILD_VERSION: "1.0.0"
            ENVIRONMENT: production

    - name: Build and push to registry
      community.docker.docker_image:
        name: registry.example.com/myapp
        tag: "{{ version }}"
        source: build
        build:
          path: ./app
        push: yes
        registry_url: registry.example.com
        registry_username: "{{ registry_user }}"
        registry_password: "{{ registry_pass }}"

    - name: Build multi-stage image
      community.docker.docker_image:
        name: optimized_app
        tag: latest
        source: build
        build:
          path: .
          dockerfile: Dockerfile.multi
          target: production

Network Management

Creating and Managing Networks

---
- name: Manage Docker networks
  hosts: docker_hosts
  tasks:
    - name: Create bridge network
      community.docker.docker_network:
        name: app_network
        driver: bridge
        ipam_config:
          - subnet: 172.20.0.0/16
            gateway: 172.20.0.1

    - name: Create overlay network (Swarm)
      community.docker.docker_network:
        name: overlay_network
        driver: overlay
        attachable: yes
        scope: swarm

    - name: Remove network
      community.docker.docker_network:
        name: old_network
        state: absent
        force: yes

    - name: Connect container to network
      community.docker.docker_container:
        name: webapp
        image: myapp:latest
        networks:
          - name: app_network
            ipv4_address: 172.20.0.10
          - name: monitoring_network

Volume Management

Creating and Managing Volumes

---
- name: Manage Docker volumes
  hosts: docker_hosts
  tasks:
    - name: Create named volume
      community.docker.docker_volume:
        name: app_data
        state: present

    - name: Create volume with driver options
      community.docker.docker_volume:
        name: nfs_volume
        driver: local
        driver_options:
          type: nfs
          o: addr=nfs-server.example.com,rw
          device: ":/exports/data"

    - name: Remove volume
      community.docker.docker_volume:
        name: old_volume
        state: absent
        force: yes

    - name: Container with volume mounts
      community.docker.docker_container:
        name: data_container
        image: postgres:15
        volumes:
          - app_data:/var/lib/postgresql/data
          - /host/backup:/backup:ro

Docker Compose

Using Docker Compose Module

---
- name: Manage Docker Compose applications
  hosts: docker_hosts
  tasks:
    - name: Deploy with docker-compose v2
      community.docker.docker_compose_v2:
        project_src: /path/to/compose/project
        state: present

    - name: Start services
      community.docker.docker_compose_v2:
        project_src: /path/to/project
        state: present
        services:
          - web
          - db

    - name: Stop and remove
      community.docker.docker_compose_v2:
        project_src: /path/to/project
        state: absent
        remove_volumes: yes

    - name: Pull and recreate
      community.docker.docker_compose_v2:
        project_src: /path/to/project
        state: present
        pull: always
        recreate: always

Managing Compose Files

---
- name: Deploy application with compose
  hosts: docker_hosts
  tasks:
    - name: Create project directory
      ansible.builtin.file:
        path: /opt/myapp
        state: directory

    - name: Template docker-compose.yml
      ansible.builtin.template:
        src: docker-compose.yml.j2
        dest: /opt/myapp/docker-compose.yml

    - name: Template environment file
      ansible.builtin.template:
        src: .env.j2
        dest: /opt/myapp/.env

    - name: Deploy compose application
      community.docker.docker_compose_v2:
        project_src: /opt/myapp
        state: present
        pull: always

# templates/docker-compose.yml.j2
---
version: '3.8'

services:
  web:
    image: nginx:{{ nginx_version }}
    ports:
      - "{{ web_port }}:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    environment:
      NGINX_HOST: {{ domain_name }}
    networks:
      - frontend

  app:
    image: {{ app_image }}:{{ app_version }}
    environment:
      DATABASE_URL: postgresql://{{ db_user }}:{{ db_password }}@db:5432/{{ db_name }}
      REDIS_URL: redis://cache:6379
    networks:
      - frontend
      - backend
    depends_on:
      - db
      - cache

  db:
    image: postgres:{{ postgres_version }}
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: {{ db_user }}
      POSTGRES_PASSWORD: {{ db_password }}
      POSTGRES_DB: {{ db_name }}
    networks:
      - backend

  cache:
    image: redis:{{ redis_version }}
    networks:
      - backend

volumes:
  db_data:

networks:
  frontend:
  backend:

Complete Application Deployment

Multi-Container Application

---
- name: Deploy full application stack
  hosts: docker_hosts
  become: true
  vars:
    app_version: "1.0.0"
    environment: production

  tasks:
    - name: Create application network
      community.docker.docker_network:
        name: myapp_network
        driver: bridge

    - name: Create data volumes
      community.docker.docker_volume:
        name: "{{ item }}"
        state: present
      loop:
        - postgres_data
        - redis_data
        - app_uploads

    - name: Deploy PostgreSQL database
      community.docker.docker_container:
        name: myapp_postgres
        image: postgres:15
        state: started
        restart_policy: unless-stopped
        networks:
          - name: myapp_network
        volumes:
          - postgres_data:/var/lib/postgresql/data
        env:
          POSTGRES_USER: "{{ db_user }}"
          POSTGRES_PASSWORD: "{{ db_password }}"
          POSTGRES_DB: "{{ db_name }}"
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U {{ db_user }}"]
          interval: 10s
          timeout: 5s
          retries: 5

    - name: Deploy Redis cache
      community.docker.docker_container:
        name: myapp_redis
        image: redis:7-alpine
        state: started
        restart_policy: unless-stopped
        networks:
          - name: myapp_network
        volumes:
          - redis_data:/data

    - name: Deploy application
      community.docker.docker_container:
        name: myapp
        image: "registry.example.com/myapp:{{ app_version }}"
        state: started
        restart_policy: unless-stopped
        networks:
          - name: myapp_network
        volumes:
          - app_uploads:/app/uploads
        env:
          DATABASE_URL: "postgresql://{{ db_user }}:{{ db_password }}@myapp_postgres:5432/{{ db_name }}"
          REDIS_URL: "redis://myapp_redis:6379"
          ENVIRONMENT: "{{ environment }}"
        depends_on:
          - myapp_postgres
          - myapp_redis

    - name: Deploy nginx proxy
      community.docker.docker_container:
        name: myapp_nginx
        image: nginx:alpine
        state: started
        restart_policy: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        networks:
          - name: myapp_network
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf:ro
          - ./ssl:/etc/nginx/ssl:ro

Docker Registry Management

Login to Registry

---
- name: Docker registry operations
  hosts: docker_hosts
  tasks:
    - name: Login to Docker Hub
      community.docker.docker_login:
        username: "{{ docker_hub_user }}"
        password: "{{ docker_hub_password }}"

    - name: Login to private registry
      community.docker.docker_login:
        registry_url: registry.example.com
        username: "{{ registry_user }}"
        password: "{{ registry_password }}"

    - name: Logout from registry
      community.docker.docker_login:
        state: absent
        registry_url: registry.example.com

Cleanup and Maintenance

Pruning Resources

---
- name: Docker cleanup tasks
  hosts: docker_hosts
  tasks:
    - name: Remove dangling images
      community.docker.docker_prune:
        images: yes
        images_filters:
          dangling: true

    - name: Remove unused containers
      community.docker.docker_prune:
        containers: yes
        containers_filters:
          until: 24h

    - name: Remove unused volumes
      community.docker.docker_prune:
        volumes: yes

    - name: Remove unused networks
      community.docker.docker_prune:
        networks: yes

    - name: Full system prune
      community.docker.docker_prune:
        containers: yes
        images: yes
        networks: yes
        volumes: yes
        builder_cache: yes

Gathering Container Information

Inspect Containers and Images

---
- name: Get Docker information
  hosts: docker_hosts
  tasks:
    - name: Get container info
      community.docker.docker_container_info:
        name: myapp
      register: container_info

    - name: Display container status
      ansible.builtin.debug:
        msg: "Container {{ container_info.container.Name }} is {{ container_info.container.State.Status }}"

    - name: List all containers
      community.docker.docker_host_info:
        containers: yes
      register: docker_info

    - name: Get image info
      community.docker.docker_image_info:
        name: nginx:latest
      register: image_info

Best Practices

Docker Management Best Practices:
  • Use Specific Tags: Avoid 'latest' tag in production
  • Health Checks: Implement container health checks
  • Resource Limits: Set memory and CPU limits
  • Named Volumes: Use named volumes for persistent data
  • Networks: Isolate containers with custom networks
  • Restart Policies: Configure appropriate restart behavior
  • Secrets: Use secrets management, not environment variables
  • Regular Cleanup: Prune unused resources periodically

Common Issues

Python Docker SDK Missing

# Install on target hosts
- name: Install Docker SDK
  ansible.builtin.pip:
    name: docker
    state: present

Permission Denied

  • Add user to docker group
  • Use become: true in playbooks
  • Restart SSH session after adding to docker group

Port Already Allocated

  • Check for existing containers using the port
  • Use different host port mapping
  • Stop conflicting services

Next Steps