# Vaultwarden Deployment (rayserver) Complete worked example for deploying Vaultwarden (Bitwarden-compatible password manager) on rayserver infrastructure. ## Architecture ``` Browser → VPS (vault.graj-media.com:443, nginx SSL) ↳ Tailscale → Home server (100.93.253.36:3446, nginx SSL) ↳ Vaultwarden (127.0.0.1:8812, Docker) ``` ## Docker Compose ```yaml services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: unless-stopped volumes: - /mnt/seagate8tb/docker/vaultwarden:/data ports: - "127.0.0.1:8812:80" environment: - DOMAIN=https://vault.graj-media.com - SIGNUPS_ALLOWED=false - WEBSOCKET_ENABLED=true - LOG_FILE=/data/vaultwarden.log - LOG_LEVEL=warn ``` **Key settings:** - `DOMAIN` — must match the public URL for correct invite links and CORS - `SIGNUPS_ALLOWED` — enable temporarily for account creation, disable after - `WEBSOCKET_ENABLED` — required for live sync with browser extensions - Bind to `127.0.0.1` — all access goes through nginx ## Nginx WebSocket Configuration Vaultwarden uses WebSocket for the notifications hub. The nginx config needs a dedicated location block for `/notifications/hub` with `Upgrade` and `Connection` headers, plus a separate block for the negotiate endpoint: ```nginx server { listen 3446 ssl; server_name vault.graj-media.com; ssl_certificate /etc/letsencrypt/live/grajmedia.duckdns.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/grajmedia.duckdns.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; client_max_body_size 128M; location / { proxy_pass http://127.0.0.1:8812; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /notifications/hub { proxy_pass http://127.0.0.1:8812; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /notifications/hub/negotiate { proxy_pass http://127.0.0.1:8812; proxy_http_version 1.1; proxy_set_header Host $host; } } ``` Without the WebSocket blocks, browser extensions can't maintain a live sync connection — they'll work for manual syncs but won't push changes in real-time. ## Admin Token Setup After first deployment, set the admin token via Docker environment variable. The token MUST be hashed with argon2id: ```bash # Generate random token ADMIN_TOKEN=$(openssl rand -hex 32) # Hash with argon2id using the Vaultwarden container HASHED=$(echo -n "$ADMIN_TOKEN" | sudo docker exec -i vaultwarden /vaultwarden hash) # Add to docker-compose.yml environment section # - ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$...' # Then restart: sudo docker compose up -d ``` Access admin panel at `https://vault.graj-media.com/admin` — use the raw (unhashed) token to log in. ## Post-Deploy Steps 1. **Open signups temporarily**: set `SIGNUPS_ALLOWED=true`, restart container 2. **Create user account**: visit https://vault.graj-media.com, register 3. **Disable signups**: set `SIGNUPS_ALLOWED=false`, restart container 4. **Configure Bitwarden apps**: Settings → Self-hosted → Server URL: `https://vault.graj-media.com` ## Bitwarden Client Setup All official Bitwarden clients support self-hosted servers: - **Browser extension**: Settings → Self-Hosted Environment → Server URL - **Desktop app**: Settings → Self-Hosted Environment - **Mobile (iOS/Android)**: Settings → Self-Hosted Environment Enter `https://vault.graj-media.com` as the server URL. No trailing slash.