Skip to content

Borg UI

GitHub GitHub ยท Docs


Borg UI is a self-hosted web interface for Borg: browse archives, search for a file, restore it, run checks, and see what each repository holds. It can also create repositories and schedule backups, but I don't use it for that.

What it's for here

borgmatic does every scheduled backup, from config in git. Borg UI runs on one server as the window into all the repositories: every server's two LAN repos plus the offsite ones, imported as existing repositories with no Borg UI schedules (observe only).

  • Restoring a file is a few clicks instead of borgmatic list --find and extract --path on the right host.
  • Checking that last night's archive exists in every repo, at a glance.
  • It can't make things worse: its SSH key is append-only on every repo, like the backup clients, so it can read and restore but can't delete history. Files it restores land in one dedicated directory, never over live data.

Compose

compose.yaml
services:
  borg-ui:
    image: ainullcode/borg-ui:2.3.10
    container_name: borg-ui
    restart: unless-stopped
    ports:
      - "8083:8081"
    environment:
      PORT: "8081"
      TZ: ${TZ}
      # No Redis: in-memory cache is enough for browsing and restores
      REDIS_HOST: disabled
      # Only read on the very first start (creates user `admin`), so the documented default is never live
      INITIAL_ADMIN_PASSWORD: ${BORG_UI_ADMIN_PASSWORD}
      LOCAL_MOUNT_POINTS: /local
    volumes:
      # App DB (repo list + passphrases), its SSH key + known hosts, generated secret key. Back this up.
      - /path/to/appdata/borg-ui/data:/data
      # Borg cache: regenerable, exclude it from backups
      - /path/to/appdata/borg-ui/cache:/home/borg/.cache/borg
      # Restore target: files restored in the UI land here (exclude it from backups)
      - /path/to/appdata/borg-ui/restore:/local
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://localhost:8081/"]
      interval: 60s
      timeout: 10s
      retries: 3
      start_period: 60s
.env.example
TZ=<TZ>                                   # e.g. Etc/UTC
BORG_UI_ADMIN_PASSWORD=<YOUR_PASSWORD>    # e.g. output of: openssl rand -base64 24
# data/ and restore/ must belong to the container user (1001:1001) BEFORE the first start;
# the entrypoint only chowns when PUID changes
sudo install -d -o 1001 -g 1001 -m 0700 /path/to/appdata/borg-ui/{data,cache,restore}
docker compose up -d

The data volume holds every repo's passphrase

Borg UI stores the repository passphrases in its database under /data. Treat that directory like a password store: 0700, backed up (encrypted, by the host's own borgmatic), and the UI only reachable from the LAN or behind your SSO/reverse proxy. Don't publish it to the internet.


SSH keys and host keys

Borg UI's own key

Borg UI uses one system SSH key for all remote repositories. Generate it in the UI (Remote Machines), as the docs recommend. It's stored in the data volume (/data/ssh_keys, which the container links as ~/.ssh), so it survives upgrades and is covered by the data volume's backup.

If you'd rather import an existing key, mount it read-only and import it from that path:

    volumes:
      - /path/to/borg-ui-ssh:/host-ssh:ro     # contains id_ed25519 (+ .pub), no passphrase

Use a key made for Borg UI only, never a host's or a person's personal key.

Pin it to append-only, on every repo host

Add Borg UI's public key to the borg user on every server that holds repos, and to the offsite account, with a forced command that lists every repo it may read and --append-only:

/var/lib/borg/.ssh/authorized_keys (on server1)
command="borg serve --append-only --restrict-to-repository /srv/borg/server2 --restrict-to-repository /srv/borg/server3",restrict ssh-ed25519 AAAA... borg-ui

Borg 1.x has no true read-only borg serve. Append-only is the closest: Borg UI can list, extract and check, and anything it "deletes" is only marked deleted and can be recovered until the server's weekly compaction. No --storage-quota is needed since it never writes backups.

Because the key is restricted, Borg UI's automatic repository detection when browsing a remote path doesn't work. Type the repository path (/srv/borg/server2) by hand when you import it.

Host keys

Verify each server's host key the first time Borg UI connects instead of blindly accepting it. On the target:

ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

Compare that fingerprint with what Borg UI shows. Borg UI keeps the accepted keys next to its own key in the data volume. If a host key changes later (reinstall), find out why first, then update it in the UI.

Importing the repositories

For each repo: Repositories โ†’ add/import an existing repository โ†’ pick the remote machine (borg@<server>), enter the path, the repo's passphrase, and don't add a schedule. Name them <client> @ <where> (for example server2 @ server1, server2 @ offsite) so you always know which copy you're looking at.


Restoring with it

  1. Open the repository, pick an archive (newest first), browse or search for the file.
  2. Restore to /local (the only local mount point it offers). On the host that's the restore/ directory.
  3. Check the file, then copy it into place yourself on the host it belongs to.

Database dumps show up in the archives under borgmatic/. Restoring them is a job for the command line; see Restore & restore testing.


Sources & further reading