Build Bitwarden
YESreplaces $1.65/mosaves $19.8/yrback to the verdict
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.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- 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 - 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 - accountabout $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 ↗
- 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 - accountfree 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.
- 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 - 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.
- accountcents 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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DOMAIN | required | https://vault.yourdomain.com | The exact public address; vaultwarden needs it for WebAuthn and links. |
ADMIN_TOKENsecret | required | '$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_ALLOWED | required | true | true until your account exists, then false. |
AGE_RECIPIENT | required | age1... | Your age public key, for encrypting backups. |
The build, in order
Bring it up behind HTTPS
The web vault over a valid certificate, data surviving a restart. Never run it on plain HTTP even briefly.
Files
docker-compose.yml.envvault.yourdomain.com with reverse_proxy localhost:80 (the container's port).
Files
Caddyfile- terminal
docker compose up -d curl -I https://vault.yourdomain.com
done when · tick each as it passeswatch out- The clients will not connect over HTTP and you will spend the time debugging the wrong layer.
The admin token
The step that wastes an evening, done right.
- terminal
docker run --rm -it vaultwarden/server:latest /vaultwarden hash
Single-quote the whole value, or escape each $ as $$; Compose otherwise interprets them as variables.
done when · tick each as it passesLock it down
One account, signups closed, nothing else exposed.
- terminal
nmap -p- vault.yourdomain.com
done when · tick each as it passesClients
Extension, desktop and mobile all logged in to your server.
done when · tick each as it passesImport
Backup, and a restore drill
Encrypted nightly backup off the box, and a restore you performed.
Files
backup.shrestore.shterminaldocker 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 passeswatch out- Until the restore drill is done, keep the hosted subscription.
Updates and the honest handover
A repeatable update and the trade written down.
Files
update.shThe 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
That is the whole plan for Bitwarden. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Emergency access, organizations and family sharing.
- Pretending you have a security team.
- managed hosting
- premium support
- emergency access
- polished admin
- reduced security maintenance risk
- A second admin device with hardware-key 2FA on your account
- Fail2ban in front of the login endpoint
Need the files? The project pack on the verdict page hands your agent the whole brief · more security.