# A web interface for the system layer, not just the repository

> borgwarehouse replaces the command-line work of running a central BorgBackup server, where every repository needs a system user, an authorised SSH key, a quota and storage carved out, with one Next.js application that automates that whole layer, monitors repository health, sends alerts through email, a push gateway or signed webhooks, and refuses by design to ever hold your repository passphrase.

**Ravinou/borgwarehouse** — A fast and modern WebUI for a BorgBackup's central repository server.

- Repository: https://github.com/Ravinou/borgwarehouse
- Website: https://borgwarehouse.com
- Stars: 674 · Forks: 38
- Language: TypeScript
- License: AGPL-3.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/ravinou-borgwarehouse

## The unit of work is a system user, not a folder

The pitch explains what is actually being automated, and the list is more than repository management.

Running a central BorgBackup server by hand means creating a system user, attaching an SSH key, setting a quota and carving out storage, for every repository. So adding a repository is not one command in that world, it is four, and removing one leaves you guessing what you forgot to clean up.

BorgWarehouse treats that whole layer as the object. Creating a repository creates the user, the authorised key and the quota together. Editing and deleting go through the same interface. The stated outcome is a repository in a few clicks, a ready-to-paste SSH command for the client side, and visible health per repository, with no terminal involved.

The install is one command that fetches the published installer and runs it, or you can run the published image directly:

```bash
curl -fsSL https://raw.githubusercontent.com/Ravinou/borgwarehouse/main/docker/install.sh | bash
```

Two details make that claim credible rather than aspirational. A repository can be given a custom icon so you can spot it in a list of twenty, and there is protection against repository deletion, which for a backup server is the difference between an interface and a hazard.

The client side is handled too, with an optional LAN variant of the generated SSH command so a client on the same network can use a plain hostname instead of a fully qualified one.

Quotas are per repository rather than global, which is the granularity you want when one backup job is small and another is a media library.

## Append-only mode, and storage you choose per repository

Two features here are about defending data rather than managing it.

Append-only mode prevents modification and deletion of repository contents, and the stated reason is ransomware or a compromised client. That is the correct threat model for a backup server: the same credentials that let a machine back up are the ones an attacker would use to encrypt everything you have. Append-only means a stolen key can add data but cannot remove what is already there.

Per-repository external storage is the other one. Instead of one repository directory, each repository can live on mounted storage you choose, with the options named as a network filesystem, a Windows file share, a dedicated disk or a cloud mount exposed through a remoting tool. The mount is selected at creation time rather than moved afterwards, and there is a live reachability check.

That check matters more than it sounds. A backup server with per-repository mounts has a failure mode a single directory does not have, which is a mount that silently disappears and a repository that appears healthy while writing nowhere. Testing reachability at creation catches the misconfiguration, and the monitoring features handle the later case.

So the two features together describe a server designed for heterogeneous storage with an immutability guarantee, rather than a single-disk convenience wrapper.

## Monitoring is a status flag plus a staleness threshold

The monitoring side is deliberately small, and both halves of it are things a human actually needs.

Per repository there is a real-time status, described as healthy or down, and there is a dashboard across all of them. The second feature is an alert for no recent backup, driven by a per-repository threshold rather than a global one.

That threshold is the piece with judgement in it. A repository that backs up nightly and one that backs up weekly need different thresholds, and a single global setting produces either noise on the fast one or silence on the slow one. Making it per repository moves that judgement to the person who knows the job.

Alerting has three channels. Email over SMTP is the plain one. A push gateway aggregates a hundred-odd services, with Discord, Telegram, Slack, Gotify and a lightweight notification service named. Webhooks are the third, and they carry an optional custom secret header so the receiving end can validate the payload.

That last option is the one to enable. A webhook endpoint with no shared secret is a URL that anyone who learns it can post to, and anyone who reads the documentation learns which URL it is.

The status checks and storage checks are also runnable as scheduled jobs, which is how monitoring continues when nobody has the interface open.

## No default credentials, and a secret that logs everyone out

The authentication section is where the operational details live.

There are local accounts and single sign-on through OAuth or OIDC, with GitHub, Google, Microsoft and GitLab named, plus any generic OIDC provider. Accounts can be linked to an existing local account, so signing in with a provider and still being the same user is possible rather than creating a second identity. Password login can be disabled entirely, leaving single sign-on as the only door.

There are no default credentials. The first run goes through a setup wizard, so the first account is one you create rather than one that shipped in the image.

Then an admin tool that can reset a password or revoke sessions. Session revocation matters more here than in most applications, because this interface can create users and attach SSH keys.

The environment sample has a comment about the authentication secret that is worth reading twice. If you leave it unset, a new random secret is generated at every restart, which logs out all users. It also shows the generation command. So the failure mode of an unset secret is not a security hole, it is an application that logs everyone out each time the container bounces, which in practice means people will paste a value in from the sample file.

The base URL variable carries a matching warning: its scheme has to match how you actually access the instance, or cookie behaviour will not match your address.

## The passphrase is the feature that will never be built

One section states a boundary rather than a capability, and it is the most consequential decision in the project.

BorgWarehouse manages the server side: repositories, users, SSH access, quotas and monitoring. It is explicitly not meant to take over client-side responsibilities. And it never asks for, stores, or has access to your repository passphrase.

Because Borg encrypts on the client before data reaches the server, the server holds ciphertext. That means the application cannot read, decrypt or browse your backups, and the documentation says it never will.

The last sentence is the one that makes this a design commitment rather than a current limitation. Any feature that would require the passphrase will simply never be built.

That rules out a whole category of features that competitors might add: server-side deduplication across repositories, server-side integrity checking of encrypted contents, any browse-your-backups feature, any deduplicating proxy. Each of those would require the server to see plaintext or the key.

So the trade is explicit. You give up features that a server which held the key could offer, and in exchange the compromise of this application exposes ciphertext and the ability to create users, not the contents of your backups. For a backup server, that is the right side of the trade, and it is worth checking that a project you are evaluating has made it deliberately.

## Two persistence stores and a build that edits its own config

The dependency list is readable and each entry maps to a feature, with two that are worth pausing on.

The framework is a current Next.js with React underneath. Charts are a charting library with a React wrapper, which is what the monitoring dashboard renders. Date handling has a dedicated library. A form library and a select component handle the creation flows, a toast library handles feedback, a media-query hook handles dark mode, and a fetch cache library handles polling.

Authentication is a dedicated library that handles local accounts and the OAuth and OIDC providers together. Passwords are hashed with a bcrypt implementation. Mail is a nodemailer wrapper. Icons are a tabelled icon set.

Now the two. One store is an SQLite binding, which is the structured database. The other is a lowdb-style JSON file store. A single application keeping both means there are two persistence mechanisms with different durability and concurrency characteristics, and knowing which data lives in which is not something the README explains.

The build is more interesting. The image is built in stages from a slim Node 24 base, using a frozen lockfile so a build cannot silently pick up a new dependency version. Then one line edits the project's configuration file during the build to switch the framework output to standalone mode, which is what lets the runtime image carry only the built server rather than the whole tree.

Editing a config file with a text substitution during the build is a pragmatic trick that works until the config format changes, and it is the kind of thing that breaks on a framework upgrade rather than on a code change.

## The image deletes its SSH host keys on purpose

The container build is where the security reasoning becomes concrete, and four decisions in it are deliberate.

First, the SSH host keys generated when the SSH server package is installed are deleted, with the reason given in the file: so the image never ships shared host keys. Fresh ones are generated at first boot by the entrypoint. That is correct practice, since a host key baked into a published image is the same for every install and is therefore worthless as an identity, and it has a consequence worth knowing, which is that restoring a configuration directory onto a new host will present a different key to your clients.

Second, the default user shipped with the Node base image, which has ID 1000, is removed, specifically to avoid conflicting with the configured user ID that will be 1000. The application user is then created with ID 1001 and a locked password, which is right, since the account exists to own files and not to log in.

Third, the application files stay owned by root and world-readable, and the running process cannot write them. The stated reason is immutability: a compromised application cannot rewrite its own binaries to persist. Writable paths, which are the mounted volumes and the home directory, are changed at runtime by the entrypoint instead.

Fourth, the runtime stage adds a backports repository to the package sources and installs the backup tool and SSH server from it, along with a process supervisor and a privilege-dropping utility. The container also runs as whatever user ID you configure, which the sample file constrains: it must match the owner of your mounted directories and must not be root.

## Conclusion

It fits a home lab or a small team that has decided Borg is the right backup tool and does not want to administer the server side by hand, since the value is removing four repeated shell procedures rather than adding a scheduling model you do not already have in Borg. Three things to set before you expose it. The authentication secret is auto-generated on every restart if you leave it unset, which logs out every user each time the container restarts, so generate one. PUID and PGID must match the owner of your mounted directories and cannot be root, and the image deliberately removes the default user with ID 1000 to keep them from colliding. And the image ships no SSH host keys at all, generating fresh ones at first boot, which is correct but means a restored configuration directory will present new keys to your clients.

## FAQ

### What does BorgWarehouse actually automate?

The whole server-side layer, not just the repository folder. Creating a repository creates the system user, the authorised SSH key and the storage quota together, so the four-step manual procedure is done once through the interface. Repositories can then be edited or deleted, given per-repository quotas, pointed at external storage, and given a ready-to-paste SSH command for the client side.

### Does BorgWarehouse need my Borg repository passphrase?

No, and never will. It manages the server side only: repositories, users, SSH access, quotas and monitoring. Backups are encrypted on your client before they reach the server, so the application can never read or decrypt them, and the documentation states that any feature which would require the passphrase will simply never be built.

### How do I install BorgWarehouse?

With a single installer fetched over the network and piped to your shell, or by running the published Docker image directly. The compose file expects host ports for the web interface and the SSH service, four host directories to mount for configuration, SSH material, host keys and repository storage, and a user and group ID that must match the owner of those directories and cannot be root.

### What happens if I do not set the authentication secret?

A new random secret is generated at every restart, which logs out all users. That is not a security weakness, but it means an instance without the variable set will sign everybody out each time the container restarts, which in practice pushes people toward pasting a value from the sample file. The sample shows the command to generate one.

### How does BorgWarehouse protect backups and notify you?

Repositories can be set to append-only mode, which prevents modification and deletion against ransomware or a compromised client, and the image deliberately ships no SSH host keys, generating fresh ones at first boot. Monitoring shows per-repository healthy or down status and raises a no-recent-backup alert on a per-repository threshold, delivered by email, by a push gateway covering services like Discord, Telegram and Slack, or by webhook with an optional custom secret header.

## Sources

- [License: AGPL-3.0](https://github.com/Ravinou/borgwarehouse/blob/main/LICENSE)
- [Project website](https://borgwarehouse.com)
- [Ravinou/borgwarehouse on GitHub](https://github.com/Ravinou/borgwarehouse)
- [README](https://github.com/Ravinou/borgwarehouse/blob/main/README.md)
- [Releases](https://github.com/Ravinou/borgwarehouse/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ravinou-borgwarehouse
