This commit is contained in:
Mike McFetridge
2026-07-20 09:23:17 -04:00
parent c1315882da
commit 72272e4006
3179 changed files with 562960 additions and 14 deletions
@@ -0,0 +1,156 @@
---
- name: Docker postgres backup
hosts: all
become: true
gather_facts: false
vars:
backup_root: "/opt/docker-db-backups/postgres"
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
backup_file_name: "backup_postgres_<< container_name >>_<< backup_timestamp >>.sql.gz"
backup_file_path: "<< backup_root >>/<< backup_file_name >>"
tasks:
- name: Ensure backup root exists
ansible.builtin.file:
path: "<< backup_root >>"
state: directory
mode: "0750"
- name: Get container information
community.docker.docker_container_info:
name: "<< container_name >>"
register: container_info
- name: Fail if container does not exist
ansible.builtin.fail:
msg: "Container '<< container_name >>' does not exist"
when: not container_info.exists
- name: Run backup and validation
block:
- name: Dump all databases with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') == 'all'
- (postgres_password | default('')) | length > 0
- name: Dump all databases without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') == 'all'
- (postgres_password | default('')) | length == 0
- name: Dump selected database with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
pg_dump -U "<< postgres_user | default('postgres') >>" \
"<< postgres_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') != 'all'
- (postgres_password | default('')) | length > 0
- name: Dump selected database without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
pg_dump -U "<< postgres_user | default('postgres') >>" \
"<< postgres_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') != 'all'
- (postgres_password | default('')) | length == 0
- name: Validate backup file exists
ansible.builtin.stat:
path: "<< backup_file_path >>"
register: backup_archive_stat
- name: Fail if backup file is missing or empty
ansible.builtin.fail:
msg: "Backup file missing or empty: << backup_file_path >>"
when:
- not (backup_archive_stat.stat.exists | default(false))
or (backup_archive_stat.stat.size | default(0) | int) == 0
- name: Validate gzip archive integrity
ansible.builtin.command: gzip -t "<< backup_file_path >>"
changed_when: false
- name: Validate postgres dump signature
ansible.builtin.shell: |
set -euo pipefail
gzip -dc "<< backup_file_path >>" \
| head -n 50 \
| grep -Eq 'PostgreSQL database dump'
args:
executable: /bin/bash
changed_when: false
- name: Remove old postgres backup archives
ansible.builtin.find:
paths: "<< backup_root >>"
patterns: "backup_postgres_<< container_name >>_*.sql.gz"
age: "<< backup_retention_days | default(14) >>d"
recurse: true
register: old_backups
- name: Delete old postgres backup archives
ansible.builtin.file:
path: "<< item.path >>"
state: absent
loop: "<< old_backups.files >>"
- name: Mark backup status success
ansible.builtin.set_fact:
backup_status: "success"
backup_status_message: "Backup and validation succeeded"
rescue:
- name: Mark backup status failed
ansible.builtin.set_fact:
backup_status: "failed"
backup_status_message: "<< ansible_failed_result.msg | default('Backup or validation failed') >>"
always:
- name: Send postgres backup status to Discord
ansible.builtin.uri:
url: "<< discord_webhook | default('') >>"
method: POST
body_format: json
body:
content: |
PostgreSQL Backup << backup_status | default('failed') | upper >>
Host: << inventory_hostname >>
Container: << container_name >>
File: << backup_file_path >>
Message: << backup_status_message | default('n/a') >>
status_code: 204
when:
- send_discord_notification | default(false)
- (discord_webhook | default('')) | length > 0
- name: Fail play if backup failed
ansible.builtin.fail:
msg: "<< backup_status_message | default('Backup failed') >>"
when: backup_status | default('failed') == 'failed'
@@ -0,0 +1,2 @@
collections:
- name: community.docker