Build Bitwarden

YESreplaces $1.65/mosaves $19.8/yrback to the verdict

0%0 of 24 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

Your own password server without writing a password manager: vaultwarden in Docker behind Caddy on HTTPS, the official Bitwarden apps and extensions pointed at it, signups closed after your account exists, an admin token done the way current vaultwarden requires, encrypted nightly backups off the box, and a restore you have actually performed before trusting it.

estimated effort one sittingthe files for this build are in the project pack

Servervaultwarden/server in DockerTLSCaddyClientsThe official Bitwarden apps and extensionsBackupstar plus age encryption, off the box

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Every step below is a command you type or a file you edit.

    Get it VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. open ↗

    Verify You can open a folder and run a command in its terminal

  2. installfree

    Why History for your code, and the way most hosts deploy.

    Get it Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. open ↗

    Verify git --version prints a version

  3. about $5 a month

    Why This needs one process running all the time with a public address. The vault must be reachable from your phone and laptops.

    Get it Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. open ↗

  4. installfree

    Why vaultwarden runs as a container.

    Get it docs.docker.com/engine/install/ubuntu, then add your user to the docker group. open ↗

    Verify docker compose version prints

  5. free on a domain you own

    Why The browser extensions and mobile apps refuse to talk to a non-HTTPS server; a domain is how you get a certificate.

    Get it vault.yourdomain.com, one A record to the VPS.

  6. installfree

    Why Backups of your entire credential set must be encrypted at rest wherever they go.

    Get it apt install age, or from the releases page. Generate a key pair with age-keygen and keep the private key in your existing password manager. open ↗

    Verify age --version prints

  7. have readyfree

    Why Phase 5 imports it. Bitwarden exports are unencrypted by default; you will delete the file securely afterwards.

    Get it In your current manager: Export vault, JSON or CSV. Keep it only until import.

  8. cents a month

    Why A backup on the server being backed up is not a backup.

    Get it Backblaze B2 or Cloudflare R2 plus rclone on the VPS. open ↗

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DOMAINrequiredhttps://vault.yourdomain.comThe exact public address; vaultwarden needs it for WebAuthn and links.
ADMIN_TOKENsecretrequired'$argon2id$v=19$...'An Argon2 PHC hash from docker run --rm -it vaultwarden/server:latest /vaultwarden hash. In a Compose .env either single-quote the whole value or escape every $ as $$.
SIGNUPS_ALLOWEDrequiredtruetrue until your account exists, then false.
AGE_RECIPIENTrequiredage1...Your age public key, for encrypting backups.

The build, in order

  1. Bring it up behind HTTPS

    The web vault over a valid certificate, data surviving a restart. Never run it on plain HTTP even briefly.

    1. Files docker-compose.yml.env

    2. vault.yourdomain.com with reverse_proxy localhost:80 (the container's port).

      Files Caddyfile

    3. terminal
      docker compose up -d
      curl -I https://vault.yourdomain.com
    done when · tick each as it passes
    watch out
    • The clients will not connect over HTTP and you will spend the time debugging the wrong layer.
  2. The admin token

    The step that wastes an evening, done right.

    1. terminal
      docker run --rm -it vaultwarden/server:latest /vaultwarden hash
    2. Single-quote the whole value, or escape each $ as $$; Compose otherwise interprets them as variables.

    done when · tick each as it passes
  3. Lock it down

    One account, signups closed, nothing else exposed.

    1. terminal
      nmap -p- vault.yourdomain.com
    done when · tick each as it passes
  4. Clients

    Extension, desktop and mobile all logged in to your server.

    done when · tick each as it passes
  5. Import

    Everything moved, and the plaintext export gone.

    done when · tick each as it passes
  6. Backup, and a restore drill

    Encrypted nightly backup off the box, and a restore you performed.

    1. Files backup.shrestore.sh

      terminal
      docker compose stop && tar czf - vw-data | age -r $AGE_RECIPIENT -o /tmp/vw-$(date +%F).tgz.age && docker compose start
      rclone copy /tmp/vw-$(date +%F).tgz.age remote:vault-backups/
    done when · tick each as it passes
    watch out
    • Until the restore drill is done, keep the hosted subscription.
  7. Updates and the honest handover

    A repeatable update and the trade written down.

    1. Files update.sh

    2. The admin-token command and the $$ trap, the restore date, and the reassurance plus warning: clients cache the vault so a dead server locks you out of nothing, but an unpatched server holding every credential is a risk you took on.

      Files README.md

    done when · tick each as it passes
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more security.