Files

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.comlocalhost: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.