Script Breakdown: Automated Docker Volume Backup Script
Published:
This script automates the process of backing up Docker container volumes and uploading the backups to a remote location using rclone.
The script performs the following steps:
- Creates a backup directory for the current day.
- Iterates through all running Docker containers.
- Stops each container to ensure consistent backups.
- Identifies and compresses eligible volumes for each container.
- Restarts the container after backup completion.
- Uploads the backup files to a remote storage location.
Script Breakdown
Variables
The script begins by defining variables for paths, timestamps, and remote storage details.
DOCKER_VOLUMES: Path where Docker volumes are stored (e.g.,/portainer/volumes).BACKUP_ROOT: Root directory where backup files will be stored (e.g.,/portainer/backups).BACKUP_DIR: Directory for today’s backups, generated based on the current date (e.g.,/portainer/backups/2024-11-15).TIMESTAMP: Current timestamp for naming backup files (e.g.,2024-11-15_10-30-00).RETENTION_DAYS:# Retention period (in days), default value set to10RCLONE_BACKUP: Default value set toFALSE, change toTRUEif cloud backups are desiredREMOTE_ROOT: Remote location for backups using rclone (e.g.,b2-kolakloud:/kolakloud/docker.kolakloud.com/volumes).
Function: purge_local_backups
Process:
- Locates any files or folders inside of
BACKUP_ROOTthat are older thanRETENTION_DAYSand deletes them
Code Snippit:
purge_local_backups() {
# Delete old files and folders in BACKUP_ROOT
echo "Deleting files and folders older than $RETENTION_DAYS days in $BACKUP_ROOT..."
find "$BACKUP_ROOT" -mindepth 1 -mtime +$RETENTION_DAYS -exec rm -rf {} \;
if [ $? -eq 0 ]; then
echo "Old files and folders deleted successfully."
else
echo "Failed to delete old files and folders."
fi
}
Function: backup_volume
Parameters:
container_name: Name of the container being processed.volume_path: Path to the Docker volume.
Process:
- Generates a unique backup file name based on the container name, volume name, and timestamp.
- Compresses the volume contents into a .zip file.
- Logs the success or failure of the operation.
Code Snippit:
backup_volume() {
local container_name=$1
local volume_path=$2
local volume_name=$(basename "$volume_path")
local backup_file="${BACKUP_DIR}/${container_name}_${volume_name}_${TIMESTAMP}.zip"
echo "Backing up volume at $volume_path for container $container_name..."
zip -pr "$backup_file" "$volume_path"
if [ $? -eq 0 ]; then
echo "Backup for $volume_path completed successfully: $backup_file"
else
echo "Backup for $volume_path failed."
fi
}
Function: backup_container
Process:
- Get Volumes: Fetches attached volumes for the container.
- Stop Container: Stops the container to ensure data consistency during backup.
- Backup Volumes: Iterates through volumes and backs up those matching the
DOCKER_VOLUMESpath. - Restart Container: Starts the container after backup completion.
- Status Output: Displays the completion of the backup process for the container.
Code Snippit:
backup_container(){
for CONTAINER_NAME in $CONTAINERS; do
echo "Processing container: $CONTAINER_NAME"
VOLUMES=$(docker inspect --format=' ' "$CONTAINER_NAME")
echo "Stopping container $CONTAINER_NAME..."
docker stop "$CONTAINER_NAME"
for VOLUME_PATH in $VOLUMES; do
if [[ "$VOLUME_PATH" == $DOCKER_VOLUMES/* ]]; then
backup_volume "$CONTAINER_NAME" "$VOLUME_PATH"
else
echo "Skipping non-eligible volume $VOLUME_PATH for container $CONTAINER_NAME."
fi
done
echo "Starting container $CONTAINER_NAME..."
docker start "$CONTAINER_NAME"
echo "Backup process for container $CONTAINER_NAME completed."
done
}
Function : rclone_upload
Process:
- Checks if
REMOTE_ROOTvariable contains a rclone path - Rclone copy: Uploads the backup files from the local backup root directory to the defined remote location.
- Logs the completion of the upload process.
Code Snippit:
rclone_upload(){
if [[ "$RCLONE_BACKUP" == "TRUE" ]]; then
echo "All containers processed, starting upload"
rclone copy --stats-one-line $BACKUP_ROOT $REMOTE_ROOT
echo "Upload Completed"
fi
}
Main : Directory Check and Creation
- Checks if the directory for today’s backups exists.
- Creates the directory if it does not.
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
Main : Container List
- Retrieves a list of all running Docker containers by their names.
CONTAINERS=$(docker ps --format "")
Main : Run Functions
- Starts the named functions from this script
purge_local_backups
backup_container
rclone_upload
Key Features
- Volume Filtering:
- Only backs up volumes within the
DOCKER_VOLUMESdirectory.
- Only backs up volumes within the
- Data Consistency:
- Containers are stopped before their volumes are backed up.
- Automated Upload:
- Uses rclone to securely transfer backups to a remote storage location.
Usage
- Place this script on the host server.
- Modify the variables (
DOCKER_VOLUMES,BACKUP_ROOT,REMOTE_ROOT) to match your environment. - Ensure zip and rclone are installed and configured.
- Run the script with appropriate permissions:
./backup_script.sh
Requirements
- Docker CLI
- Zip utility
- Rclone configured for the desired remote storage
Final Thoughts
I hope that this detailed breakdown should help the reader understand each step of the script and its purpose.
Changelog
- 2024-11-15: Inital posting and documentation creation
- 2024-11-23: Added logic at script start to remove backup data older than n-number of days
- 2025-03-17: Updated upload logic to purge old backups, quieted output from zip command
Complete Script
#!/bin/bash
# --Variables Start--
# Location Docker volumes are stored
DOCKER_VOLUMES="/opt/docker/volumes"
# Location of to place backups
BACKUP_ROOT="/opt/docker-backups/"
# Directory where you want to save the backups
BACKUP_DIR="$BACKUP_ROOT/$(date +%Y-%m-%d)"
# Timestamp
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) # Current timestamp for backup files
# Retention period (in days) - LOCAL
RETENTION_DAYS="3"
# Retention period (in days) - RCLONE
REMOTE_RETENTION_DAYS="21"
# Change from FALSE to TRUE to enable rclone backups
RCLONE_BACKUP="TRUE"
# Rclone root path
REMOTE_ROOT="b2-kolakloud:/kolakloud/docker/volumes/"
# -- Variables End--
# Function to back up a volume
purge_local_backups() {
# Delete old files and folders in BACKUP_ROOT
echo "Deleting files and folders older than $RETENTION_DAYS days in $BACKUP_ROOT..."
find "$BACKUP_ROOT" -mindepth 1 -mtime +$RETENTION_DAYS -exec rm -rf {} \;
if [ $? -eq 0 ]; then
echo "Old files and folders deleted successfully."
else
echo "Failed to delete old files and folders."
fi
}
# Function to back up a volume
backup_volume() {
local container_name=$1
local volume_path=$2
local volume_name=$(basename "$volume_path")
local backup_file="${BACKUP_DIR}/${container_name}_${volume_name}_${TIMESTAMP}.zip"
#Zips files in docker volume, to see output, change -pr to -r
echo "Backing up volume at $volume_path for container $container_name..."
zip -rq "$backup_file" "$volume_path"
if [ $? -eq 0 ]; then
echo "Backup for $volume_path completed successfully: $backup_file"
else
echo "Backup for $volume_path failed."
fi
}
# Function to backup up containers
backup_container(){
for CONTAINER_NAME in $CONTAINERS; do
echo "Processing container: $CONTAINER_NAME"
VOLUMES=$(docker inspect --format=' ' "$CONTAINER_NAME")
echo "Stopping container $CONTAINER_NAME..."
docker stop "$CONTAINER_NAME"
for VOLUME_PATH in $VOLUMES; do
if [[ "$VOLUME_PATH" == $DOCKER_VOLUMES/* ]]; then
backup_volume "$CONTAINER_NAME" "$VOLUME_PATH"
else
echo "Skipping non-eligible volume $VOLUME_PATH for container $CONTAINER_NAME."
fi
done
echo "Starting container $CONTAINER_NAME..."
docker start "$CONTAINER_NAME"
echo "Local backup for container $CONTAINER_NAME completed."
done
}
# Function to upload containers using rclone
rclone_upload(){
if [[ "$RCLONE_BACKUP" == "TRUE" ]]; then
echo "All containers processed, starting upload"
rclone copy --stats-one-line $BACKUP_ROOT $REMOTE_ROOT
#cp -r $BACKUP_ROOT/* /mnt/b2-kolakloud-backup/kolakloud/docker/volumes/
echo "Container Backup Upload Completed"
fi
}
# Function to purge remote backups older than specified days
purge_remote_backups() {
local days=$1
if [[ "$RCLONE_BACKUP" == "TRUE" ]]; then
echo "Purging remote backups older than $days days..."
rclone delete --min-age ${days}d --include "**/*" $REMOTE_ROOT
if [ $? -eq 0 ]; then
echo "Old remote backups deleted successfully."
else
echo "Failed to delete old remote backups."
fi
fi
}
# MAIN
# Check if backup directory exists, create if not
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
# Get a list of all running containers
CONTAINERS=$(docker ps --format "")
purge_local_backups
backup_container
rclone_upload
purge_remote_backups $REMOTE_RETENTION_DAYS
