Deploy and configure BorgWarehouse using Docker and Ansible

Hello there,

a few days ago, when I was checking my backups on “”, I thought “why does nothing similar to this exist, that I can self-host”. Well, apparently there is. It’s still a very young project but already very impressive.

Today I want to show you how to deploy and configure “BorgWarehouse” as a docker container (which was recently implemented) using Ansible and set up a simple backup.

I will also use Traefik as a reverse proxy for the WebUI. This is optional, and I will probably make a separate guide on Traefik at some point. But, I want to provide the whole configuration that I am actually using.

So, what is BorgWarehouse? Simply put, it,s a central borg repository management system with a pretty WebUI. It’s quick and easy to configure.

Let’s begin.

In this guide, I will provide the Ansible role I am actually using. You can find the docker compose file on the official documentation. I also assume you have a functioning docker server. If not, I have a guide on how to set this up here.

Generate SSH Key Pair

We need an SSH key pair for later, so let’s create that one first.

I will create this on the server I want to create the backups from. But it doesn’t really matter where you create it, as long as the files are at the correct location.

Give it a password if you want, I will create one without.

nextcloud :: ~ » ssh-keygen -t ed25519 -f ~/.ssh/borgwarehouse-backup
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /admin/.ssh/borgwarehouse-backup.
Your public key has been saved in /admin/.ssh/
The key fingerprint is:
SHA256:Dmh6emDysw3YYdauq3OLnAMKWo5P2NKR+S6vOEpn3OA admin@nextcloud.gtu.local
The key's randomart image is:
+--[ED25519 256]--+
|                 |
|                 |
|                 |
|   + .           |
|  B.+ . S        |
|+O*Bo  o         |
|OOBE+.  .        |
|O*XB.            |
|*OXX+            |

Deploying BorgWarehouse on Docker using Ansible

First, let me show you how I build my roles in Ansible.

My folders look like this.

../Ansible/roles/ansible-role-docker/{files, handlers, tasks, vars}

And here is the file structure I am using for this.


- hosts: docker
  gather_facts: true
  become: true
   - ansible-role-docker


-- import_tasks: create-container-folders.yml
   tags: [create-folders, docker]
-- import_tasks: docker.yml
   tags: [docker]
-- import_tasks: crontab.yml
   tags: [crontab, docker]


Make sure to enter the username that has the UID and GID with 1001
  - name: Create borgwarehouse folders
     path: "{{ item }}"
     state: directory
     mode: 0770
     owner: borgwarehouse
     group: borgwarehouse
     - /etc/borgwarehouse/app/config
     - /etc/borgwarehouse/ssh
     - /etc/borgwarehouse/sshd
     - /etc/borgwarehouse/repos
     - /etc/borgwarehouse/tmp
     - /etc/borgwarehouse/logs


This creates a separate docker network and assigns the network and IP to the borgwarehouse container.
If you want to use this without Traefik remove the yellow parts and add the red lines. Also modify the green lines.
  - name: Create docker proxy network
       name: backup
       internal: no
         - subnet:

  - name: Create borgwarehouse
       name: borgwarehouse
       image: borgwarehouse/borgwarehouse:latest
        - name: backup
       pull: yes
       state: started
## Remove this line if you use Traefik
        - /etc/borgwarehouse/app/config:/home/borgwarehouse/app/config
        - /etc/borgwarehouse/ssh:/home/borgwarehouse/.ssh
        - /etc/borgwarehouse/sshd:/etc/ssh
        - /etc/borgwarehouse/repos:/home/borgwarehouse/repos
        - /etc/borgwarehouse/tmp:/home/borgwarehouse/tmp
        - /etc/borgwarehouse/logs:/home/borgwarehouse/logs
         WEB_SERVER_PORT: "3000"
         SSH_SERVER_PORT: "2222"
         FQDN: ""
         NEXTAUTH_URL: ""
         NEXTAUTH_SECRET: "<random-secret>"
         CRONJOB_KEY: "<another-random-secret>"
         UID: "1001"
         GID: "1001"
         CONFIG_PATH: "/home/borgwarehouse/app/config"
         SSH_PATH: "/home/borgwarehouse/.ssh"
         SSH_HOST: "/etc/ssh/ssh_host"
         BORG_REPOSITORY_PATH: "/home/borgwarehouse/repos"
         TMP_PATH: "/home/borgwarehouse/tmp"
         LOGS_PATH: "/home/borgwarehouse/logs"

         MAIL_SMTP_FROM: ""
         MAIL_SMTP_HOST: "<your-smtp-server>"
         MAIL_SMTP_PORT: "25"
         MAIL_SMTP_LOGIN: ""
         MAIL_SMTP_PWD: "<your-secret-password"

## Remove the comment if you use Traefik
       # labels:
        # traefik.enable: "true"
        # traefik.http.routers.borgwarehouse.entryPoints: "websecure"
        # traefik.http.routers.borgwarehouse.rule: "Host(``)"
        # traefik.http.routers.borgwarehouse.tls.certresolver: "myresolver"
        # traefik.http.routers.borgwarehouse.tls: "true"
        # "3000"
## Only uncomment if you want to restrict the access to specific IPs
        # traefik.http.routers.borgwarehouse.middlewares: "borgwarehouse-middlewares"
        # traefik.http.middlewares.borgwarehouse-middlewares.ipwhitelist.sourcerange: ","


This is for the repository status update. Used disk space and so on.
  - name: crontab "trigger Borgwarehouse API Status"
      name: Trigger Borgwarehouse API Status
      minute: "5"
      job: "curl --request POST --url '{{borgwarehouse_url}}/api/cronjob/checkStatus' --header 'Authorization: Bearer {{borgwarehouse_crontab_key}}' ; curl --request POST --url '{{borgwarehouse_url}}/api/cronjob/getStorageUsed' --header 'Authorization: Bearer {{borgwarehouse_crontab_key}}'"


borgwarehouse_url: ""
borgwarehouse_crontab_key: "<random-secret>"


                 ansible_user: ansible
                 ansible_become_pass: <super-secrete-sudo-pass>
                 ansible_ssh_private_key_file: ssh_key/ansible
                 ansible_python_interpreter: /usr/bin/python3

I don’t really know if this way of showing the configuration is really helpful. 😛
If not, just leave a short comment, I will try to make it a bit more readable.

Anyway. Let’s deploy it.

fedora :: ~ » cd ~/Nextcloud/Ansible
fedora :: Ansible » ansible-playbook -i hosts roles/ansible-role-docker.yml

TASK [ansible-role-docker : Create borgwarehouse folders] ********************************************************************************************
ok: [docker] => (item=/etc/borgwarehouse/app/config)
changed: [docker] => (item=/etc/borgwarehouse/ssh)
ok: [docker] => (item=/etc/borgwarehouse/sshd)
changed: [docker] => (item=/etc/borgwarehouse/repos)
ok: [docker] => (item=/etc/borgwarehouse/tmp)
ok: [docker] => (item=/etc/borgwarehouse/logs)

TASK [ansible-role-docker : Create borgwarehouse] ****************************************************************************************************
ok: [docker]

PLAY RECAP ****************************************************************************************************************************************
docker             : ok=9    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Now, the container should be up and running. If you use Traefik, your server should be reachable via “”. Replace with your URL of course. If you use it without a reverse proxy, type in the port at the end. “”.

Setting up BorgWarehouse

Alright. We should be on the WebUI now. The default login is “admin” “admin”.

BorgWarehouse login page.

Once in, you should see an empty window with the option to add a new repository. But first, we should change the default password. Click on the username on the top right.

Deploy and configure BorgWarehouse using Docker and Ansible

Here you can set your new password. While at it, also set your E-Mail address. This is only necessary if you want to use the E-Mail notification feature.

Deploy and configure BorgWarehouse using Docker and Ansible

Now, switch to the “notifications” tab. Here, we can enable the E-Mail notification and send a test E-Mail. Below that, we have the option to set up and enable the apprise alert service. For this we need an additional docker container, “apprise”. I am not going to use it.

Deploy and configure BorgWarehouse using Docker and Ansible

Once that’s done, switch back to the main page with the “repository” button on the left.

Click on “Add a repository” to create a new one.

Here we can set an “Alias”. This is only for you.

Below it wants a public key which will be used to connect via SSH to the server with borg. This does not allow you to connect to the CLI, it’s only for the backup. Paste the public key we created earlier.

Below that, is the max storage size that can be used with this repository.

Next, you can set the time it should wait before sending you a notification in case the backup does not work.

Once done, click on “Add”.

Alright. Our first repository. On the top right of the repo you can display the SSH URL for your backup. Copy the line for later.

There is actually a great wizard implemented that can guide you through the process of configuring your server or client you want to back up. For this, click on the “Setup Wizard” on the left. It will show you the necessary steps for applications like Vorta.

I will not use this though, so let’s proceed.

Setting up the Backup

Initializing the Repository

Backup to the server we want to back up. First we want to initialize the repository.

# Set the ssh key path
nextcloud :: ~ » export BORG_RSH='ssh -i /admin/.ssh/borgwarehouse-backup'
nextcloud :: ~ » borg init -e repokey-blake2 ssh://
Enter new passphrase: 
Enter same passphrase again: 
Do you want your passphrase to be displayed for verification? [yN]:

Create a Backup

Now we configure our backup. I have a script for this, but a simple one-liner would look like this.

# This will backup the folder /var/www/html/nextcloud.
nextcloud :: ~ » borg create ssh://$(date +%F_%R) /var/www/html/nextcloud/
Keeping archive: NEXTCLOUD-2024-01-11_13:11           Thu, 2024-01-11 13:11:49 [0705bc583001f686bed7d89853c889217b76z93afd79f0a683c328f4768db4c1]
                       Original size      Compressed size    Deduplicated size
Deleted data:                    0 B                  0 B                  0 B
All archives:                5.62 GB              5.32 GB              5.21 GB

                       Unique chunks         Total chunks
Chunk index:                    5901                 8129

# To remove old Backups
nextcloud :: ~ » borg prune --stats --list --keep-hourly=0 --keep-daily=5 --keep-weekly=0 --keep-monthly=1 ssh://

Here is also the script, if you are interested.

set +o history
export BORG_RSH='ssh -i /root/.ssh/vserver'
export BORG_PASSPHRASE="<your-secret-repo-passphrase>"
set -o history

if borg create ssh://$(date +%F_%R) /var/www/html/nextcloud/ --exclude '/var/www/html/nextcloud/data/'; then
echo success
curl -d "Nextcloud to BorgWarehouse backup failed" https://backup:'<super-secret>'@ntfy-server/Backup

# Remove old Backups
borg prune --stats --list --keep-hourly=0 --keep-daily=5 --keep-weekly=0 --keep-monthly=1 ssh://

Alright. Let’s take a look at the WebUI.

First the repository itself.

And the “Monitoring” page.

Keep in mind that the cronjob we created earlier has to run, for the used storage to show.

You can also manually execute a stat refresh.

Monitoring / Stat refresh

Either I don’t get it, or this part isn’t really well documented. It’s just a single line in the documentation. Anyway, if you want the “used storage” and “monitoring” page to actually show anything, you have to execute the “getStorageUsed” and “checkStatus” API calls.

Here is the line, you can also check this on the documentation page at the bottom under “Scheduled tasks”.

## Replace the "borgwarehouse_crontab_key" with the key you used in the container deployment. 
docker :: ~ » curl --request POST --url '' --header 'Authorization: Bearer {{borgwarehouse_crontab_key}}' ; curl --request POST --url '' --header 'Authorization: Bearer {{borgwarehouse_crontab_key}}'

Ok. That is it. Till next time.

Leave a Reply