How to Deploy Vaultwarden: Lightweight Self-Hosted Bitwarden with Docker Compose and Caddy

Vaultwarden is an open-source, lightweight implementation of the Bitwarden Client API written in Rust. Learn how to deploy Vaultwarden with Docker Compose and Caddy HTTPS, configure Web Vault secrets, and manage users.

How to Deploy Vaultwarden: Lightweight Self-Hosted Bitwarden with Docker Compose and Caddy

Vaultwarden is an open-source, lightweight alternative implementation of the Bitwarden Client API written in Rust. Licensed under the AGPL-3.0 License, Vaultwarden delivers 100% compatibility with official Bitwarden applications—including browser extensions, mobile apps (iOS and Android), desktop clients (macOS, Windows, and Linux), and the Bitwarden CLI—while replacing the resource-intensive multi-container upstream stack with a single, highly efficient binary.

Vaultwarden Unlocked Web Vault

Key Capabilities and Architecture

  1. Drastic Resource Efficiency:

The official Bitwarden self-hosted server consists of over a dozen containerized .NET microservices backed by Microsoft SQL Server, requiring a minimum of 3 to 4 GB of RAM. Vaultwarden condenses the complete Bitwarden server API into a single Rust binary running on the Rocket web framework and Tokio runtime, consuming merely 8 to 25 MB of RAM at idle. This enables smooth operation on low-power homelab hardware, Raspberry Pis, or entry-level cloud VPS instances.

  1. Integrated Web Vault & WebSockets:

Vaultwarden packages a customized build of the upstream Bitwarden Web Vault (web-vault), serving the full client dashboard directly from the root URL. Real-time push notifications are handled over async WebSockets (/notifications/hub) on the main listener, removing the need for a legacy standalone notification daemon or separate port multiplexing.

  1. Built-in SQLite with Write-Ahead Logging:

Out of the box, Vaultwarden persists all ciphers, folders, users, and audit records into an embedded SQLite database (db.sqlite3) configured with WAL mode. External database drivers for PostgreSQL and MariaDB/MySQL are also available at compile-time for enterprise deployments.

  1. End-to-End Cryptographic Zero-Knowledge:

Client items are encrypted and decrypted strictly within the user's browser or native client using AES-256-CBC, PBKDF2/Argon2id, and HMAC-SHA256 before any data is sent over the network. The server never receives or stores your master password or unencrypted vault ciphers.

  1. Bitwarden Send & Organization Features:

Vaultwarden natively supports Bitwarden Send for sharing end-to-end encrypted, expiring text snippets and file attachments with anonymous external recipients, as well as full organization vault sharing, collections, and role-based access control.

---

Prerequisites

Before starting the deployment, verify that your target host environment meets the following requirements:

  • Host Operating System: Linux (Debian, Ubuntu, Arch, Fedora, Rocky), macOS, or Windows via WSL2.
  • Container Runtime: Docker Engine with Docker Compose v2 (docker compose version v2.20+ recommended).
  • Available Host Ports: Host port 8443 (or port 443 for public internet deployments). Port 80 inside the container is kept internal to the Docker network.
  • System Utilities: Standard shell environment with openssl (for generating cryptographically secure admin tokens) and curl (for health checks).
  • Zero Host Toolchains: No Rust toolchain, Node.js runtime, or external database engines are required on the host system. All dependencies are containerized.

---

Exact Install Steps

The validated deployment pairs the official vaultwarden/server:latest image with a lightweight caddy:2 reverse proxy container on a private Docker bridge network.

Critical HTTPS Requirement: Modern Web Vault releases (Web-Vault 2026.7.0+) strictly enforce HTTPS and throw an InsecureUrlNotAllowedError for plain HTTP origins—even on http://localhost. Deploying Caddy alongside Vaultwarden ensures TLS is terminated seamlessly with internal self-signed certificates locally, or automatic Let's Encrypt certificates in production.

Step 1: Create the Project Directory and Persistent Storage

Create the deployment directory, initialize the persistent SQLite data folder, and enter the directory:

mkdir vaultwarden && cd vaultwarden
mkdir vw-data

Step 2: Generate Admin Token and Environment Variables

Generate a 64-character hexadecimal authentication secret for the /admin portal and configure your .env file:

cat > .env <<EOF
ADMIN_TOKEN=$(openssl rand -hex 32)
DOMAIN=https://localhost:8443
SIGNUPS_ALLOWED=true
EOF
chmod 600 .env

Security Note: ADMIN_TOKEN unlocks administrative control over your instance via /admin. Keep .env restricted with permissions 600 and do not commit this file to version control. Set DOMAIN to match the exact protocol, hostname, and port where users access the service; this ensures invitation links and Bitwarden Send URLs are generated with the proper origin.

Step 3: Write the Docker Compose Configuration

Create docker-compose.yml to define the Vaultwarden application service and the Caddy HTTPS reverse proxy:

cat > docker-compose.yml <<'EOF'
services:
  vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./vw-data:/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:80/alive || exit 1"]
      interval: 5s
      timeout: 3s
      retries: 10

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "8443:8443"
    command: caddy reverse-proxy --from https://localhost:8443 --to vaultwarden:80 --internal-certs
    depends_on:
      - vaultwarden
EOF

Production Note: When exposing Vaultwarden to the public internet on a custom domain, replace --from https://localhost:8443 with --from https://vault.example.com:443, publish port "443:443", remove --internal-certs, and set DOMAIN=https://vault.example.com in .env. Caddy will automatically provision and renew valid Let's Encrypt / ZeroSSL certificates.

Step 4: Pull Images and Launch the Stack

Pull the verified images and start the services in detached mode:

docker compose pull
docker compose up -d

---

Verification

Confirm that both containers are running and healthy before accessing the Web Vault.

1. Check Container Health and Process Status

Verify that Vaultwarden passes its internal healthcheck and Caddy is bound to port 8443:

docker compose ps

Expected output:

NAME                     IMAGE                       COMMAND                  SERVICE       CREATED          STATUS                    PORTS
vaultwarden-caddy-1      caddy:2                     "caddy reverse-proxy…"   caddy         10 seconds ago   Up 10 seconds             0.0.0.0:8443->8443/tcp
vaultwarden-server-1     vaultwarden/server:latest   "/vaultwarden"           vaultwarden   10 seconds ago   Up 10 seconds (healthy)   80/tcp

2. Verify /alive Liveness Endpoint

Query the liveness endpoint over HTTPS using curl -sk (the -k flag accepts Caddy's internal local certificate):

curl -sk https://localhost:8443/alive

Expected output:

"2026-10-31T20:03:06.091120Z"

*(A returned ISO-8601 JSON timestamp string confirms the Rocket server is active and healthy.)*

3. Verify Server API Configuration

Confirm the Bitwarden API endpoint reports the correct server name:

curl -sk https://localhost:8443/api/config | grep -o '"name":"Vaultwarden"'

Expected output:

"name":"Vaultwarden"

4. Verify Admin Portal Accessibility

Confirm that /admin returns HTTP status 200:

curl -sko /dev/null -w "%{http_code}\n" https://localhost:8443/admin

Expected output:

200

---

Browser Setup

Follow this step-by-step walkthrough to initialize your primary vault account, store credentials, test encrypted file sharing, and access administrative settings.

Step 1: Open the Web Vault and Create an Account

  1. Open your browser and navigate to https://localhost:8443. If using internal certificates, accept your browser's self-signed security warning.
  2. The Web Vault landing screen displays the Bitwarden login form.

Web Vault Login Screen

  1. Click Create account.
  2. Fill in your account details:

- Email Address: demo@vaultwarden.local - Name: DevOps Admin

Create Account Form

  1. Choose a strong master password (e.g. SuperSecretMasterPassword123!), re-enter it to confirm, and provide an optional hint.

Master Password Setup

  1. Click Create account. Vaultwarden completes client-side cryptographic key derivation and establishes your vault.

Step 2: Access Your Fresh Vault

Log in with your newly created master credentials. You will be greeted by the clean, empty vault dashboard.

Fresh Empty Vault

Step 3: Add an Encrypted Login Credential

  1. Click New item (or + Item).
  2. Configure a new Login credential:

- Item type: Login - Name: Production Kubernetes Cluster - Username: cluster-admin - Password: Enter or generate an encrypted password - URI 1: https://k8s.internal.croogserverdude.ca - Notes: Add operational cluster documentation or bootstrap notes

New Login Item Dialog

  1. Click Save. An Item added confirmation toast appears.

Item Added Confirmation

  1. The item is displayed in your encrypted vault inventory.

Vault List with Item

  1. Click the entry to open the item detail view, where passwords can be revealed or copied to clipboard with one click.

Item Detail View

Step 4: Share Encrypted Data with Bitwarden Send

Bitwarden Send allows you to share end-to-end encrypted notes or secrets with colleagues or external recipients without requiring them to have an account.

  1. Navigate to the Send tab in the top navigation bar.

Empty Send View

  1. Click New Send.
  2. Fill out the Send creation modal:

- Send type: Text - Name: Database Backup Key - Text: Enter sensitive key material or credentials - Expiration: Select an expiration window (e.g. 7 days)

New Send Modal

  1. Click Save. The Send is created and a unique, cryptographically keyed URL is generated.

Send Created

  1. Click Copy link from the modal or inspect your active Send list.

Send Management List

Step 5: Anonymous Recipient Verification

Open an incognito or private browsing window and navigate to the copied Send URL. The recipient can immediately inspect and copy the decrypted secret without logging in.

Anonymous Recipient View

Step 6: Access the Administration Portal

Vaultwarden features an administrative console at /admin for server governance.

  1. Open https://localhost:8443/admin in your browser.
  2. Enter the ADMIN_TOKEN value generated during Step 2 of the installation.
  3. The Settings tab allows runtime configuration of SMTP email relays, domain rules, and security policies.

Admin Settings Panel

  1. Click Users to view all registered users, verify user statuses, invite members, or deauthorize devices.

Admin Users Management

  1. Click Diagnostics to review compiled Rust features, database backend metrics, and WebSocket support.

Admin Diagnostics Panel

Step 7: Auto-Lock and Vault Unlocking

When you switch tabs or leave the Web Vault session, the vault automatically locks to prevent unauthorized local access.

Vault Locked Screen

Enter your master password to unlock the session and resume managing your credentials.

---

Post-Install Hardening

Once you have registered your primary administrator account, disable open user registrations to prevent unauthorized public signups on your server.

Edit .env and set:

SIGNUPS_ALLOWED=false

Apply the updated configuration by reloading the container:

docker compose up -d

Additional accounts can subsequently be invited directly through the /admin console.

---

Troubleshooting

Symptom / IssueProbable CauseRecommended Resolution
Web Vault refuses login or signup (InsecureUrlNotAllowedError)Web-Vault 2026.7.0 client requires HTTPS even on localhostDeploy a reverse proxy terminating TLS (such as Caddy with --internal-certs) or configure ROCKET_TLS directly in Vaultwarden.
Admin login blocked with HTTP 429 (Too Many Requests)Vaultwarden enforces rate limits on /admin (burst of 3 attempts, then 1 per 300 seconds)Wait 5 minutes for the rate limit window to expire before attempting another admin login.
Browser extensions or mobile apps fail to connectUntrusted internal certificate or DOMAIN environment variable mismatchEnsure DOMAIN in .env matches the exact external URL. For mobile clients, use a valid public CA certificate (Let's Encrypt) or install Caddy's root CA certificate into the client OS trust store.
Registration fails with "User already exists"Stale user records in persistent SQLite databaseLog in to https://localhost:8443/admin, navigate to Users, and delete the stale test account, or wipe vw-data/ on fresh deployments.
Real-time synchronization not pushing to clientsReverse proxy dropping WebSocket connection upgrades on /notifications/hubEnsure your proxy forwards the Upgrade and Connection headers. Caddy handles WebSockets automatically; Nginx requires explicit proxy_set_header Upgrade $http_upgrade.
Permission denied when removing vw-dataContainer user wrote SQLite database files as rootRemove root-owned files with sudo rm -rf vw-data or via Docker: docker run --rm -v $(pwd):/v alpine rm -rf /v/vw-data.

---

Teardown & Maintenance

To safely stop the Vaultwarden stack while preserving all encrypted passwords and configuration:

docker compose down

To update Vaultwarden and Caddy to the newest upstream container releases:

docker compose pull
docker compose up -d

To back up your vault database, copy the SQLite database while WAL journaling is consistent:

sqlite3 vw-data/db.sqlite3 ".backup 'vw-data/backup-$(date +%F).sqlite3'"

To completely delete the deployment, including containers, networks, and persistent database volumes:

docker compose down -v
rm -rf vw-data

---

Assumptions & decisions

  • HTTPS Requirement & Architecture: As discovered during end-to-end verification, Web-Vault 2026.7.0 client explicitly checks if(!e.url.startsWith("https://")) throw new InsecureUrlNotAllowedError. Therefore, deploying over plain HTTP on http://localhost:3000 is insufficient for real-world users. We standardized the primary install instructions on a dual-container Compose stack combining vaultwarden/server:latest with caddy:2 using --internal-certs on port 8443.
  • Port Strategy: Published HTTPS on port 8443 to avoid port collisions with host port 443 during homelab testing, while clearly documenting how to bind port 443 with automatic Let's Encrypt certificates for production domain setups.
  • Admin Security: Generated the ADMIN_TOKEN using openssl rand -hex 32 into .env with file permissions 600, preventing exposure in version control and shell logs.
  • Single-Container Database Model: Standardized on Vaultwarden's default embedded SQLite engine (/data/db.sqlite3) with WAL mode. For 99% of homelab and small organization workloads, SQLite outperforms client-server databases in simplicity and memory footprint (~8.4 MiB idle).
  • Screenshot References: Embedded verified PNG screenshots captured by the Playwright test suite (runs/vaultwarden-20261031-155614/browser/*.png) to illustrate the complete user journey from sign-up to Bitwarden Send and admin diagnostics.
  • Post-Install Signup Policy: Documented setting SIGNUPS_ALLOWED=false immediately following initial account creation to ensure public-facing servers are not vulnerable to open user registration.