13 KiB
Docker Compose App Role
A flexible Ansible role for deploying and managing Docker Compose applications with features including backup/restore and healthchecks.
Overview
This role provides a standardized way to deploy containerized applications using Docker Compose. It handles directory creation, template rendering, and container lifecycle management.
Requirements
- Collections:
community.docker(>=1.10.0) andansible.posix(>=1.4.0). Both are declared as collection dependencies and installed automatically with the collection. - The backup workflow copies archives to the controller with
ansible.posix.synchronize, sorsyncmust be installed on both the controller and the managed host.
Features
- Template-based deployment with Jinja2 templating
- Dual backup system (controller + remote host)
- Health checks and deployment verification
- Multiple deployment modes (template, file, or inline content)
- Flexible directory management
- Configurable backup retention policies
Quick Start
1. Create a New Role
Use the provided script to create a new application skeleton role:
./create_app_role.sh my-app
This creates:
roles/my-app/
├── defaults/main.yml # Default variables
├── meta/main.yml # Role metadata and dependencies
└── templates/
└── compose.yml.j2 # Docker Compose template
2. Configure Your Application
Edit roles/my-app/defaults/main.yml:
---
app_role_name: my-app
my_app_container_name: "{{ app_name | default('my-app') }}"
my_app_container_version: latest
my_app_restart_policy: "{{ app_restart_policy }}"
my_app_http_port: 8080
my_app_data_path: "{{ app_dir }}/data"
my_app_config_path: "{{ app_dir }}/config"
# Optional: directories to create
app_subdirectories:
- "{{ my_app_data_path }}"
- "{{ my_app_config_path }}"
# Optional: backup configuration
app_backup_subdirectories:
- "{{ my_app_data_path }}"
- "{{ my_app_config_path }}"
3. Create Docker Compose Template
Edit roles/my-app/templates/compose.yml.j2:
---
services:
my-app:
image: "my-app:{{ my_app_container_version }}"
container_name: "{{ my_app_container_name }}"
restart: "{{ my_app_restart_policy }}"
ports:
- "{{ my_app_http_port }}:8080"
volumes:
- "{{ my_app_data_path }}:/data"
- "{{ my_app_config_path }}:/config"
environment:
- TZ={{ app_timezone | default('UTC') }}
4. Deploy Your Application
Add to your playbook:
---
- name: Deploy my application
hosts: localhost
roles:
- role: my-app
Advanced Usage
Backup and Restore
Create a New Backup
Create an on-demand backup using tags:
# Backup a specific application
ansible-playbook --tags backup playbook.yml
Configure backup retention and options in your role's meta vars:
# In roles/my-app/meta/main.yml
dependencies:
- role: patrickj.docker_apps.docker_compose_app
vars:
app_backup_subdirectories:
- "{{ my_app_data_path }}"
- "{{ my_app_config_path }}"
app_backup_retention_days_controller: 90 # Keep backups for 90 days
app_backup_retention_days_remote: 7 # Keep remote backups for 7 days
app_backup_stop_services: true # Stop containers during backup
When app_backup_stop_services is enabled the containers are stopped only while the tarball is created on the managed host, then restarted immediately — the transfer of the archive back to the controller (via rsync) happens after the application is running again, so downtime is limited to the snapshot itself.
Automated Backups
For scheduled backups:
# Run daily backup at 2 AM - only runs backup tasks
0 2 * * * ansible-playbook -i inventory --tags backup my-playbook.yml
Tag-Based Execution
The role supports selective execution using tags:
# Setup only - doesn't start the containers
ansible-playbook --tags setup playbook.yml
# Full deployment - setup + start containers
ansible-playbook --tags deploy playbook.yml
# Restore and deploy
ansible-playbook --tags restore,deploy playbook.yml
# Backup only
ansible-playbook --tags backup playbook.yml
# Update only
ansible-playbook --tags update playbook.yml
# Backup then update
ansible-playbook --tags backup,update playbook.yml
Restore from Backup
Restore from the latest backup:
# Setup directories, restore from backup, then deploy
ansible-playbook --tags setup,restore,deploy playbook.yml
Configure restore options in your role's meta vars:
# In roles/my-app/meta/main.yml
dependencies:
- role: patrickj.docker_apps.docker_compose_app
vars:
app_restore_source: controller # or 'remote'
app_restore_max_age_days: 30 # Only restore backups newer than 30 days
Restore from a specific archive by setting the variable at runtime:
ansible-playbook --tags restore playbook.yml \
-e app_restore_archive=/path/to/specific/backup-20240315-120000.tar.gz
Health Checks
Configure health monitoring:
- role: my-app
vars:
app_name: my-app
app_health_check: true
app_health_check_method: http
app_health_check_url: "http://localhost:8080/health"
app_health_check_retries: 30
app_health_check_delay: 10
Updates
Update application containers using tags:
# Update a specific application
ansible-playbook --tags update playbook.yml
# Update all applications in a playbook
ansible-playbook --tags update site.yml
Update process:
- Stops the application
- Pulls images according to
app_compose_pull(defaults topolicy; set toalwaysto force fresh pulls) - Recreates containers with new images
- Runs health checks (if enabled)
Rollbacks
Rollback using the restore functionality:
# First, create a backup then update
ansible-playbook --tags backup,update playbook.yml
# If update fails, rollback from backup
ansible-playbook --tags restore playbook.yml
Important Notes:
- Updates do NOT create automatic backups - create backups manually beforehand
- Rollbacks must be triggered manually using the restore functionality
- Using
latesttags prevents effective rollbacks since the old image is overwritten - For production, use specific version tags instead of
latest
Multiple Instances
Deploy multiple instances of the same application:
- role: jellyfin
vars:
app_name: jellyfin-movies
jellyfin_http_port: 8096
- role: jellyfin
vars:
app_name: jellyfin-tv
jellyfin_http_port: 8097
Custom Templates and Files
Override template location:
- role: my-app
vars:
app_compose_template: /path/to/custom/compose.yml.j2
Use a static compose file:
If you have an existing docker-compose.yml file that doesn't need templating:
- role: my-app
vars:
app_name: my-app
app_compose_file: /path/to/existing/docker-compose.yml
The file will be copied as-is to the application directory without Jinja2 processing.
Use inline content:
- role: patrickj.docker_apps.docker_compose_app
vars:
app_name: simple-app
app_role_name: simple-app
app_compose_content: |
services:
app:
image: nginx:alpine
ports:
- "80:80"
Configuration Reference
Required Externals
These variables have no defaults and must be set by the calling playbook (typically as group_vars or host_vars). They are consumed by the defaults shown further down; if you override every derived variable that uses them, you can leave the corresponding external unset.
| Variable | Description | Consumed by |
|---|---|---|
host_root_path |
Base directory on the target host under which each app's directory is created (e.g. /srv/apps, /opt/docker) |
app_dir |
backup_path |
Base directory on the controller where fetched backup archives are stored (e.g. ~/backups) |
app_backup_path |
The templates/ directory of the calling shim role is discovered automatically via ansible_parent_role_paths — no path variable to set.
Core Variables
| Variable | Default | Description |
|---|---|---|
app_name |
Required | Unique application instance name |
app_dir |
{{ host_root_path }}/{{ app_name }} |
Application directory on the target host |
app_uid |
{{ ansible_facts.user_uid }} |
File ownership UID |
app_gid |
{{ ansible_facts.user_gid }} |
File ownership GID |
app_permission_mode |
"0640" |
File permission mode |
Deployment Options
| Variable | Default | Description |
|---|---|---|
app_compose_template |
compose.yml.j2 |
Template file path — relative names resolve via Ansible's role templates search path (finds the calling shim role's templates/); absolute paths also accepted |
app_compose_file |
- | Static compose file path |
app_compose_content |
- | Inline compose content |
app_compose_validate |
true |
Validate compose syntax |
app_compose_pull |
policy |
Image pull strategy (also used by update — set to always to force pulls on update) |
app_compose_recreate |
auto |
Container recreate strategy |
app_compose_project_name |
{{ app_dir | basename }} |
Compose project name; override when your compose file sets a non-default project name |
Backup Configuration
| Variable | Default | Description |
|---|---|---|
app_backup |
true |
Whether backups run when invoked with --tags backup; set false to opt a host out |
app_backup_subdirectories |
[] |
Specific directories to backup |
app_backup_stop_services |
true |
Stop containers during backup |
app_backup_retention_days_controller |
90 |
Controller backup retention |
app_backup_retention_days_remote |
7 |
Remote backup retention |
Restore Configuration
| Variable | Default | Description |
|---|---|---|
app_restore_source |
controller |
Restore source (controller/remote) |
app_restore_archive |
- | Specific archive file to restore (overrides latest) |
app_restore_max_age_days |
30 |
Maximum backup age for restore (when finding latest) |
Health Check Configuration
| Variable | Default | Description |
|---|---|---|
app_health_check |
true |
Enable health checks |
app_health_check_method |
docker |
Check method (docker/http) |
app_container_name |
{{ app_name }} |
Container name inspected by the docker health check; override when your compose service uses a different container_name |
app_health_check_url |
- | HTTP endpoint for health check |
app_health_check_retries |
30 |
Number of check retries |
app_health_check_delay |
10 |
Delay between checks (seconds) |
app_health_check_status_codes |
[200, 201, 202] |
Valid HTTP status codes |
Directory Management
| Variable | Default | Description |
|---|---|---|
app_subdirectories |
[] |
Directories to create in app_dir |
app_extra_templates |
[] |
Additional templates to render |
Example app_extra_templates:
app_extra_templates:
- src: config.json.j2
dest: "{{ app_dir }}/config/app.json"
- src: env.j2
dest: "{{ app_dir }}/.env"
Best Practices
- Use semantic versioning for container tags instead of
latest - Test restore procedures regularly to ensure backups are valid
- Use specific directories for backups instead of entire app directory
- Monitor disk usage on backup storage locations
Architecture
The role is organized into logical task files for selective execution:
├── setup.yml # Validation, directories, and templates
├── restore.yml # Backup restoration logic
├── deploy.yml # Docker Compose deployment and startup
├── health_check.yml # Health monitoring and verification
├── backup.yml # Backup creation and management
├── update.yml # Container update and image pulling
└── manage_compose.yml # Docker Compose lifecycle management
Task Execution Flow
-
Setup (
setup.yml) - [setup, deploy]- Docker/Compose validation
- Variable validation and security checks
- Directory creation
- Additional template deployment
-
Restore (
restore.yml) - [restore, never]- Backup file selection and validation
- Service stopping
- Archive extraction
- Service restart
-
Deploy (
deploy.yml) - [deploy]- Docker Compose file creation
- Container startup and management
-
Health Check (
health_check.yml) - [deploy, healthcheck]- Service health verification
- Endpoint monitoring
-
Backup (
backup.yml) - [backup, never]- Service stopping (optional)
- Archive creation
- Backup copying and cleanup
- Service restart
-
Update (
update.yml) - [update, never]- Service stopping
- Image pulling
- Container recreation
- Health verification
Each task file is designed to be idempotent and can be run multiple times safely.