AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible for Docker and Podman: Container Automation Complete Guide

By Luca Berton · Published 2024-01-01 · Category: installation

Automate Docker and Podman with Ansible. Build images, manage containers, configure networks and volumes, deploy with docker-compose, manage registries.

Why Ansible for Container Management?

Docker CLI and docker-compose handle single-host container workflows well. But when you need to deploy containers across dozens of servers, enforce consistent configuration, manage secrets, coordinate rolling updates, and integrate with existing infrastructure automation — Ansible fills the gap.

Ansible treats containers like any other infrastructure resource: declarative, idempotent, and version-controlled.

See also: Ansible Execution Environments: Build Custom EEs for Enterprise Automation

Collections

# Docker modules
ansible-galaxy collection install community.docker

# Podman modules
ansible-galaxy collection install containers.podman

# Dependencies
pip install docker    # For community.docker

Docker Container Management

Run Containers

---
- name: Deploy application containers
  hosts: docker_hosts
  become: true

  tasks:
    - name: Run nginx container
      community.docker.docker_container:
        name: nginx-proxy
        image: nginx:1.27-alpine
        state: started
        restart_policy: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /etc/nginx/conf.d:/etc/nginx/conf.d:ro
          - /etc/letsencrypt:/etc/letsencrypt:ro
          - nginx-logs:/var/log/nginx
        env:
          TZ: "UTC"
        labels:
          app: proxy
          environment: production
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost/"]
          interval: 30s
          timeout: 10s
          retries: 3

    - name: Run PostgreSQL
      community.docker.docker_container:
        name: postgres
        image: postgres:16-alpine
        state: started
        restart_policy: unless-stopped
        ports:
          - "5432:5432"
        volumes:
          - pgdata:/var/lib/postgresql/data
          - /backups/postgres:/backups
        env:
          POSTGRES_USER: "{{ vault_pg_user }}"
          POSTGRES_PASSWORD: "{{ vault_pg_password }}"
          POSTGRES_DB: app
        memory: "2g"
        cpus: 2
        shm_size: "256m"

    - name: Run Redis
      community.docker.docker_container:
        name: redis
        image: redis:7-alpine
        state: started
        restart_policy: unless-stopped
        command: redis-server --requirepass {{ vault_redis_password }} --maxmemory 512mb --maxmemory-policy allkeys-lru
        ports:
          - "127.0.0.1:6379:6379"
        volumes:
          - redis-data:/data

Build Images

---
- name: Build and push Docker images
  hosts: build_server
  become: true

  vars:
    app_version: "{{ lookup('env', 'APP_VERSION') | default('latest') }}"
    registry: registry.company.com

  tasks:
    - name: Build application image
      community.docker.docker_image:
        name: "{{ registry }}/myapp"
        tag: "{{ app_version }}"
        source: build
        build:
          path: /opt/app
          dockerfile: Dockerfile
          args:
            NODE_ENV: production
            BUILD_DATE: "{{ ansible_date_time.iso8601 }}"
          pull: true
          nocache: "{{ force_rebuild | default(false) }}"
        push: true
        force_source: true

    - name: Tag as latest
      community.docker.docker_image:
        name: "{{ registry }}/myapp"
        tag: latest
        source: local
        repository: "{{ registry }}/myapp"
        push: true
        force_tag: true

Docker Networks

    - name: Create application network
      community.docker.docker_network:
        name: app-network
        driver: bridge
        ipam_config:
          - subnet: 172.20.0.0/16
            gateway: 172.20.0.1
        driver_options:
          com.docker.network.bridge.name: br-app

    - name: Create overlay network (Swarm)
      community.docker.docker_network:
        name: service-mesh
        driver: overlay
        attachable: true
        encrypted: true

Docker Volumes

    - name: Create named volumes
      community.docker.docker_volume:
        name: "{{ item }}"
        state: present
      loop:
        - pgdata
        - redis-data
        - app-uploads
        - nginx-logs

    - name: Create NFS volume
      community.docker.docker_volume:
        name: shared-data
        driver: local
        driver_options:
          type: nfs
          o: "addr=10.0.1.100,rw,nfsvers=4"
          device: ":/exports/shared"

Docker Compose with Ansible

---
- name: Deploy with docker-compose
  hosts: app_servers
  become: true

  tasks:
    - name: Deploy compose stack
      community.docker.docker_compose_v2:
        project_src: /opt/app
        files:
          - docker-compose.yml
          - docker-compose.prod.yml
        state: present
        pull: always
        env_files:
          - .env.production
      register: compose_result

    - name: Show deployment result
      ansible.builtin.debug:
        msg: "Services: {{ compose_result.services | map(attribute='name') | list }}"

    - name: Wait for app to be healthy
      ansible.builtin.uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: health
      retries: 30
      delay: 5
      until: health.status == 200

Registry Management

    - name: Log into private registry
      community.docker.docker_login:
        registry_url: registry.company.com
        username: "{{ vault_registry_user }}"
        password: "{{ vault_registry_password }}"

    - name: Pull specific image
      community.docker.docker_image:
        name: registry.company.com/myapp
        tag: "{{ deploy_version }}"
        source: pull

    - name: Prune old images
      community.docker.docker_prune:
        images: true
        images_filters:
          until: 168h  # 7 days
        volumes: true
        builder_cache: true

See also: Ansible with Podman: Manage Containers Using Inventory & Modules (Guide)

Podman Container Management

Podman is the rootless, daemonless alternative to Docker — increasingly standard on RHEL, Fedora, and CentOS.

Run Podman Containers

---
- name: Deploy with Podman
  hosts: podman_hosts
  become: true

  tasks:
    - name: Run nginx with Podman
      containers.podman.podman_container:
        name: nginx
        image: docker.io/nginx:1.27-alpine
        state: started
        restart_policy: always
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /etc/nginx/conf.d:/etc/nginx/conf.d:ro,Z
          - nginx-logs:/var/log/nginx:Z
        label:
          app: proxy

    - name: Run rootless container (user scope)
      containers.podman.podman_container:
        name: dev-app
        image: docker.io/node:22-alpine
        state: started
        user: "1000"
        userns: keep-id
        ports:
          - "3000:3000"
        volumes:
          - ./app:/app:Z
        command: node /app/server.js
      become: false  # Run as regular user

Podman Pods (Like Kubernetes Pods)

    - name: Create application pod
      containers.podman.podman_pod:
        name: webapp
        state: started
        ports:
          - "8080:80"
          - "5432:5432"
        infra: true

    - name: Run app in pod
      containers.podman.podman_container:
        name: webapp-app
        image: myapp:latest
        pod: webapp
        state: started

    - name: Run database in same pod
      containers.podman.podman_container:
        name: webapp-db
        image: postgres:16-alpine
        pod: webapp
        state: started
        env:
          POSTGRES_PASSWORD: "{{ vault_pg_password }}"

Generate Systemd Units from Podman

    - name: Generate systemd service for container
      containers.podman.podman_container:
        name: myapp
        image: myapp:latest
        state: started
        generate_systemd:
          path: /etc/systemd/system/
          restart_policy: always
          names: true
          new: true

    - name: Enable container service
      ansible.builtin.systemd:
        name: container-myapp
        enabled: true
        daemon_reload: true

Podman Compose Alternative — Quadlet

    # Quadlet: systemd-native container management (Podman 4.4+)
    - name: Deploy Quadlet container unit
      ansible.builtin.copy:
        content: |
          [Container]
          Image=docker.io/nginx:1.27-alpine
          PublishPort=80:80
          Volume=/etc/nginx/conf.d:/etc/nginx/conf.d:ro,Z
          AutoUpdate=registry

          [Service]
          Restart=always

          [Install]
          WantedBy=default.target
        dest: /etc/containers/systemd/nginx.container
        mode: '0644'
      notify: reload systemd

    - name: Deploy Quadlet network unit
      ansible.builtin.copy:
        content: |
          [Network]
          Subnet=172.20.0.0/16
          Gateway=172.20.0.1

          [Install]
          WantedBy=default.target
        dest: /etc/containers/systemd/app.network
        mode: '0644'
      notify: reload systemd

Install Docker with Ansible

---
- name: Install Docker Engine
  hosts: all
  become: true

  tasks:
    - name: Install prerequisites
      ansible.builtin.apt:
        name:
          - ca-certificates
          - curl
          - gnupg
        state: present
        update_cache: true

    - name: Add Docker GPG key
      ansible.builtin.get_url:
        url: https://download.docker.com/linux/ubuntu/gpg
        dest: /etc/apt/keyrings/docker.asc
        mode: '0644'

    - name: Add Docker repository
      ansible.builtin.apt_repository:
        repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
        state: present

    - name: Install Docker
      ansible.builtin.apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-buildx-plugin
          - docker-compose-plugin
        state: present
        update_cache: true

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

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

See also: Ansible Builder & Execution Environments: Complete Guide (2026)

Rolling Updates

---
- name: Rolling container update
  hosts: app_servers
  serial: 1
  become: true

  tasks:
    - name: Pull new image
      community.docker.docker_image:
        name: registry.company.com/myapp
        tag: "{{ deploy_version }}"
        source: pull

    - name: Remove from load balancer
      ansible.builtin.uri:
        url: "http://lb.company.com/api/pool/{{ inventory_hostname }}"
        method: DELETE
      delegate_to: localhost

    - name: Wait for connections to drain
      ansible.builtin.pause:
        seconds: 30

    - name: Update container
      community.docker.docker_container:
        name: myapp
        image: "registry.company.com/myapp:{{ deploy_version }}"
        state: started
        restart: true
        restart_policy: unless-stopped
        ports:
          - "8080:8080"

    - name: Wait for health check
      ansible.builtin.uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: health
      retries: 30
      delay: 5
      until: health.status == 200

    - name: Re-add to load balancer
      ansible.builtin.uri:
        url: "http://lb.company.com/api/pool"
        method: POST
        body_format: json
        body:
          host: "{{ inventory_hostname }}"
          port: 8080
      delegate_to: localhost

FAQ

Should I use Ansible or docker-compose?

Use docker-compose for single-host development environments. Use Ansible when you need to deploy containers across multiple servers, integrate with non-container infrastructure (networking, storage, DNS), manage secrets with Vault, or coordinate rolling updates. They're complementary — Ansible can deploy docker-compose files with community.docker.docker_compose_v2.

Should I use Docker or Podman?

Docker is the industry standard with the largest ecosystem. Podman is rootless by default, daemonless, and the default on RHEL/Fedora. If you're running RHEL-based systems or need rootless containers for security, use Podman. For most other cases, Docker works well. Ansible supports both equally.

Can Ansible build Docker images?

Yes. community.docker.docker_image with source: build builds images from a Dockerfile. For complex CI/CD pipelines, dedicated tools like Buildah, Kaniko, or GitHub Actions may be more appropriate, but Ansible handles simple builds well.

How do I manage secrets in containerized apps?

Use ansible.builtin.vault to encrypt secrets, then pass them as environment variables to containers via the env parameter. For Docker Swarm, use Docker secrets. For Kubernetes, use Kubernetes secrets managed by Ansible's kubernetes.core collection.

Conclusion

Ansible automates the full container lifecycle — install Docker/Podman, build images, manage registries, deploy containers with networking and volumes, orchestrate rolling updates, and clean up unused resources. Use community.docker for Docker and containers.podman for Podman. For production deployments across multiple hosts, Ansible's idempotent approach ensures consistent, repeatable container infrastructure.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home