105 lines
3.8 KiB
Markdown
105 lines
3.8 KiB
Markdown
# 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. Navigate to `http://100.71.155.7:8080` from a Tailscale-connected device (or `https://vault.iamgmb.com` from anywhere)
|
|
2. Create your account (first user to register becomes the admin)
|
|
3. Navigate to the admin panel (uses ADMIN_TOKEN)
|
|
4. 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`:
|
|
```
|
|
vault.iamgmb.com {
|
|
reverse_proxy localhost:8080
|
|
}
|
|
```
|
|
Then `caddy fmt --overwrite /etc/caddy/Caddyfile && systemctl reload caddy`.
|