--- # Application Rollback Playbook # Rolls back to a specific image tag using container deployment --- # Rollback Playbook: setzt IMAGE_TAG zurück und recreatet Services - name: Rollback Custom PHP Framework Application hosts: web_servers become: true gather_facts: false vars: app_path: "/var/www/html" domain_name: "{{ DOMAIN_NAME | default('michaelschiemer.de') }}" rollback_tag: "{{ ROLLBACK_TAG | default('') }}" compose_project: "{{ compose_project_name | default(app_path | basename) }}" pre_tasks: - name: Validate ROLLBACK_TAG is provided ansible.builtin.fail: msg: "Setze ROLLBACK_TAG auf ein gültiges Image-Tag." when: rollback_tag | length == 0 tasks: - name: Ensure environment file exists ansible.builtin.stat: path: "{{ app_path }}/.env.{{ environment }}" register: env_file - name: Fail if environment file is missing ansible.builtin.fail: msg: "Environment-Datei fehlt: {{ app_path }}/.env.{{ environment }}" when: not env_file.stat.exists - name: Write IMAGE_TAG to env file ansible.builtin.lineinfile: path: "{{ app_path }}/.env.{{ environment }}" regexp: '^IMAGE_TAG=' line: "IMAGE_TAG={{ rollback_tag }}" create: no backrefs: false mode: "0600" - name: Recreate services with rollback tag community.docker.docker_compose_v2: project_src: "{{ app_path }}" files: - docker-compose.yml - "docker-compose.{{ environment }}.yml" env_files: - ".env.{{ environment }}" pull: false build: false state: present recreate: smart remove_orphans: true timeout: 300 - 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" ) - name: Verify application HTTP health ansible.builtin.uri: url: "https://{{ domain_name }}/health" method: GET status_code: 200 timeout: 30 validate_certs: true register: http_health retries: 15 delay: 10 until: http_health.status == 200 post_tasks: - name: Rollback completed ansible.builtin.debug: msg: - "Rollback erfolgreich" - "Neues aktives Image-Tag: {{ rollback_tag }}" - name: Rollback Custom PHP Framework Application hosts: web_servers become: true gather_facts: true vars: app_path: "/var/www/html" rollback_tag: "{{ ROLLBACK_TAG | mandatory }}" domain_name: "{{ DOMAIN_NAME | default('michaelschiemer.de') }}" environment: "{{ ENV | default('production') }}" pre_tasks: - name: Verify rollback requirements assert: that: - rollback_tag is defined - rollback_tag != '' - rollback_tag != 'latest' fail_msg: "Rollback requires specific ROLLBACK_TAG (not 'latest')" tags: always - name: Check if target tag exists locally community.docker.docker_image_info: name: "{{ project_name | default('michaelschiemer') }}:{{ rollback_tag }}" register: rollback_image_info ignore_errors: true tags: always - name: Pull rollback image if not available locally community.docker.docker_image: name: "{{ project_name | default('michaelschiemer') }}:{{ rollback_tag }}" source: pull force_source: true when: rollback_image_info.images | length == 0 tags: always - name: Store current deployment for emergency recovery shell: | if [ -f {{ app_path }}/.env.{{ environment }} ]; then grep '^IMAGE_TAG=' {{ app_path }}/.env.{{ environment }} | cut -d'=' -f2 > {{ app_path }}/.emergency_recovery_tag || echo 'none' fi tags: backup tasks: - name: Update environment with rollback tag template: src: "{{ environment }}.env.template" dest: "{{ app_path }}/.env.{{ environment }}" owner: deploy group: deploy mode: '0600' backup: true vars: IMAGE_TAG: "{{ rollback_tag }}" DOMAIN_NAME: "{{ domain_name }}" no_log: true tags: rollback - name: Stop current services community.docker.docker_compose_v2: project_src: "{{ app_path }}" files: - docker-compose.yml - "docker-compose.{{ environment }}.yml" env_files: - ".env.{{ environment }}" state: stopped timeout: 120 tags: rollback - name: Deploy rollback version community.docker.docker_compose_v2: project_src: "{{ app_path }}" files: - docker-compose.yml - "docker-compose.{{ environment }}.yml" env_files: - ".env.{{ environment }}" pull: "never" # Use local image build: "never" state: present recreate: "always" # Force recreate for rollback timeout: 300 tags: rollback - name: Wait for containers to be healthy after rollback community.docker.docker_container_info: name: "{{ item }}" register: container_info retries: 15 delay: 10 until: container_info.container.State.Health.Status == "healthy" or container_info.container.State.Status == "running" loop: - "{{ ansible_hostname }}_php_1" - "{{ ansible_hostname }}_web_1" - "{{ ansible_hostname }}_db_1" - "{{ ansible_hostname }}_redis_1" ignore_errors: true tags: rollback - name: Verify application health after rollback uri: url: "https://{{ domain_name }}/health" method: GET status_code: 200 timeout: 30 headers: User-Agent: "Mozilla/5.0 (Ansible Rollback Check)" validate_certs: true retries: 10 delay: 15 tags: rollback - name: Update successful rollback tag copy: content: "{{ rollback_tag }}" dest: "{{ app_path }}/.last_successful_release" owner: deploy group: deploy mode: '0644' tags: rollback post_tasks: - name: Rollback success notification debug: msg: - "Application rollback completed successfully" - "Rolled back to: {{ rollback_tag }}" - "Environment: {{ environment }}" - "Domain: {{ domain_name }}" - "Emergency recovery tag stored for further rollback if needed" tags: always - name: Log rollback event lineinfile: path: "{{ app_path }}/rollback.log" line: "{{ ansible_date_time.iso8601 }} - Rollback to {{ rollback_tag }} from {{ environment }} completed successfully" create: true owner: deploy group: deploy mode: '0644' tags: always