Introduction
community.docker.docker_compose_v2 is the Ansible module for deploying and managing Docker Compose stacks — it wraps the docker compose CLI to bring up, update, and remove multi-container applications idempotently. This article covers the complete workflow from basic deployment to production patterns with health checks and rolling updates.
Prerequisites
# Install the collection
ansible-galaxy collection install community.docker
# Target hosts need:
# - Docker Engine 24+
# - Docker Compose V2 (docker compose, not docker-compose)
Basic Stack Deployment
---
- name: Deploy application stack
hosts: appservers
become: true
tasks:
- name: Create project directory
ansible.builtin.file:
path: /opt/myapp
state: directory
mode: '0755'
- name: Deploy docker-compose.yml
ansible.builtin.copy:
dest: /opt/myapp/docker-compose.yml
content: |
services:
web:
image: nginx:1.27
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- api
restart: unless-stopped
api:
image: myregistry.example.com/myapi:latest
environment:
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
restart: unless-stopped
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: unless-stopped
cache:
image: redis:7-alpine
restart: unless-stopped
volumes:
pgdata:
- name: Deploy .env file
ansible.builtin.copy:
dest: /opt/myapp/.env
content: |
DB_PASSWORD={{ vault_db_password }}
mode: '0600'
no_log: true
- name: Start stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
register: output
- name: Show running services
ansible.builtin.debug:
msg: "{{ output.containers | map(attribute='Name') | list }}"
Module Reference
community.docker.docker_compose_v2
| Parameter | Default | Description |
|---|---|---|
project_src | — | Path to directory containing docker-compose.yml |
project_name | directory name | Override project name |
files | — | List of compose files (override default) |
state | present | present, absent, stopped, restarted |
pull | policy | always, missing, never |
build | policy | always, never |
remove_orphans | false | Remove containers for undefined services |
recreate | auto | always, never, auto |
services | — | Specific services to manage |
profiles | — | Compose profiles to activate |
env_files | — | Additional .env files |
timeout | — | Shutdown timeout in seconds |
Common Operations
Pull and Recreate (Update)
- name: Update application
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
recreate: always
services:
- api
- web
Stop Stack
- name: Stop all services
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: stopped
Remove Stack
- name: Remove stack and volumes
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: absent
remove_orphans: true
Restart Specific Service
- name: Restart API service
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: restarted
services:
- api
Template-Based Compose Files
---
- name: Deploy with templated compose
hosts: appservers
become: true
vars:
app_version: "1.5.0"
app_replicas: 3
db_version: "16"
domain: app.example.com
tasks:
- name: Deploy compose file from template
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /opt/myapp/docker-compose.yml
mode: '0644'
register: compose_file
- name: Apply changes
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
recreate: "{{ 'always' if compose_file.changed else 'auto' }}"
# templates/docker-compose.yml.j2
services:
web:
image: myregistry.example.com/web:{{ app_version }}
ports:
- "443:443"
environment:
DOMAIN: {{ domain }}
deploy:
replicas: {{ app_replicas }}
restart: unless-stopped
db:
image: postgres:{{ db_version }}
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: {{ vault_db_password }}
restart: unless-stopped
volumes:
pgdata:
Health Check Verification
- name: Deploy and verify health
hosts: appservers
become: true
tasks:
- name: Start stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
- name: Wait for API health check
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 5
- name: Verify all containers healthy
community.docker.docker_container_info:
name: "myapp-{{ item }}-1"
register: container_info
loop:
- web
- api
- db
failed_when: container_info.container.State.Status != 'running'
Rolling Updates Across Hosts
---
- name: Rolling update of Docker Compose stack
hosts: appservers
become: true
serial: 1 # One host at a time
max_fail_percentage: 0 # Stop on any failure
tasks:
- name: Pull new images
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
recreate: always
services: [api, web]
- name: Wait for health check
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 5
- name: Verify service responding
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}/api/version"
return_content: true
register: version
- name: Display deployed version
ansible.builtin.debug:
msg: "{{ inventory_hostname }}: {{ version.json.version }}"
Manage Docker Networks
- name: Create shared network
community.docker.docker_network:
name: app-network
driver: bridge
ipam_config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
# Reference in compose
# networks:
# default:
# external: true
# name: app-network
Backup Database Before Update
- name: Safe update with backup
hosts: appservers
become: true
tasks:
- name: Backup database
community.docker.docker_container_exec:
container: myapp-db-1
command: pg_dump -U app myapp
register: db_dump
- name: Save backup
ansible.builtin.copy:
content: "{{ db_dump.stdout }}"
dest: "/backups/myapp-{{ ansible_date_time.iso8601_basic_short }}.sql"
- name: Update stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
recreate: always
Troubleshooting
"docker compose" Not Found
# Ensure Docker Compose V2 plugin is installed
- name: Install Docker Compose V2
ansible.builtin.apt:
name: docker-compose-plugin
state: present
Permission Denied
# Add ansible user to docker group
- name: Add user to docker group
ansible.builtin.user:
name: ansible
groups: docker
append: true
Container Won't Start
# Check logs
- name: Get container logs
community.docker.docker_container_info:
name: myapp-api-1
register: info
- name: Show logs
ansible.builtin.command: docker compose -f /opt/myapp/docker-compose.yml logs api --tail=50
register: logs
- ansible.builtin.debug:
var: logs.stdout_lines
Best Practices
- Template compose files — use Jinja2 for environment-specific config
- Pin image tags — never use
:latestin production - Use
.envfiles for secrets — deploy withmode: 0600andno_log: true - Health checks in compose — let Docker monitor container health
- Rolling updates with
serial: 1— update one host at a time - Backup before update — especially databases
remove_orphans: true— clean up old services after refactoring- Separate data volumes — never store data in containers
Related Articles
Conclusion
The community.docker.docker_compose_v2 module manages Docker Compose stacks declaratively through Ansible — deploy, update, restart, and remove multi-container applications. Use templated compose files for environment-specific configuration, rolling updates with serial: 1 for zero-downtime deployments, and health check verification to confirm each host is serving before moving to the next.