Skip to content
NewPassbox is here: on the web, desktop, iPhone, Android and your own server.Passbox is here · See all apps

Self-hosting

Host Passbox yourself

Passbox is one Rust program that serves the API and the web vault. The Docker Compose setup runs it with PostgreSQL, Caddy for automatic HTTPS and encrypted nightly backups. It is MIT-licensed, so you can run it for yourself, your family or your whole company at no cost, with every feature.

  • Docker Compose

    Passbox, PostgreSQL and Caddy with automatic HTTPS.

  • PostgreSQL

    Separate roles, row-level security, encrypted backups.

  • MIT License

    Every feature, free, for any number of members.

Quick start

Clone the repository, fill in the settings and start everything with Docker Compose:

terminal
$ git clone https://github.com/passboxnet/passbox$ cd passbox/deploy/compose$ cp .env.example .env    # then fill in every value$ docker compose up -d

Before the first start, set PASSBOX_DOMAIN, ACME_EMAIL, the four database passwords (openssl rand -hex 32) and the public backup key BACKUP_AGE_RECIPIENT in .env. Caddy then gets the certificate, the migrate service sets up the database, and the vault is available at your domain. Create your account first; it becomes the instance admin.

deploy/compose/docker-compose.yml
services:  postgres:    image: docker.io/library/postgres:17.10-bookworm    volumes:      - postgres-data:/var/lib/postgresql/data      - ./initdb:/docker-entrypoint-initdb.d:ro    networks: [backend]  # One-shot: applies database migrations, then exits.  migrate:    image: ${PASSBOX_IMAGE:-ghcr.io/passboxnet/server:latest}    command: ["migrate"]    depends_on:      postgres:        condition: service_healthy    networks: [backend]  passbox:    image: ${PASSBOX_IMAGE:-ghcr.io/passboxnet/server:latest}    environment:      PASSBOX_DATABASE_URL: postgres://passbox_app:…@postgres:5432/passbox      PASSBOX_MIGRATION_DATABASE_URL: postgres://passbox_migrator:…@postgres:5432/passbox      PASSBOX_WORKER_DATABASE_URL: postgres://passbox_worker:…@postgres:5432/passbox      PASSBOX_PUBLIC_URL: https://${PASSBOX_DOMAIN:?set in .env}    depends_on:      migrate:        condition: service_completed_successfully    networks: [backend, frontend]  caddy:    image: docker.io/library/caddy:2.11.6-alpine    ports:      - "80:80"      - "443:443"      - "443:443/udp"    networks: [frontend]  # Encrypted backups: pg_dump of the database and a tar of the attachment  # blobs, encrypted with age to BACKUP_AGE_RECIPIENT.  backup:    build: ./backup    environment:      BACKUP_AGE_RECIPIENT: ${BACKUP_AGE_RECIPIENT:?set in .env}    networks: [backend]networks:  # No route to the internet: only Passbox, migrations and backups reach the  # database.  backend:    internal: true  frontend: {}

Back up from day one

Set BACKUP_AGE_RECIPIENT before the first start and keep the matching private key offline. Without it, no backup can be restored.

Requirements

  • A Linux host with Docker Engine 24 or later and Compose v2 (amd64 or arm64)
  • 1 vCPU and 2 GB of memory for a few hundred users
  • A domain name whose DNS records point to the host, with ports 80 and 443 reachable
  • Optional: an SMTP server for invitations and security notices

HTTPS

Browsers only allow the cryptography and clipboard features Passbox needs on HTTPS (or localhost). In the Docker Compose setup, Caddy gets and renews the certificate automatically. If you run the image on its own, put your own reverse proxy with TLS in front of it. The server sends strict security headers and rate-limits sign-in attempts on its own.

Administration

The first account on a fresh server becomes its instance admin. Create yours right after starting the server. Accounts listed in PASSBOX_ADMIN_EMAILS are admins too. Admins find Administration in the web vault's sidebar.

  • Name the server, set a help address and the language of mails
  • Choose who can create an account and require stronger master passwords
  • Invite people, see accounts and their last activity, disable accounts
  • Read the administration log, protected by a hash chain
ModeWho can create an account
openAnyone who can reach the server (default)
invite_onlyOnly people with an invitation
domainsEmails of the allowed domains, plus invited people

Email

Passbox sends invitations and security notices, such as sign-ins from new devices and recovery requests, in English or German. Set PASSBOX_SMTP_URL, PASSBOX_MAIL_FROM and PASSBOX_PUBLIC_URL; without them, mails are written to the log. Mails go through a queue in the database and are retried.

PASSBOX_SMTP_URL='smtps://passbox%40example.com:app-password@smtp.example.com:465'PASSBOX_MAIL_FROM='Passbox <passbox@example.com>'PASSBOX_PUBLIC_URL='https://passbox.example.com'

Configuration

Every option can be set as a command-line flag or an environment variable. In the Docker Compose setup, .env fills in the database URLs and the most common options; add others to the environment of the passbox service.

VariableDefaultDescription
PASSBOX_BIND0.0.0.0:8080Address and port to listen on
PASSBOX_DATABASE_URLrequiredPostgreSQL URL for the API; its role is subject to row-level security
PASSBOX_MIGRATION_DATABASE_URLPASSBOX_DATABASE_URLPostgreSQL URL of the role that owns the schema and runs migrations
PASSBOX_WORKER_DATABASE_URLPASSBOX_DATABASE_URLPostgreSQL URL of the role for background jobs such as the mail queue
PASSBOX_WEB_DIRunsetDirectory with the built web vault; the image sets it
PASSBOX_PUBLIC_URLunsetAddress users open Passbox at, for links in mails
PASSBOX_SMTP_URLunsetSMTP server for outgoing mail
PASSBOX_MAIL_FROMunsetSender of mails, required with an SMTP server
PASSBOX_ADMIN_EMAILSunsetComma-separated emails that are always instance admins
PASSBOX_TRUST_PROXYfalseTake the client IP from X-Forwarded-For. Enable only behind a reverse proxy that sets it; IPs are used for rate limiting and shown in the activity log
PASSBOX_LOG_FORMATprettypretty or json
RUST_LOGinfoLog level, for example debug

Database

Passbox stores everything in PostgreSQL. On its first start, the postgres service creates the database and three roles, each with its own password from .env:

RoleUsed for
passbox_migratorOwns the schema; the migrate service runs migrations with it
passbox_appThe API; row-level security applies to it
passbox_workerBackground jobs such as the mail queue, with narrow rights

The database sits on an internal network without internet access and is not published on the host. To use your own PostgreSQL server instead, set PASSBOX_DATABASE_URL and ideally the two other URLs; they default to PASSBOX_DATABASE_URL.

PASSBOX_DATABASE_URL=postgres://passbox_app:…@db.example.com/passbox?sslmode=verify-fullPASSBOX_MIGRATION_DATABASE_URL=postgres://passbox_migrator:…@db.example.com/passbox?sslmode=verify-fullPASSBOX_WORKER_DATABASE_URL=postgres://passbox_worker:…@db.example.com/passbox?sslmode=verify-full

Backups

The backup service makes a backup every 24 hours and keeps it for 14 days (BACKUP_INTERVAL_HOURS, BACKUP_KEEP_DAYS). Everything is encrypted with age to your public key; the private key stays offline.

  • Database: a pg_dump in custom format, including the server secrets.
  • Attachments: an archive of the attachment files, which the clients have already encrypted.
  • Off the machine: copy the backups directory elsewhere, for example with rsync or restic. A backup on demand: docker compose run --rm backup once.

To restore, stop Passbox and run the restore service with the private key and the time of the backup. It replaces the database and the attachments; docker compose up -d then starts everything again and migrates an older backup.

$ docker compose stop passbox backup$ BACKUP_AGE_KEY_FILE=/path/to/passbox-backup-key.txt \$   docker compose --profile restore run --rm restore 20261025T030000Z$ docker compose up -d

Keep the server secrets

Losing the server_secrets table invalidates all logins. Users cannot sign in anymore, although their data stays intact. The backups include it; test a restore from time to time.

Updating

Make a backup, then pull the new images and restart. The migrate service applies database migrations before Passbox starts. Migrations are not reversed; to go back, restore the backup. For production, pin a version in PASSBOX_IMAGE.

$ docker compose pull && docker compose up -d