# 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. ## 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: ```bash ./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`: ```yaml --- 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`: ```yaml --- 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: ```yaml --- - 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: ```bash # Backup a specific application ansible-playbook --tags backup playbook.yml ``` Configure backup retention and options in your role's meta vars: ```yaml # In roles/my-app/meta/main.yml dependencies: - role: 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 ``` #### Automated Backups For scheduled backups: ```bash # 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: ```bash # 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: ```bash # Setup directories, restore from backup, then deploy ansible-playbook --tags setup,restore,deploy playbook.yml ``` Configure restore options in your role's meta vars: ```yaml # In roles/my-app/meta/main.yml dependencies: - role: 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: ```bash ansible-playbook --tags restore playbook.yml \ -e app_restore_archive=/path/to/specific/backup-20240315-120000.tar.gz ``` ### Health Checks Configure health monitoring: ```yaml - 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: ```bash # Update a specific application ansible-playbook --tags update playbook.yml # Update all applications in a playbook ansible-playbook --tags update site.yml ``` **Update process:** 1. Stops the application 2. Pulls latest images for all services 3. Recreates containers with new images 4. Runs health checks (if enabled) ### Rollbacks Rollback using the restore functionality: ```bash # 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 `latest` tags 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: ```yaml - 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: ```yaml - 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: ```yaml - 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: ```yaml - role: 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 ### Core Variables | Variable | Default | Description | |----------|---------|-------------| | `app_name` | Required | Unique application instance name | | `role_name` | Required | Role name for template paths | | `app_dir` | `{{ host_root_path }}/{{ app_name }}` | Application directory | | `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` | `{{ app_templates_path }}/compose.yml.j2` | Template file path | | `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 | | `app_compose_recreate` | `auto` | Container recreate strategy | ### Backup Configuration | Variable | Default | Description | |----------|---------|-------------| | `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_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`: ```yaml app_extra_templates: - src: config.json.j2 dest: "{{ app_dir }}/config/app.json" - src: env.j2 dest: "{{ app_dir }}/.env" ``` ## Best Practices 1. **Use semantic versioning** for container tags instead of `latest` 2. **Test restore procedures** regularly to ensure backups are valid 3. **Use specific directories** for backups instead of entire app directory 4. **Monitor disk usage** on backup storage locations ## Architecture The role is organized into logical task files for selective execution: ``` ├── setup.yaml # Validation, directories, and templates ├── restore.yaml # Backup restoration logic ├── deploy.yaml # Docker Compose deployment and startup ├── health_check.yaml # Health monitoring and verification ├── backup.yaml # Backup creation and management ├── update.yaml # Container update and image pulling └── manage_compose.yaml # Docker Compose lifecycle management ``` ### Task Execution Flow 1. **Setup** (`setup.yaml`) - [setup, deploy] - Docker/Compose validation - Variable validation and security checks - Directory creation - Additional template deployment 2. **Restore** (`restore.yaml`) - [restore, never] - Backup file selection and validation - Service stopping - Archive extraction - Service restart 3. **Deploy** (`deploy.yaml`) - [deploy] - Docker Compose file creation - Container startup and management 4. **Health Check** (`health_check.yaml`) - [deploy, healthcheck] - Service health verification - Endpoint monitoring 5. **Backup** (`backup.yaml`) - [backup, never] - Service stopping (optional) - Archive creation - Backup copying and cleanup - Service restart 6. **Update** (`update.yaml`) - [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.