├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── Readme.md ├── backup-manager.sh ├── backup ├── backup-all.sh ├── backup-container-data.sh ├── backup-images.sh ├── backup-volumes.sh ├── config │ └── dropbox_uploader.conf ├── coreos-timer │ ├── backup-containers.service │ ├── backup-containers.timer │ └── install-service.sh └── sync-dropbox.sh └── restore ├── restore-all.sh └── restore-volumes.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: piscue 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | runs-on: macos-13 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Update brew 21 | run: brew update 22 | 23 | - name: Install shellcheck 24 | run: brew install shellcheck 25 | 26 | - name: Run shellcheck 27 | run: shellcheck *.sh backup/**.sh restore/*.sh -e SC2154 -e SC1091 28 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # docker backup scripts 2 | 3 | ## Description 4 | 5 | A bunch of Bash scripts to make a backup of all your running containers 6 | dynamically. 7 | 8 | This will create a backup of docker images, volumes, upload to dropbox and 9 | remove the backup files after to save space 10 | 11 | ## Setup and Usage 12 | 13 | This script use flags for configuration. The flags are: 14 | 15 | - **-m** to set the mode (backup or restore) 16 | - **-b** to set the backup path 17 | - **-t** to set the tar options 18 | - **-u** to upload to dropbox 19 | - **-f** to force 20 | - **-s** to run in non-interactive mode 21 | 22 | Also, on dropbox you must create an App to store this backups, refer to 23 | https://www.dropbox.com/developers to get your **Generated access token** 24 | before running it and placed inside the **config/dropbox-uploader.conf** file 25 | 26 | Give permissions to all sh files in the folder 27 | 28 | ```bash 29 | cd docker-backup-scripts 30 | chmod +x *.sh 31 | ``` 32 | 33 | Run the backup 34 | 35 | ```bash 36 | ./backup-manager.sh -p -m backup 37 | ``` 38 | 39 | Run the restoration 40 | 41 | ```bash 42 | ./backup-manager.sh -p -m restore 43 | ``` 44 | 45 | ## Extra 46 | 47 | Create a cron if you want to run it often (use the -s flag to run it in non-interactive mode) 48 | 49 | ```bash 50 | crontab -e 51 | ``` 52 | 53 | ```bash 54 | 0 0 * * * /path/to/backup-manager.sh -p -m backup -s 55 | ``` 56 | 57 | For CoreOS, I supply a timer to allow run it daily with an installation script 58 | -------------------------------------------------------------------------------- /backup-manager.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Backup Manager 4 | # Author: piscue 5 | # The backup manager is a script that allows you to backup all your docker volumes, images and container data. 6 | # It also allows you to upload the backup to dropbox. 7 | # The script is interactive, so you can choose what you want to backup. 8 | # You can also use it in non-interactive mode, so you can use it in a cron job. (see README.md for more information) 9 | # The script can also restore your backup, see README.md for more information. 10 | 11 | cd "${BASH_SOURCE%/*}" || exit 12 | 13 | # Flags 14 | # -s: non-interactive mode 15 | # -p: backup path 16 | # -t: tar options 17 | # -u: upload to dropbox 18 | # -f: force 19 | # -m: mode (backup or restore) 20 | 21 | 22 | non_interactive=false 23 | backup_path="/home/core/backups" 24 | tar_opts="--exclude='/var/run/*'" 25 | docker_upload_enable=false 26 | force=false 27 | mode="" 28 | 29 | while getopts "sp:t:ufm:" opt; do 30 | case $opt in 31 | s) 32 | non_interactive=true 33 | ;; 34 | p) 35 | backup_path=$OPTARG 36 | ;; 37 | t) 38 | tar_opts=$OPTARG 39 | ;; 40 | u) 41 | docker_upload_enable=true 42 | ;; 43 | f) 44 | force=true 45 | ;; 46 | m) 47 | mode=$OPTARG 48 | ;; 49 | \?) 50 | echo "Invalid option: -$OPTARG" >&2 51 | ;; 52 | esac 53 | done 54 | 55 | # Set the backup path to absolute path if it is not 56 | if [[ "$backup_path" != /* ]] 57 | then 58 | backup_path="$PWD/$backup_path" 59 | fi 60 | 61 | 62 | 63 | # Setting mode $1 (need to be backup or restore) 64 | if [ "$mode" = "backup" ] 65 | then 66 | source backup/backup-all.sh 67 | elif [ "$mode" = "restore" ] 68 | then 69 | source restore/restore-all.sh 70 | else 71 | echo "Invalid mode, please use backup or restore" 72 | exit 1 73 | fi 74 | -------------------------------------------------------------------------------- /backup/backup-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Starting docker backup" 4 | echo " - backup path: $backup_path" 5 | echo " - tar options: $tar_opts" 6 | echo "" 7 | 8 | # Check if backup path exists 9 | if [ ! -d "$backup_path" ] 10 | then 11 | # Attempt to create the directory 12 | if ! mkdir -p "$backup_path" 13 | then 14 | echo "Error: backup path does not exist and could not be created" 15 | exit 1 16 | fi 17 | else 18 | # Check if backup path is empty 19 | if [ "$(ls -A "$backup_path")" ] 20 | then 21 | # Backup path is not empty 22 | if [ "$force" = false ] 23 | then 24 | echo "Error: backup path is not empty, use -f to force" 25 | exit 1 26 | else 27 | echo "Warning: backup path is not empty, but force flag is set" 28 | fi 29 | fi 30 | 31 | # Check if backup path is writable 32 | if ! touch "$backup_path/test.txt" 2>/dev/null 33 | then 34 | echo "Error: backup path is not writable" 35 | exit 1 36 | else 37 | rm "$backup_path/test.txt" 38 | fi 39 | fi 40 | 41 | 42 | if [ "$non_interactive" = false ] 43 | then 44 | echo "Backup container data (inspection output) ? (y/n)" 45 | read -r backup_container_data 46 | 47 | echo "Backup container images ? (y/n)" 48 | read -r backup_container_images 49 | 50 | echo "Backup volumes ? (y/n)" 51 | read -r backup_volumes 52 | 53 | echo "Should I compress the backup directory ? (y/n)" 54 | read -r compress_backup 55 | else 56 | backup_container_data="y" 57 | backup_container_images="y" 58 | backup_volumes="y" 59 | compress_backup="n" 60 | fi 61 | 62 | if [ "$backup_container_data" = "y" ] 63 | then 64 | source backup/backup-container-data.sh 65 | fi 66 | 67 | if [ "$backup_container_images" = "y" ] 68 | then 69 | source backup/backup-images.sh 70 | fi 71 | 72 | if [ "$backup_volumes" = "y" ] 73 | then 74 | source backup/backup-volumes.sh 75 | fi 76 | 77 | if [ "$compress_backup" = "y" ] 78 | then 79 | echo -n "Compressing backup directory - " 80 | tar -czf "$backup_path.tar.gz" "$backup_path" >/dev/null 2>&1 81 | echo "OK" 82 | 83 | echo -n "Removing backup directory - " 84 | rm -rf "$backup_path" 85 | echo "OK" 86 | fi 87 | 88 | echo "" 89 | echo "Backup finished" 90 | 91 | if [ "$compress_backup" = "y" ] 92 | then 93 | echo "Backup file: $backup_path.tar.gz" 94 | else 95 | echo "Backup directory: $backup_path" 96 | fi 97 | 98 | if [ "$docker_upload_enable" = true ] 99 | then 100 | echo - upload to dropbox 101 | echo "" 102 | source backup/sync-dropbox.sh 103 | echo "" 104 | fi 105 | -------------------------------------------------------------------------------- /backup/backup-container-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Backing up container data (inspection output)" 4 | echo "--------------------------------------------" 5 | 6 | for i in $(docker ps -q | xargs docker inspect --format='{{.Name}}' | cut -f2 -d/) 7 | do container_name=$i 8 | echo -n "$container_name - " 9 | container_data=$(docker inspect "$container_name") 10 | mkdir -p "$backup_path/$container_name" 11 | echo "$container_data" > "$backup_path/$container_name/$container_name-data.txt" 12 | echo "OK" 13 | done 14 | 15 | echo "" 16 | -------------------------------------------------------------------------------- /backup/backup-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Backing up container images" 4 | echo "---------------------------" 5 | 6 | for i in $(docker ps -q | xargs docker inspect --format='{{.Name}}' | cut -f2 -d/) 7 | do container_name=$i 8 | echo -n "$container_name - " 9 | container_image=$(docker inspect --format='{{.Config.Image}}' "$container_name") 10 | mkdir -p "$backup_path"/"$container_name" 11 | save_file="$backup_path"/"$container_name"/"$container_name-image.tar" 12 | docker save -o "$save_file" "$container_image" 13 | echo "OK" 14 | done 15 | 16 | echo "" 17 | -------------------------------------------------------------------------------- /backup/backup-volumes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Path: backup-volumes.sh 4 | # Backup all volumes from docker 5 | 6 | echo "Backing up volumes" 7 | echo "------------------" 8 | 9 | for volume in $(docker volume ls -q | xargs docker volume inspect -f '"{{.Name}}""{{.Mountpoint}}"') 10 | do 11 | # Get the volume name and path 12 | volume_name=$(echo "$volume" | cut -f2 -d\") 13 | volume_path=$(echo "$volume" | cut -f4 -d\") 14 | 15 | # Create the backup directory 16 | mkdir -p "$backup_path"/volumes 17 | 18 | 19 | # Backup the volume 20 | echo -n "$volume_name - " 21 | docker run --rm -v "$volume_path":/volume -v "$backup_path"/volumes:/backup busybox tar -cvzf /backup/"$volume_name".tar.gz /volume >/dev/null 2>&1 22 | echo "OK" 23 | done 24 | 25 | echo "" 26 | -------------------------------------------------------------------------------- /backup/config/dropbox_uploader.conf: -------------------------------------------------------------------------------- 1 | OAUTH_ACCESS_TOKEN= 2 | -------------------------------------------------------------------------------- /backup/coreos-timer/backup-containers.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Backup all running containers 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/opt/bin/backup-all.sh 7 | 8 | [Install] 9 | WantedBy=backup-containers.timer 10 | -------------------------------------------------------------------------------- /backup/coreos-timer/backup-containers.timer: -------------------------------------------------------------------------------- 1 | # https://www.freedesktop.org/software/systemd/man/systemd.timer.html 2 | 3 | [Unit] 4 | Description=Backup all containers at 01:00 daily 5 | 6 | [Timer] 7 | OnCalendar=*-*-* 01:00:00 8 | Unit=backup-containers.service 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /backup/coreos-timer/install-service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # setup path of scripts 4 | # know where backup-all.sh will be stored 5 | 6 | cd "${BASH_SOURCE%/*}" || exit 7 | cd .. 8 | sudo mkdir -p /opt/bin 9 | sudo cp *.sh /opt/bin 10 | sudo cp -R config /opt/bin 11 | sudo chmod +x /opt/bin/*sh 12 | 13 | # copy service and time to the system 14 | cd coreos-timer || exit 15 | sudo cp backup-containers* /etc/systemd/system/ 16 | 17 | # enabling timer 18 | sudo systemctl enable backup-containers.timer 19 | sudo systemctl start backup-containers.timer 20 | sudo systemctl enable backup-containers.service 21 | -------------------------------------------------------------------------------- /backup/sync-dropbox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker_containers=$(docker ps -q) 4 | for i in $(docker inspect --format='{{.Name}}' "$docker_containers" | cut -f2 -d/) 5 | do container_name=$i 6 | 7 | # Creating container folder 8 | docker run --rm --user="$(id -u)":"$(id -g)" \ 9 | -v "$PWD"/config:/config \ 10 | -v "$backup_path":/workdir \ 11 | peez/dropbox-uploader \ 12 | mkdir "$container_name" 13 | 14 | # Uploading image 15 | echo "$container_name - image to Dropbox " 16 | # TODO check if the file exists 17 | docker run --rm --user="$(id -u)":"$(id -g)" \ 18 | --name "dropbox-$container_name-image-backup" \ 19 | -v "$PWD"/config:/config \ 20 | -v "$backup_path":/workdir \ 21 | peez/dropbox-uploader \ 22 | upload "$container_name"/"$container_name-image.tar" \ 23 | "$container_name"/"$container_name-image.tar" && \ 24 | 25 | # remove local image, TODO creating a condition to know if the file 26 | # uploaded well before deleting 27 | rm -f "$backup_path"/"$container_name"/"$container_name-image.tar" 28 | 29 | # Uploading volume 30 | echo "$container_name - volume to Dropbox " 31 | # TODO check if the file exists 32 | docker run --rm --user="$(id -u)":"$(id -g)" \ 33 | --name "dropbox-$container_name-volume-backup" \ 34 | -v "$PWD"/config:/config \ 35 | -v "$backup_path":/workdir \ 36 | peez/dropbox-uploader \ 37 | upload "$container_name"/"$container_name-volume.tar.xz" \ 38 | "$container_name"/"$container_name-volume.tar.xz" && \ 39 | 40 | # remove local volume, TODO creating a condition to know if the file 41 | # uploaded well before deleting 42 | rm -f "$backup_path"/"$container_name"/"$container_name-volume.tar.xz" 43 | done 44 | -------------------------------------------------------------------------------- /restore/restore-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Starting docker restore" 4 | echo "" 5 | 6 | # Check if backup path exists 7 | if [ ! -d "$backup_path" ] 8 | then 9 | echo "Error: backup path does not exist" 10 | exit 1 11 | fi 12 | 13 | 14 | if [ "$non_interactive" = false ] 15 | then 16 | echo "Restore volumes ? (y/n)" 17 | read -r restore_volumes 18 | else 19 | restore_volumes="y" 20 | fi 21 | 22 | if [ "$restore_volumes" = "y" ] 23 | then 24 | source restore/restore-volumes.sh 25 | fi 26 | 27 | 28 | 29 | echo "" 30 | echo "Restoration finished" -------------------------------------------------------------------------------- /restore/restore-volumes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Path: restore-volumes.sh 4 | # Restore all volumes from the backup 5 | # Inside the backup directory, there is a volumes directory, which contains all the volumes. 6 | 7 | echo "Volumes restaurations" 8 | echo "------------------" 9 | 10 | volumes=$(ls "$backup_path/volumes") 11 | 12 | for volume in $volumes 13 | do 14 | # Get volume name from the file name 15 | volume=$(echo "$volume" | cut -f1 -d.) 16 | 17 | echo -n "$volume - " 18 | docker run --rm -v "$volume":/volume -v "$backup_path"/volumes:/backup busybox sh -c "cd /volume && tar -xvf /backup/$volume.tar.gz --strip 1" >/dev/null 2>&1 19 | echo "OK" 20 | done --------------------------------------------------------------------------------