Files
michaelschiemer/deployment/infrastructure/playbooks/deploy-application.yml
Michael Schiemer 66f7efdcfc Fix cache directory paths for production deployment
- Change FileCache CACHE_PATH from relative to absolute path
- Change FileCacheCleaner cache folder to absolute path
- Resolves read-only file system issue in production containers
- Cache now uses writable /var/www/html/storage/cache location
2025-08-12 18:37:24 +02:00

226 lines
7.0 KiB
YAML

---
# Production Container Deployment Playbook
# Deploys pre-built container images for Custom PHP Framework
- name: Deploy Custom PHP Framework Application
hosts: web_servers
become: true
gather_facts: true
vars:
app_path: "/var/www/html"
backup_path: "/var/www/backups"
image_tag: "{{ IMAGE_TAG | default('latest') }}"
domain_name: "{{ DOMAIN_NAME | default('michaelschiemer.de') }}"
backup_enabled: "{{ BACKUP_ENABLED | default(true) | bool }}"
backup_retention_days: "{{ BACKUP_RETENTION_DAYS | default(30) }}"
cdn_update: "{{ CDN_UPDATE | default(false) | bool }}"
# Pfade für Templates/Compose relativ zum Playbook-Verzeichnis
compose_base_src: "{{ playbook_dir }}/../../../docker-compose.yml"
compose_overlay_src: "{{ playbook_dir }}/../../applications/docker-compose.{{ environment }}.yml"
env_template_src: "{{ playbook_dir }}/../../applications/environments/.env.{{ environment }}.template"
# Compose-Projektname: Standardmäßig Verzeichnisname von app_path (z. B. 'html')
compose_project: "{{ compose_project_name | default(app_path | basename) }}"
pre_tasks:
- name: Verify deployment requirements
assert:
that:
- app_path is defined
- domain_name is defined
- image_tag is defined
- image_tag != 'latest' or environment != 'production'
fail_msg: "Production deployment requires specific image tag (not 'latest')"
tags: always
- name: Create required directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: deploy
group: deploy
mode: '0755'
loop:
- "{{ app_path }}"
- "{{ backup_path }}"
- /var/log/applications
tags: always
- name: Store current image tag for rollback
ansible.builtin.shell: |
if [ -f {{ app_path }}/.env.{{ environment }} ]; then
grep '^IMAGE_TAG=' {{ app_path }}/.env.{{ environment }} | cut -d'=' -f2 > {{ app_path }}/.last_release || echo 'none'
fi
ignore_errors: true
tags: backup
tasks:
- name: Check for existing deployment
ansible.builtin.stat:
path: "{{ app_path }}/docker-compose.yml"
register: existing_deployment
tags: deploy
- name: Render environment file from template
ansible.builtin.template:
src: "{{ env_template_src }}"
dest: "{{ app_path }}/.env.{{ environment }}"
owner: deploy
group: deploy
mode: '0600'
backup: true
vars:
IMAGE_TAG: "{{ image_tag }}"
DOMAIN_NAME: "{{ domain_name }}"
no_log: true
tags: deploy
- name: Copy Docker Compose files (base + overlay)
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ app_path }}/{{ item.dest }}"
owner: deploy
group: deploy
mode: '0644'
loop:
- { src: "{{ compose_base_src }}", dest: "docker-compose.yml" }
- { src: "{{ compose_overlay_src }}", dest: "docker-compose.{{ environment }}.yml" }
tags: deploy
- name: Stop existing services gracefully if present
community.docker.docker_compose_v2:
project_src: "{{ app_path }}"
files:
- docker-compose.yml
- "docker-compose.{{ environment }}.yml"
env_files:
- ".env.{{ environment }}"
state: stopped
timeout: 60
when: existing_deployment.stat.exists
ignore_errors: true
tags: deploy
- name: Create storage volumes with proper permissions
ansible.builtin.file:
path: "{{ app_path }}/{{ item }}"
state: directory
owner: www-data
group: www-data
mode: '0775'
loop:
- storage
- storage/logs
- storage/cache
- var
- var/logs
tags: deploy
- name: Deploy application with Docker Compose v2
community.docker.docker_compose_v2:
project_src: "{{ app_path }}"
files:
- docker-compose.yml
- "docker-compose.{{ environment }}.yml"
env_files:
- ".env.{{ environment }}"
pull: true
build: false
state: present
recreate: smart
remove_orphans: true
timeout: 300
tags: deploy
- name: Wait for PHP container to be healthy (label-based)
community.docker.docker_container_info:
filters:
label:
- "com.docker.compose.service=php"
- "com.docker.compose.project={{ compose_project }}"
register: php_info
retries: 20
delay: 10
until: php_info.containers is defined and
(php_info.containers | length) > 0 and
(
(php_info.containers[0].State.Health is defined and php_info.containers[0].State.Health.Status == "healthy")
or
php_info.containers[0].State.Status == "running"
)
tags: deploy
- name: Run database migrations
community.docker.docker_container_exec:
container: "{{ php_info.containers[0].Id }}"
command: php console.php db:migrate --force
chdir: /var/www/html
tags: deploy
- name: Clear application caches
community.docker.docker_container_exec:
container: "{{ php_info.containers[0].Id }}"
command: "php console.php {{ item }}"
chdir: /var/www/html
loop:
- cache:clear
- view:clear
ignore_errors: true
tags: deploy
- name: Wait for application to be ready
ansible.builtin.uri:
url: "https://{{ domain_name }}/health"
method: GET
status_code: 200
timeout: 30
headers:
User-Agent: "Mozilla/5.0 (Ansible Health Check)"
validate_certs: true
register: http_health
retries: 15
delay: 10
until: http_health.status == 200
tags: deploy
- name: Store successful deployment tag
ansible.builtin.copy:
content: "{{ image_tag }}"
dest: "{{ app_path }}/.last_successful_release"
owner: deploy
group: deploy
mode: '0644'
tags: deploy
post_tasks:
- name: Clean up old backups
ansible.builtin.find:
paths: "{{ backup_path }}"
age: "{{ backup_retention_days }}d"
file_type: directory
register: old_backups
when: backup_enabled
tags: cleanup
- name: Remove old backup directories
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_backups.files }}"
when: backup_enabled and old_backups.files is defined
tags: cleanup
- name: Import CDN update playbook if enabled
import_playbook: update-cdn.yml
when: cdn_update | default(false) | bool
tags: cdn
- name: Deployment success notification
ansible.builtin.debug:
msg:
- "Application deployment completed successfully"
- "Image Tag: {{ image_tag }}"
- "Environment: {{ environment }}"
- "Domain: {{ domain_name }}"
- "CDN Updated: {{ cdn_update }}"
tags: always