--- - name: Deploy Application Update hosts: production gather_facts: yes become: yes vars: # These should be passed via -e from CI/CD image_tag: "{{ image_tag | default('latest') }}" git_commit_sha: "{{ git_commit_sha | default('unknown') }}" deployment_timestamp: "{{ deployment_timestamp | default(ansible_date_time.iso8601) }}" pre_tasks: - name: Optionally load registry credentials from encrypted vault include_vars: file: "{{ playbook_dir }}/../secrets/production.vault.yml" no_log: yes ignore_errors: yes delegate_to: localhost become: no - name: Derive docker registry credentials from vault when not provided set_fact: docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(omit)) }}" docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(omit)) }}" - name: Verify Docker is running systemd: name: docker state: started register: docker_service - name: Fail if Docker is not running fail: msg: "Docker service is not running" when: docker_service.status.ActiveState != 'active' - name: Create backup directory file: path: "{{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}" state: directory owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0755' tasks: - name: Backup current deployment metadata shell: | docker service inspect {{ stack_name }}_app --format '{{ "{{" }}.Spec.TaskTemplate.ContainerSpec.Image{{ "}}" }}' \ > {{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/current_image.txt || true docker stack ps {{ stack_name }} --format 'table {{ "{{" }}.Name{{ "}}" }}\t{{ "{{" }}.Image{{ "}}" }}\t{{ "{{" }}.CurrentState{{ "}}" }}' \ > {{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/stack_status.txt || true args: executable: /bin/bash changed_when: false - name: Login to Docker registry (if credentials provided) docker_login: registry_url: "{{ docker_registry_url }}" username: "{{ docker_registry_username }}" password: "{{ docker_registry_password }}" no_log: yes when: - docker_registry_username is defined - docker_registry_password is defined - name: Pull new Docker image docker_image: name: "{{ app_image }}" tag: "{{ image_tag }}" source: pull force_source: yes register: image_pull - name: Update docker-compose.prod.yml with new image tag lineinfile: path: "{{ compose_file }}" regexp: '^(\s+image:\s+){{ app_image }}:.*$' line: '\1{{ app_image }}:{{ image_tag }}' backrefs: yes when: compose_file is file - name: Deploy stack update docker_stack: name: "{{ stack_name }}" compose: - "{{ compose_file }}" state: present prune: yes register: stack_deploy - name: Wait for service to be updated command: > docker service ps {{ stack_name }}_app --filter "desired-state=running" --format '{{ "{{" }}.CurrentState{{ "}}" }}' register: service_status until: "'Running' in service_status.stdout" retries: 30 delay: 10 changed_when: false - name: Get updated service info command: docker service inspect {{ stack_name }}_app --format '{{ "{{" }}.Spec.TaskTemplate.ContainerSpec.Image{{ "}}" }}' register: deployed_image changed_when: false - name: Record deployment metadata copy: content: | Deployment Timestamp: {{ deployment_timestamp }} Git Commit: {{ git_commit_sha }} Image Tag: {{ image_tag }} Deployed Image: {{ deployed_image.stdout }} Stack Deploy Output: {{ stack_deploy }} dest: "{{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/deployment_metadata.txt" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0644' - name: Cleanup old backups (keep last {{ max_rollback_versions }}) shell: | cd {{ backups_path }} ls -t | tail -n +{{ max_rollback_versions + 1 }} | xargs -r rm -rf args: executable: /bin/bash changed_when: false post_tasks: - name: Display deployment summary debug: msg: - "Deployment completed successfully!" - "Image: {{ app_image }}:{{ image_tag }}" - "Commit: {{ git_commit_sha }}" - "Timestamp: {{ deployment_timestamp }}" - "Health check URL: {{ health_check_url }}"