3.8 KiB
Vaultwarden Deployment Pattern
Standard for deploying a private password manager on this infrastructure. Everything behind Tailscale, zero public exposure.
Prerequisites
- Docker and Docker Compose installed
- A Tailscale-assigned IP for the host (for DOMAIN config)
- UFW port opened for the chosen port
Service layout
~/docker/vaultwarden/
├── docker-compose.yml ← pinned version, never :latest
├── .env ← ADMIN_TOKEN (generated via openssl rand -base64 48)
├── data/ ← SQLite DB, RSA keys (backed up every 15 min to S3)
└── CHANGELOG.md ← every config change logged
docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:1.33.2
container_name: vaultwarden
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./data:/data
environment:
- DOMAIN=http://100.71.155.7:8080
- ADMIN_TOKEN=${ADMIN_TOKEN}
- SIGNUPS_ALLOWED=false
- INVITATIONS_ALLOWED=true
env_file:
- .env
Deployment steps
# Generate admin token
ADMIN_TOKEN=$(openssl rand -base64 48)
echo "ADMIN_TOKEN=$ADMIN_TOKEN" > ~/docker/vaultwarden/.env
# Create compose file
# Start container
cd ~/docker/vaultwarden && docker compose up -d
# Allow through firewall
ufw allow 8080/tcp comment 'Vaultwarden'
# Verify locally
curl -s http://localhost:8080/ | head -5
DNS and DOMAIN
The DOMAIN env var must match how the client accesses it. The web vault is a JavaScript SPA that uses this to build API URLs. Common options:
| Access method | DOMAIN value |
|---|---|
| Localhost | http://localhost:8080 |
| Tailscale IP | http://100.x.x.x:8080 |
| Public domain (HTTPS) | https://vaultwarden.example.com |
| Tailscale Serve (HTTPS) | https://100.x.x.x |
| Caddy reverse proxy (HTTPS) | https://vault.iamgmb.com |
Current deployment (Jul 12, 2026): Caddy on Core reverse-proxies vault.iamgmb.com → localhost:8080 with auto Let's Encrypt TLS. DNS record: A vault.iamgmb.com → 152.53.192.33 (Cloudflare proxied, TTL 120).
If the DOMAIN is wrong, the web vault shows a spinning loader forever and docker logs vaultwarden will NOT show request errors (the browser makes the API calls, not the server). Fix by updating DOMAIN and restarting.
Getting started as admin
- Navigate to
http://100.71.155.7:8080from a Tailscale-connected device (orhttps://vault.iamgmb.comfrom anywhere) - Create your account (first user to register becomes the admin)
- Navigate to the admin panel (uses ADMIN_TOKEN)
- Disable new user signups in admin panel (already set in compose, but verify)
iOS Browser Issue
Safari on iOS blocks plain HTTP connections to LAN/Tailscale IPs. Use Chrome on iOS, or use the HTTPS endpoint https://vault.iamgmb.com which works on all browsers including Safari.
Backup
The entire ~/docker/vaultwarden/ directory (including data/) is synced to S3 every 15 min via hermes-live-sync.sh. The database is SQLite — this means WAL files also sync.
Pitfalls
- DOMAIN must be set correctly — wrong DOMAIN = spinner that never resolves. This is the #1 deployment mistake.
- Port must be opened in UFW — Tailscale gets traffic to the server, but UFW still needs to permit the port.
- ADMIN_TOKEN plaintext warning is cosmetic — Vaultwarden warns about plaintext admin tokens at startup. This is normal for self-hosted deployments.
- First user = admin — The first registered account becomes the admin console user.
- Safari blocks HTTP locally on iOS — Use Chrome or the HTTPS endpoint instead.
- Caddy must have handle for vault.iamgmb.com — Add to
/etc/caddy/Caddyfile:Thenvault.iamgmb.com { reverse_proxy localhost:8080 }caddy fmt --overwrite /etc/caddy/Caddyfile && systemctl reload caddy.