A half-full circle

Because I don't do social media.

Bash script for daily backups from PostgreSQL Docker containers

How I backup PostgreSQL databases from Docker containers.

I use Docker with Docker Compose for my own apps. I've got a few apps that use PostgreSQL, and a couple of years ago I was looking for a way to backup the databases from those containers in an easy and automated way.

I found a few solutions, but all were way too complicated or inefficient or required more work to restore, or were reliant on the host machine having PostgreSQL installed (which I didn't want because my apps are running different PostgreSQL versions), so I wrote a script myself!

I've been using this bash script to backup the databases from the containers. It's a simple script that runs every day at a specific time (via a simple cron job), and backs up the databases to a specific directory (via pg_dump), without any passwords in the script. It keeps 14 days of backups, compresses them, and cleans up old backups. Oh, and it supports multiple containers!

Here it is:

backup.sh

#!/bin/bash

#
# Backup Postgresql databases into daily files for multiple containers.
#

# Base backup settings
BASE_BACKUP_DIR=/home/www/backups
DAYS_TO_KEEP=14
FILE_SUFFIX=_backup.sql

# App container names
declare -A CONTAINERS=(
    ["amazing-app"]="/home/www/apps/amazing-app"
    ["another-app"]="/home/www/apps/another-app"
)

# Common PostgreSQL settings
POSTGRES_USER=postgres
POSTGRES_CONTAINER_USER=postgres

# Function to backup a single container
backup_container() {
    local container_name=$1
    local container_dir=$2
    local backup_dir="${BASE_BACKUP_DIR}/${container_name}"
    local file=$(date +"%Y%m%d%H%M")${FILE_SUFFIX}
    local output_file="${backup_dir}/${file}"
    local postgres_container="${container_name}-postgresql-1"
    local postgres_db="${container_name}"

    # Create backup directory if it doesn't exist
    mkdir -p "${backup_dir}"

    # Do the database backup (dump)
    cd "${container_dir}" && docker exec -u ${POSTGRES_CONTAINER_USER} ${postgres_container} pg_dump --dbname="${postgres_db}" --username="${POSTGRES_USER}" > "${output_file}"

    # gzip the database dump file
    gzip "${output_file}"

    # Show the result
    echo "${output_file}.gz was created:"
    ls -l "${output_file}.gz"

    # Prune old backups for this container
    find "${backup_dir}" -maxdepth 1 -mtime +${DAYS_TO_KEEP} -name "*${FILE_SUFFIX}.gz" -exec rm -f '{}' ';'
}

for container_name in "${!CONTAINERS[@]}"; do
    echo "Backing up ${container_name}..."
    backup_container "${container_name}" "${CONTAINERS[$container_name]}"
done

echo "Done backing up databases."

## Restore:
# cd ${container_dir} && docker cp /home/www/backups/${postgres_container}/*_backup.sql.gz ${postgres_container}:/tmp/backup.sql.gz
# docker exec -u ${POSTGRES_CONTAINER_USER} -i ${postgres_container} pg_restore -C --dbname="${postgres_db}" --username="${POSTGRES_USER}" /tmp/backup.sql.gz

Obviously there are some things you can improve, like adding support for multiple databases per container, multiple postgresql container names (per container), and multiple postgresql users. I don't need that, so it'd be unnecessary complexity for me.

I run this with a simple cronjob:

# Every day at 3:07am
7 3 * * * /home/www/scripts/backup.sh > /home/www/logs/backup.log 2>&1

Which keeps a nice log of the last daily backup in /home/www/logs/backup.log.

I hope this helps you.

Thank you so much for being here. I really appreciate you!