Password managers are critical personal and organizational infrastructure. While commercial password services introduce third-party risk, deploying the upstream Bitwarden stack requires gigabytes of RAM across a dozen enterprise Java and MS SQL containers. In contrast, Vaultwarden—a lightweight, Bitwarden-compatible API written in Rust—operates comfortably in less than 50MB of RAM.
This runbook details the production deployment of Vaultwarden on a Linux VPS using Docker Compose, automated TLS termination via Caddy, native WebSocket live-sync notifications, security hardening (disabling open registrations, Argon2 password hashing), and atomic SQLite pre-backup routines.
1. Architecture & Security Prerequisites
- HTTPS Enforcement: Modern web browsers enforce the WebCrypto API. Browser extensions and mobile apps will refuse to decrypt or synchronize vaults over plain HTTP. An automated reverse proxy (Caddy or Cloudflare Tunnel) is strictly required.
- WebSocket Notifications: Vaultwarden uses WebSockets (on port 3012 or integrated via Rocket) so that changes made on one client instantly synchronize across other devices without polling.
- Signups Disabled: Never leave a password manager open to public user registrations on a public VPS. Once your primary account is created, disable registration immediately.
2. Production Docker Compose Configuration
Create /opt/komodo/stacks/vaultwarden/compose.yaml:
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- WEBSOCKET_ENABLED=true
- SIGNUPS_ALLOWED=false # Lock down registration after creating admin
- INVITATIONS_ALLOWED=false
- SHOW_PASSWORD_HINT=false
- DOMAIN=https://vault.example.com
- ADMIN_TOKEN=${ADMIN_TOKEN} # Generated with argon2 hashing
volumes:
- ./data:/data
networks:
- web_net
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
memory: 256M
networks:
web_net:
external: true
3. Reverse Proxy Configuration with Caddy
Caddy automatically obtains and renews Let’s Encrypt / ZeroSSL TLS certificates and seamlessly handles HTTP/2, HTTP/3, and WebSocket proxying without complex header rewriting.
In your /opt/komodo/stacks/0.proxy/Caddyfile:
vault.example.com {
encode gzip zstd
# Route all traffic to Vaultwarden on internal Docker network
reverse_proxy vaultwarden:80 {
header_up X-Real-IP {remote_host}
}
}
4. Admin Token Generation (Argon2id)
Protect the Vaultwarden administration interface (/admin) using an Argon2id password hash rather than a plain text secret:
# Generate secure Argon2id hash using Docker
docker run --rm -it vaultwarden/server:latest /vaultwarden hash --preset owasp
# Output:
# ADMIN_TOKEN='$argon2id$v=19$m=65536,t=3,p=4$...'
Store this hash in your /opt/komodo/stacks/vaultwarden/.env file:
ADMIN_TOKEN='$argon2id$v=19$m=65536,t=3,p=4$...'
5. Atomic Database Backup Strategy
Vaultwarden uses SQLite with Write-Ahead Logging (WAL) enabled by default. Backing up a live db.sqlite3 file directly can result in corrupted backups if a write transaction is in progress. Always use the SQLite native backup API to create an atomic, crash-consistent snapshot before running Restic backups:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/opt/backups/vaultwarden"
DATA_DIR="/opt/komodo/stacks/vaultwarden/data"
mkdir -p "$BACKUP_DIR"
# Perform atomic SQLite snapshot
docker exec vaultwarden sqlite3 /data/db.sqlite3 ".backup '/data/db_backup.sqlite3'"
# Move snapshot to backup directory
mv "$DATA_DIR/db_backup.sqlite3" "$BACKUP_DIR/vaultwarden-$(date +%F_%H%M%S).sqlite3"
# Keep last 7 days of daily database snapshots
find "$BACKUP_DIR" -name "*.sqlite3" -mtime +7 -delete
Automating Stack Deployment with Ansible
You can deploy Vaultwarden and its atomic backup routines declaratively using the community.docker.docker_compose_v2 module:
# playbook-vaultwarden.yml
---
- name: Deploy Vaultwarden Stack
hosts: vps
become: true
tasks:
- name: Ensure Vaultwarden directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ new_user }}"
group: "{{ new_user }}"
mode: "0750"
loop:
- /opt/komodo/stacks/vaultwarden
- /opt/komodo/stacks/vaultwarden/data
- /opt/backups/vaultwarden
- name: Deploy Vaultwarden compose file
ansible.builtin.copy:
src: compose.yaml
dest: /opt/komodo/stacks/vaultwarden/compose.yaml
owner: "{{ new_user }}"
group: "{{ new_user }}"
mode: "0640"
- name: Start Vaultwarden container stack
community.docker.docker_compose_v2:
project_src: /opt/komodo/stacks/vaultwarden
state: present
Related Production Runbooks
- Modern Reverse Proxy Architecture: Caddy Server & Cloudflare Tunnels for Container Ingress
- Production Docker Engine Setup & daemon.json Hardening on a Linux VPS
- The 3-2-1 Backup Architecture: Automated Encrypted Restic Backups to B2 & S3
- Deploying a Secure WireGuard & Tailscale Exit Node on a VPS for Private Mobile Egress