Easy and Secure Backups Using Borg and Docker

docker borg backup

BorgBackup is a secure backup solution which is also easy to use. It provides compression, encryption, deduplication and authentication.

Getting started

I’ve created a Dockerfile based on Alpine Linux which is also available on DockerHub. It gets built weekly to always stay up to date.

This Makefile can be used to quickly get started using a containerized version of Borg:

SHELL := /bin/bash
VERSION ?= latest

# The directory of this file
DIR := $(shell echo $(shell cd "$(shell  dirname "${BASH_SOURCE[0]}" )" && pwd ))

IMAGE_NAME ?= ps1337/borg-docker
CONTAINER_NAME ?= borg

# This will output the help for each task
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
.PHONY: help

help: ## This help
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.DEFAULT_GOAL := help

# Build the container
build: ## Build the container
	docker build --rm -t $(IMAGE_NAME) .

build-nc: ## Build the container without caching
	docker build --rm --no-cache -t $(IMAGE_NAME) .

run: ## Run container
	sudo docker run \
	-d \
	--name $(CONTAINER_NAME) \
	-v $(DIR)/data:/var/backups/borg \
	-v $(DIR)/authorized_keys:/home/borg/.ssh/authorized_keys \
	-p 22:22 \
	$(IMAGE_NAME):$(VERSION)

stop: ## Stop a running container
	docker stop $(CONTAINER_NAME)

remove: ## Remove a (running) container
	docker rm -f $(CONTAINER_NAME)

remove-image-force: ## Remove the latest image (forced)
	docker rmi -f $(IMAGE_NAME):$(VERSION)

This docker run call mounts two things:

  1. ./dir to persistently store the backup data on the container host
  2. ./authorized_keys to perform authentication with the OpenSSH server of borg. You have to provide this file to get started.

Creating repositories and backups

To create a borg backup repository using a specified SSH private key, use

BORG_RSH='ssh -i </path/to/private/key>' borg init -e repokey <ssh://borg@yourDomain:exposedPort/var/backups/borg/repoName>

For each backup repository, you can create a borg backup script like this:

#!/bin/sh

export BORG_RSH='<ssh -i /path/to/private/key>'

# Setting this, so the repo does not need to be given on the commandline:
export BORG_REPO=<ssh://borg@yourDomain:exposedPort/var/backups/borg/repoName>

# Setting this, so you won't be asked for your repository passphrase:
export BORG_PASSPHRASE=$(<Password manager call to get the secret key>)

HOSTNAME=`hostname`
DATE=`date +%Y-%m-%d-%s `

# some helpers and error handling:
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }
trap 'echo $( date ) Backup interrupted >&2; exit 2' INT TERM

info "Starting backup"

# Backup the most important directories into an archive named after
# the machine this script is currently running on:

borg create                         \
    --verbose                       \
    --filter AME                    \
    --list                          \
    --progress                      \
    --stats                         \
    --show-rc                       \
    --compression zlib              \
    --exclude-caches                \
    --exclude '/home/*/.cache/*'    \
    ::$HOSTNAME-$DATE               \
    /home

backup_exit=$?

if [ ${backup_exit} -eq 0 ];
    then
        info "Pruning repository"

        # Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly
        # archives of THIS machine. The '{hostname}-' prefix is very important to
        # limit prune's operation to this machine's archives and not apply to
        # other machines' archives also:

        borg prune                          \
            --list                          \
            --prefix $HOSTNAME-             \
            --show-rc                       \
            --keep-daily    7               \
            --keep-weekly   4               \
            --keep-monthly  6               \

        prune_exit=$?

        # use highest exit code as global exit code
        global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit ))

        if [ ${global_exit} -eq 1 ];
        then
            info "Backup and/or Prune finished with a warning"
        fi

        if [ ${global_exit} -gt 1 ];
        then
            info "Backup and/or Prune finished with an error"
        fi

        exit ${global_exit}
fi

exit ${backup_exit}

This script can be called daily or using a cronjob.

After creating a few backups, the results can look like this:

                       Original size      Compressed size    Deduplicated size
This archive:               75.24 GB             61.03 GB             53.69 GB
All archives:              100.14 GB             84.17 GB             53.69 GB

Note the deduplicated size vs. original size, showing the amount of space that’s possible to save using borg.

Does This Syncthing Work?

python backup

Information Leak in Docker

docker vulnerability

Docker Breakout Using X11

docker pentesting hacking