How to Self-Host Vaultwarden as a Bitwarden Alternative on Docker

How to Self-Host Vaultwarden as a Bitwarden Alternative on Docker

We earn commissions when you shop through the links on this page, at no additional cost to you. Learn more.

Your passwords are too important to hand off to a company that could get breached, acquired, or simply decide to raise prices arbitrarily. I moved my entire household off LastPass years ago and never looked back — Vaultwarden running on a small VPS has been rock solid, costs me almost nothing in compute, and gives me full control over my credential vault. In this tutorial I'll walk you through a complete, production-ready Vaultwarden setup using Docker Compose and Caddy as the reverse proxy, including automatic HTTPS, admin token configuration, and a few security gotchas I've learned the hard way.

What Is Vaultwarden and Why Not Just Use Bitwarden?

Vaultwarden (formerly bitwarden_rs) is an unofficial, open-source Bitwarden server implementation written in Rust. It is compatible with all official Bitwarden clients — desktop, mobile, browser extensions — so your users don't notice any difference on their end. The key distinction from the official Bitwarden server is resource usage: the official server requires a full .NET stack and a dedicated SQL Server instance, which means you're looking at 2+ GB of RAM just to idle. Vaultwarden idles comfortably under 20 MB of RAM. For a homelab or a cheap VPS, that difference is enormous.

I prefer running this on a small DigitalOcean Droplet. Their $6/month Basic Droplet with 1 GB RAM is more than enough, and the reliability is excellent. Create your DigitalOcean account today and you'll have a server ready in under two minutes.

Prerequisites

If you don't already have Docker installed, run:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

Project Directory Structure

I keep all my self-hosted services under /opt/selfhosted/ with one subdirectory per service. Let's create the Vaultwarden workspace:

sudo mkdir -p /opt/selfhosted/vaultwarden
cd /opt/selfhosted/vaultwarden
mkdir -p data caddy/data caddy/config

The Docker Compose File

Here's the complete docker-compose.yml I use. It runs Vaultwarden alongside a Caddy container that handles TLS automatically via Let's Encrypt. Caddy is my preferred reverse proxy for setups like this because it requires zero certificate management on my part — you point it at a domain and it just works.

cat > /opt/selfhosted/vaultwarden/docker-compose.yml << 'EOF'
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - ./data:/data
    environment:
      DOMAIN: "https://vault.yourdomain.com"
      SIGNUPS_ALLOWED: "false"
      ADMIN_TOKEN: "CHANGE_ME_USE_A_STRONG_RANDOM_TOKEN"
      WEBSOCKET_ENABLED: "true"
      LOG_LEVEL: "warn"
      SMTP_HOST: ""
      SMTP_FROM: ""
    networks:
      - vaultwarden_net

  caddy:
    image: caddy:2-alpine
    container_name: vaultwarden_caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/data:/data
      - ./caddy/config:/config
    networks:
      - vaultwarden_net
    depends_on:
      - vaultwarden

networks:
  vaultwarden_net:
    driver: bridge
EOF
Watch out: Never leave SIGNUPS_ALLOWED: "true" on a public-facing instance. After creating your account, set it to false and restart the container. Anyone who finds your Vaultwarden URL could otherwise register and store data on your server.

Generating a Secure Admin Token

The ADMIN_TOKEN protects the /admin panel. Do not use a plain text string — Vaultwarden supports Argon2 hashed tokens, which I strongly recommend. Generate one with the following command (requires the vaultwarden container to have been pulled at least once, or use any Argon2 tool):

# Pull the image first
docker pull vaultwarden/server:latest

# Generate an Argon2id hash for your chosen admin password
docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp
# You'll be prompted to enter your password
# Copy the resulting $argon2id$... string into ADMIN_TOKEN in your compose file

Paste the full hash string (starting with $argon2id$) as the value for ADMIN_TOKEN. When you log into the admin panel, you'll use your plain text password — Vaultwarden hashes it on the fly and compares.

The Caddyfile

Create a minimal Caddyfile in the project directory. Caddy will automatically obtain and renew a Let's Encrypt certificate for your domain:

cat > /opt/selfhosted/vaultwarden/Caddyfile << 'EOF'
vault.yourdomain.com {
    encode gzip

    # Serve the Vaultwarden web vault and API
    reverse_proxy vaultwarden:80

    # WebSocket support for live sync
    @websockets {
        header Connection *Upgrade*
        header Upgrade websocket
    }
    reverse_proxy @websockets vaultwarden:3012

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy no-referrer
    }
}
EOF
Tip: The WebSocket block on port 3012 enables real-time vault sync across all your devices. Without it, clients won't receive push notifications when your vault is updated from another device — you'll have to manually refresh. Don't skip it.

Starting the Stack

With both files in place, start everything:

cd /opt/selfhosted/vaultwarden
docker compose up -d

# Watch the logs to confirm Caddy obtains a certificate
docker compose logs -f caddy

You should see Caddy successfully issuing a certificate from Let's Encrypt within 30 seconds. Once it does, navigate to https://vault.yourdomain.com in your browser. You'll see the Bitwarden-style login page.

First Login and Account Setup

Because SIGNUPS_ALLOWED is set to false, you need to create your account before the container restarts with registrations locked. Here's my recommended sequence:

  1. Temporarily set SIGNUPS_ALLOWED: "true" in docker-compose.yml
  2. Run docker compose up -d to apply
  3. Visit https://vault.yourdomain.com/#/register and create your account with a very strong master password
  4. Set SIGNUPS_ALLOWED: "false" again and run docker compose up -d
  5. Confirm the admin panel at https://vault.yourdomain.com/admin is accessible with your admin token

Now install the official Bitwarden browser extension or mobile app, point it at your custom server URL (https://vault.yourdomain.com) in the server settings, and log in. Everything works exactly as it does with the cloud-hosted Bitwarden.

Enabling Email (Optional but Recommended)

Email is required for features like two-step login verification links, emergency access, and password hint emails. If you have an SMTP provider (I use Mailgun for low-volume transactional email), add these environment variables to the vaultwarden service:

SMTP_HOST: "smtp.mailgun.org"
SMTP_FROM: "[email protected]"
SMTP_FROM_NAME: "Vaultwarden"
SMTP_SECURITY: "starttls"
SMTP_PORT: "587"
SMTP_USERNAME: "[email protected]"
SMTP_PASSWORD: "your-mailgun-smtp-password"

Backups — Don't Skip This

Your entire Vaultwarden database lives in ./data/db.sqlite3. I run a nightly cron job that copies this file to a separate location and then syncs it offsite. At minimum, do this:

# Add to crontab: crontab -e
0 2 * * * docker exec vaultwarden sqlite3 /data/db.sqlite3 ".backup /data/db.sqlite3.bak" && \
  cp /opt/selfhosted/vaultwarden/data/db.sqlite3.bak \
     /opt/backups/vaultwarden/vaultwarden-$(date +\%Y\%m\%d).sqlite3

SQLite's .backup command creates a hot backup of the live database without needing to stop the container. I also rsync the entire ./data/ directory to a DigitalOcean Spaces bucket weekly. Get dependable uptime with DigitalOcean's 99.99% SLA and predictable monthly pricing with Droplets.

Keeping Vaultwarden Updated

Vaultwarden releases updates frequently. I use Watchtower to handle this automatically, but for a password manager specifically, I prefer to update manually so I can review the changelog first:

cd /opt/selfhosted/vaultwarden
docker compose pull
docker compose up -d

Docker Compose will pull the new vaultwarden/server:latest image and restart only the containers that changed. Downtime is typically under five seconds.

Wrapping Up

At this point you have a fully functional, HTTPS-secured Vaultwarden instance that is 100% compatible with every official Bitwarden client. Your passwords live on hardware you control, are never transmitted to a third-party cloud service, and cost you only the price of a small VPS to run. For my household of four people with hundreds of vault items, this setup has been running without incident for over two years.

Your next steps: enable two-factor authentication for your Vaultwarden account (TOTP works great, configured through the web vault's security settings), and set up the emergency access feature so a trusted family member can request access if you're ever unreachable. After that, take a look at our guide on implementing Authelia behind a reverse proxy if you want an extra authentication layer in front of the admin panel.

DigitalOcean

Discussion