Your passwords are the keys to everything — email, banking, your homelab, your entire digital life. Trusting a cloud service to store them is fine for most people, but if you’re already self-hosting your files, media, and apps, why hand the most critical piece of your security to someone else?

Self-hosted password managers give you full control over your credential vault. No subscription fees, no data leaving your network, no wondering what a third-party company does with your master password hash. In 2026, the options are better than ever — and three names stand out: Vaultwarden, Bitwarden, and Passbolt.

This guide breaks down all three, gives you production-ready Docker Compose files for each, and tells you exactly which one to pick based on your situation.

💡 This article contains affiliate links. If you buy through them, we earn a small commission at no extra cost to you. Learn more.

Why Self-Host a Password Manager?

Before we compare options, let’s be honest about the trade-offs:

Why do it:

  • Full data sovereignty — your passwords live on your hardware
  • No monthly fees — 1Password, LastPass, Bitwarden Premium all charge; self-hosted is free
  • Air-gapped option — run it entirely on your local network with zero internet exposure
  • Audit trail — some options give you granular logging of who accessed what
  • Team/family use — share vaults with your household without paying per-seat fees

The reality check:

  • You’re responsible for backups — if the server dies and you have no backup, those passwords are gone
  • You need to keep the software updated (security patches matter here more than almost anywhere)
  • Setup takes 30–60 minutes, not 5

Still worth it? Absolutely. Let’s look at the three best options.


Option 1: Vaultwarden (The Lightweight Champion)

Vaultwarden is a community-maintained, Rust-based reimplementation of the Bitwarden server. It’s tiny (under 50MB), blazingly fast, and compatible with all official Bitwarden clients. This is the single most popular self-hosted password manager in the homelab community — and for good reason.

What Makes Vaultwarden Special

  • Tiny resource footprint — runs happily on a Raspberry Pi or a single shared container
  • Full Bitwarden client compatibility — use the official Chrome, Firefox, iOS, and Android apps
  • Supports almost all Bitwarden Premium features for free — including file attachments, custom fields, and Bitwarden Send
  • Single binary, zero dependencies — no database setup required (uses SQLite by default, with PostgreSQL/MySQL support)
  • Active development — security patches land fast

Vaultwarden Docker Compose Setup

Here’s a production-ready configuration. This assumes you have a reverse proxy (Traefik, Caddy, or nginx Proxy Manager) handling SSL termination:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# /opt/docker/vaultwarden/docker-compose.yml
version: "3.8"

services:
  vaultwarden:
    image: dani-garcia/vaultwarden:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      # Change this to your actual domain
      DOMAIN: "https://vault.yourdomain.com"

      # Disable registration after you've created your account(s)
      SIGNUPS_ALLOWED: "false"

      # Allow specific emails to register (if signups are off)
      # SIGNUPS_FILTER_WHITELIST: "^youremail@yourdomain\\.com$"

      # Enable admin panel (change this to a strong random token)
      ADMIN_TOKEN: "your-random-admin-token-here"

      # Email relay (optional but recommended for 2FA codes)
      SMTP_HOST: "smtp.yourisp.com"
      SMTP_PORT: "587"
      SMTP_FROM: "vaultwarden@yourdomain.com"
      SMTP_USERNAME: "your-smtp-user"
      SMTP_PASSWORD: "your-smtp-password"
      SMTP_SECURITY: "starttls"

      # Logging
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"

      # Security hardening
      SESSION_TIMEOUT: "86400"  # 24 hours
      DISABLE_ICON_SERVICE: "true"  # Prevents leaking domain lookups

    volumes:
      - ./data:/data
    ports:
      - "8080:80"  # Only if you're NOT using a reverse proxy
    # If using Traefik, add labels instead of ports:
    # labels:
    #   - "traefik.enable=true"
    #   - "traefik.http.routers.vaultwarden.rule=Host(`vault.yourdomain.com`)"
    #   - "traefik.http.routers.vaultwarden.entrypoints=websecure"
    #   - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
    #   - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/api/config"]
      interval: 30s
      timeout: 10s
      retries: 3
1
2
3
4
5
6
7
8
# Deploy it
mkdir -p /opt/docker/vaultwarden
cd /opt/docker/vaultwarden
# Create the docker-compose.yml above, then:
docker compose up -d

# Verify it's running
docker compose logs -f vaultwarden

SQLite works fine for a single-user or small-family vault. But if you want redundancy, concurrent access, or automated backups via pg_dump, switch to PostgreSQL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Add this service to your docker-compose.yml
  db:
    image: postgres:16-alpine
    container_name: vaultwarden-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "change-me-use-env-file"
      POSTGRES_DB: vaultwarden
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vaultwarden"]
      interval: 10s
      timeout: 5s
      retries: 5

# Then update the vaultwarden service:
  vaultwarden:
    # ... (same as before, add this env var)
    environment:
      DATABASE_URL: "postgresql://vaultwarden:change-me-use-env-file@db:5432/vaultwarden"
    depends_on:
      db:
        condition: service_healthy

Automated Backup Script

Password vaults are critical. Back. Them. Up.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
# /opt/docker/vaultwarden/backup.sh
# Run via cron: 0 3 * * * /opt/docker/vaultwarden/backup.sh

BACKUP_DIR="/opt/docker/vaultwarden/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

mkdir -p "$BACKUP_DIR"

# Backup PostgreSQL database
docker exec vaultwarden-db pg_dump -U vaultwarden -d vaultwarden \
  | gzip > "$BACKUP_DIR/vaultwarden_db_${DATE}.sql.gz"

# Backup attachments and data files
tar -czf "$BACKUP_DIR/vaultwarden_data_${DATE}.tar.gz" \
  -C /opt/docker/vaultwarden/data .

# Clean up old backups
find "$BACKUP_DIR" -type f -mtime +${RETENTION_DAYS} -delete

echo "[$(date)] Backup complete: vaultwarden_db_${DATE}.sql.gz"

Option 2: Bitwarden (The Official Self-Hosted Server)

Bitwarden is the original — open source, audit-tested, and backed by a real company. The official self-hosted server is heavier than Vaultwarden (it’s .NET-based and needs SQL Server or PostgreSQL), but it’s the “blessed” path if you want guaranteed compatibility and enterprise features.

When to Choose Bitwarden Over Vaultwarden

  • You need enterprise SSO (SAML/OIDC integration)
  • You’re deploying for a company or organization with compliance requirements
  • You want the official support path (Bitwarden offers paid self-hosted plans)
  • You need directory sync (Active Directory, LDAP, or Azure AD)

Bitwarden Docker Compose Setup

Bitwarden ships an official docker-compose.yml template, but here’s a clean, production version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# /opt/docker/bitwarden/docker-compose.yml
version: "3.8"

services:
  db:
    image: postgres:16-alpine
    container_name: bitwarden-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: bitwarden
      POSTGRES_PASSWORD: "${DB_PASSWORD}"
      POSTGRES_DB: bitwarden
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  nginx:
    image: nginx:alpine
    container_name: bitwarden-nginx
    restart: unless-stopped
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api
      - identity
      - web

  api:
    image: bitwarden/api:latest
    container_name: bitwarden-api
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:80
      - GlobalSettings__DatabaseProvider=postgresql
      - GlobalSettings__Postgre__ConnectionString="Host=db;Database=bitwarden;Username=bitwarden;Password=${DB_PASSWORD}"
      - GlobalSettings__Jwt__Key="${JWT_KEY}"
      - GlobalSettings__Jwt__ExpiresMinute=120
      - GlobalSettings__DisableGoogleKeysImport=true
      - GlobalSettings__SmtpHost="${SMTP_HOST}"
      - GlobalSettings__SmtpPort="${SMTP_PORT}"
      - GlobalSettings__SmtpUsername="${SMTP_USERNAME}"
      - GlobalSettings__SmtpPassword="${SMTP_PASSWORD}"
    depends_on:
      - db

  identity:
    image: bitwarden/identity:latest
    container_name: bitwarden-identity
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - GlobalSettings__DatabaseProvider=postgresql
      - GlobalSettings__Postgre__ConnectionString="Host=db;Database=bitwarden;Username=bitwarden;Password=${DB_PASSWORD}"
      - GlobalSettings__Jwt__Key="${JWT_KEY}"
      - ASPNETCORE_URLS=http://+:80
    depends_on:
      - db

  web:
    image: bitwarden/web:latest
    container_name: bitwarden-web
    restart: unless-stopped

Create a matching .env file:

1
2
3
4
5
6
7
# /opt/docker/bitwarden/.env
DB_PASSWORD=use-a-strong-random-password
JWT_KEY=use-a-64-character-random-string
SMTP_HOST=smtp.yourisp.com
SMTP_PORT=587
SMTP_USERNAME=your-smtp-user
SMTP_PASSWORD=your-smtp-password

Note: Bitwarden’s official setup guide recommends using their bitwarden.sh script installer for the most straightforward path. The Compose file above is for teams who want full control. Check Bitwarden’s self-hosted docs for the latest image tags.


Option 3: Passbolt (The Team-First Option)

Passbolt is built from the ground up for team collaboration. While Vaultwarden and Bitwarden focus on individual vault management, Passbolt’s core product is sharing secrets across a team — using public-key cryptography. Every user has a GPG key, and secrets are encrypted individually for each person who has access.

Why Passbolt Stands Out

  • Zero-knowledge architecture — the server never sees your decrypted passwords. Ever.
  • Team-native — sharing, access control, and permissions are first-class features
  • Open source (Community Edition is fully free)
  • Compliance-friendly — built with SOC 2 and enterprise security teams in mind
  • Browser extension is excellent — clean UI, fast autofill

Passbolt Docker Compose Setup

Passbolt is more opinionated about its stack (it needs MySQL/MariaDB and has specific GPG requirements), but their official Docker image handles most of the complexity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# /opt/docker/passbolt/docker-compose.yml
version: "3.8"

services:
  db:
    image: mariadb:10.11
    container_name: passbolt-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
      MYSQL_DATABASE: passbolt
      MYSQL_USER: passbolt
      MYSQL_PASSWORD: "${DB_PASSWORD}"
    volumes:
      - ./dbdata:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-u", "passbolt", "-p${DB_PASSWORD}"]
      interval: 15s
      timeout: 5s
      retries: 5

  passbolt:
    image: passboltsa/passbolt_ce-debian:latest
    container_name: passbolt
    restart: unless-stopped
    environment:
      # Database connection
      DATASOURCES_DEFAULT_HOST: db
      DATASOURCES_DEFAULT_PORT: 3306
      DATASOURCES_DEFAULT_DATABASE: passbolt
      DATASOURCES_DEFAULT_USERNAME: passbolt
      DATASOURCES_DEFAULT_PASSWORD: "${DB_PASSWORD}"

      # App settings
      APP_FULL_BASE_URL: "https://passbolt.yourdomain.com"
      APP_SITE_DOMAIN: "passbolt.yourdomain.com"
      PASSBOLT_REGISTRATION_PUBLIC_ALLOWED: "false"

      # Email (required for user invitations)
      EMAIL_TRANSPORT_SENDMAIL_CMD: "/usr/sbin/sendmail -bs"
      # Or use SMTP:
      # MAILER_TRANSPORT: smtp
      # EMAIL_TRANSPORT_SMTP_HOST: "smtp.yourisp.com"
      # EMAIL_TRANSPORT_SMTP_PORT: "587"

      # Disable SSL check if behind internal reverse proxy
      PASSBOLT_SSL_VERIFY_PEER: "false"

    volumes:
      - ./gpg:/var/lib/passbolt/.gnupg
      - ./uploads:/var/www/passbolt/webroot/img/public
    ports:
      - "8443:443"
      - "8880:80"
    depends_on:
      db:
        condition: service_healthy

    entrypoint: |
      bash -c '
        # Wait for DB to be ready
        until mysqladmin ping -h db -u passbolt -p${DB_PASSWORD} 2>/dev/null; do
          echo "Waiting for database..."
          sleep 2
        done
        # Run the default entrypoint
        exec /entrypoint.sh "$@"
      ' _

After first run, you’ll need to set up the admin user:

1
2
3
4
5
6
7
# Exec into the container to create the first admin user
docker exec -it passbolt \
  su -s /bin/bash www-data -c \
  "/var/www/passbolt/src/Utility/passbolt register \
    --name=\"Admin\" \
    --email=\"admin@yourdomain.com\" \
    --role=admin"

Head-to-Head Comparison

FeatureVaultwardenBitwarden (Official)Passbolt CE
Resource usage~30 MB RAM~500 MB+ RAM~200 MB RAM
DatabaseSQLite / PostgreSQL / MySQLPostgreSQL / SQL ServerMariaDB / MySQL
Client appsAll Bitwarden clientsAll Bitwarden clientsOwn browser extension + mobile
Team sharingBasic shared collectionsFull org/team managementPublic-key encrypted sharing
Free tierFully freeFree (Community), paid for EnterpriseCommunity Edition is free
SSO supportNoYes (Enterprise)Yes (Business, paid)
Setup complexity⭐ Easy⭐⭐⭐ Moderate⭐⭐ Moderate
Active development✅ Yes✅ Yes✅ Yes
Audit historyBasicFullFull
Browser extension qualityBitwarden’s (excellent)Bitwarden’s (excellent)Good, improving

Which One Should You Pick?

Here’s the decision tree:

→ Running a personal or family vault? Pick Vaultwarden. It’s tiny, fast, uses the best client ecosystem (Bitwarden’s apps are genuinely great), and handles 90% of use cases with zero friction. There’s a reason it’s the #1 recommendation in every homelab community.

→ Deploying for a company with compliance needs? Pick Bitwarden. The official server is the only one that qualifies for Bitwarden’s enterprise support and has the audit trail and SSO integrations that IT departments require.

→ Running a team that needs shared secrets with strict access control? Pick Passbolt. Its public-key model means even if someone compromises your server, they can’t decrypt other people’s passwords. That’s a meaningfully different security model.


Final Tips for Any Choice

1. Always enable 2FA. Every single one of these supports TOTP (Google Authenticator, Authy, etc.). Turn it on. No exceptions.

2. Back up regularly. A password vault with no backup is worse than no password manager at all. Set up automated database backups and test restoring from them quarterly.

3. Put it behind a VPN if possible. If you’re running Tailscale or WireGuard on your homelab, keep your password manager on the internal network. It does not need to be exposed to the public internet.

4. Keep it updated. Password manager software is a high-value target. Subscribe to the project’s security advisories and update within 48 hours of any patch.

5. Test your setup end-to-end. After deploying, install the browser extension, create a test account on a throwaway site, save it, delete your browser history, and confirm you can log back in using the vault. Then do the same on mobile. If any step fails, fix it before you migrate your real passwords.


Self-hosting a password manager is one of the highest-impact moves you can make in your homelab. You’re not just saving money — you’re removing one of the biggest blind spots in your digital security. Pick the right tool for your needs, set it up properly, and sleep a little better knowing your passwords are on your hardware.