Skip to Content

How to Deploy Vaultwarden with Docker Compose and Traefik (Production Guide)

Production-focused setup with TLS, secret hygiene, backups, and verifiable operations checks.

Self-hosted password management is one of the most practical reliability and security upgrades for growing teams. In this guide, you will deploy Vaultwarden using Docker Compose + Traefik with a production-first approach: TLS automation, segmented networking, disciplined secrets handling, backup and restore drills, and operational checks that reduce surprises during upgrades. The objective is not just to make the service run, but to make it dependable under real production conditions.

Architecture and flow overview

The stack contains an edge proxy (Traefik) and the password service (Vaultwarden). Traefik terminates HTTPS, redirects port 80 to 443, and routes traffic via host rules. Vaultwarden remains private on an internal network and is exposed only through Traefik. Persistent storage is mounted for service data and certificate state. This model is simple to operate and easy to audit.

  • Ingress: Public internet to Traefik on 443.
  • Routing: Host-based rule sends requests to Vaultwarden.
  • Storage: Mounted volume for encrypted vault data.
  • Certificates: ACME-managed certificates with automatic renewal.
  • Operations: Logging, health checks, and backup policy.

Prerequisites

  • Ubuntu 22.04 or 24.04 server (2 vCPU / 4 GB RAM minimum)
  • Domain and DNS record for vault.yourdomain.com
  • Firewall rules allowing only 22, 80, and 443
  • SMTP account for invitations and recovery email
  • Secure location for secret storage and backup keys
sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo /usr/bin/tee /etc/apt/sources.list.d/docker.list >/dev/null

sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

If the copy button is unavailable on your device, manually select the command block and copy it.

Step-by-step deployment

Step 1: Prepare project structure and environment file

Create a dedicated directory, lock down secret file permissions, and separate runtime data from configuration. This makes backups and audits easier.

sudo mkdir -p /opt/vaultwarden/{data,traefik,logs,backups}
sudo chown -R $USER:$USER /opt/vaultwarden
cd /opt/vaultwarden

cat > .env << "EOF"
DOMAIN=vault.yourdomain.com
[email protected]
VW_ADMIN_TOKEN=REPLACE_WITH_LONG_RANDOM_VALUE
VW_SIGNUPS_ALLOWED=false
VW_WEBSOCKET_ENABLED=true
VW_SMTP_HOST=smtp.yourprovider.com
[email protected]
VW_SMTP_PORT=587
VW_SMTP_SECURITY=starttls
[email protected]
VW_SMTP_PASSWORD=REPLACE_WITH_SMTP_SECRET
EOF

chmod 600 .env

If the copy button is unavailable on your device, manually select the command block and copy it.

Step 2: Define Traefik + Vaultwarden compose stack

Use labels for dynamic routing and keep services on a dedicated network. Avoid exposing Vaultwarden directly to host ports.

version: "3.9"
services:
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.tlschallenge=true"
      - "--certificatesresolvers.le.acme.email=${ACME_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
    networks: [edge]
    restart: unless-stopped

  vaultwarden:
    image: vaultwarden/server:latest
    env_file: .env
    environment:
      - DOMAIN=https://${DOMAIN}
      - ADMIN_TOKEN=${VW_ADMIN_TOKEN}
      - SIGNUPS_ALLOWED=${VW_SIGNUPS_ALLOWED}
      - WEBSOCKET_ENABLED=${VW_WEBSOCKET_ENABLED}
      - SMTP_HOST=${VW_SMTP_HOST}
      - SMTP_FROM=${VW_SMTP_FROM}
      - SMTP_PORT=${VW_SMTP_PORT}
      - SMTP_SECURITY=${VW_SMTP_SECURITY}
      - SMTP_USERNAME=${VW_SMTP_USERNAME}
      - SMTP_PASSWORD=${VW_SMTP_PASSWORD}
    volumes:
      - ./data:/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vw.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.vw.entrypoints=websecure"
      - "traefik.http.routers.vw.tls.certresolver=le"
      - "traefik.http.services.vw.loadbalancer.server.port=80"
      - "traefik.http.routers.vw-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.vw-http.entrypoints=web"
      - "traefik.http.routers.vw-http.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    networks: [edge]
    restart: unless-stopped

networks:
  edge:
    name: vaultwarden_edge

If the copy button is unavailable on your device, manually select the command block and copy it.

Step 3: Start services and inspect health

Launch containers, review initial logs, and ensure ACME obtains certificates successfully. Resolve DNS issues before repeated restarts.

cd /opt/vaultwarden
touch traefik/acme.json
chmod 600 traefik/acme.json

docker compose pull
docker compose up -d

docker compose ps
docker compose logs traefik --tail=100
docker compose logs vaultwarden --tail=100

If the copy button is unavailable on your device, manually select the command block and copy it.

Configuration and secrets handling

Set and rotate sensitive values with discipline. Keep administrative access restricted and avoid exposing sensitive values in automation logs.

  • Use a long random admin token; rotate quarterly or after incidents.
  • Disable open signups and onboard users by invitation.
  • Store secrets in a secure vault; do not commit .env.
  • Harden host SSH: key-only auth, limited users, and intrusion controls.
  • Review SMTP configuration for SPF/DKIM/DMARC alignment.
# generate token
openssl rand -base64 48

# verify no world-readable secrets file
stat -c "%a %n" /opt/vaultwarden/.env

# recreate only app service after secret rotation
cd /opt/vaultwarden
docker compose up -d --force-recreate vaultwarden

If the copy button is unavailable on your device, manually select the command block and copy it.

Backup and recovery strategy

Backups must be testable. Keep daily snapshots, weekly immutable copies, checksums, and documented restore workflows. Run restore drills to a disposable host at least monthly.

#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F-%H%M)
BASE=/opt/vaultwarden
mkdir -p "$BASE/backups"

tar -C "$BASE/data" -czf "$BASE/backups/vaultwarden-$TS.tar.gz" .
sha256sum "$BASE/backups/vaultwarden-$TS.tar.gz" > "$BASE/backups/vaultwarden-$TS.tar.gz.sha256"

# optional: encrypt before upload
# age -r <PUBLIC_KEY> -o "$BASE/backups/vaultwarden-$TS.tar.gz.age" "$BASE/backups/vaultwarden-$TS.tar.gz"

find "$BASE/backups" -type f -mtime +21 -delete

If the copy button is unavailable on your device, manually select the command block and copy it.

During restore drills, deploy in isolation, restore data, verify login + item decryption, and confirm attachment access. Do not restore directly onto the production volume until validation is complete.

Verification

  • TLS certificate valid and not close to expiration.
  • Primary login, organization sharing, and attachment sync all work.
  • Email invitation/recovery flow succeeds.
  • Container restart retains data and settings.
  • No critical errors in 24h logs.
curl -I https://vault.yourdomain.com
openssl s_client -connect vault.yourdomain.com:443 -servername vault.yourdomain.com </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer

docker compose exec vaultwarden /vaultwarden --version

docker compose logs --since=24h vaultwarden | egrep -i "error|panic|fatal" || echo "No critical errors"

If the copy button is unavailable on your device, manually select the command block and copy it.

Common issues and fixes

ACME certificate request fails

Check DNS propagation and ensure inbound 80/443 are reachable from the internet. Also verify no conflicting reverse proxy is binding those ports.

Vaultwarden is reachable but WebSocket sync is unstable

Confirm reverse proxy route labels and headers; test persistent connection behavior under your CDN settings if one is in front of the origin.

SMTP appears configured but emails are not delivered

Validate relay policy, sender authorization, and TLS mode. Many providers require exact sender domain alignment.

After upgrade, service starts but users report errors

Rollback to previous image tag, inspect logs, then replay upgrade in staging. Keep snapshots from immediately before upgrades.

Unexpected storage growth

Review attachment retention and backup artifact retention. Confirm log rotation and prune unused images regularly.

Admin panel unavailable after token change

Ensure the container was recreated and old token values are not cached in an external secret source.

FAQ

Is Vaultwarden suitable for production use?

Yes for many teams, provided you enforce hardening, backup validation, and upgrade discipline. The operational model matters more than the initial install.

Do I need Traefik, or can I publish Vaultwarden directly?

Use a reverse proxy. It centralizes TLS, routing, and policy controls and makes operations safer at scale.

Should I keep SQLite or migrate to PostgreSQL?

SQLite is often fine for small teams. For larger multi-team usage or stricter reliability expectations, PostgreSQL can be preferable.

How often should I test restore procedures?

At least monthly, and after major architecture changes. Recovery confidence decays quickly if tests are skipped.

Can this run behind Cloudflare?

Yes, but verify TLS mode, WebSocket behavior, and origin restrictions. Avoid insecure flexible SSL modes.

What are the minimum monitoring checks I should set up?

Certificate expiry, container restarts, error logs, SMTP failures, and backup job outcomes are the highest-value first alerts.

Related guides

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Operational note: run periodic game-day exercises for this stack. Simulate worker outages, SMTP failures, and webhook endpoint latency spikes to validate alerting thresholds and escalation paths. Teams that rehearse these scenarios recover faster and reduce customer-facing impact during real incidents.

Capacity note: booking traffic is often bursty around local business hours and campaign launches. Profile container CPU/memory during those windows and leave headroom for background jobs. Scaling only after saturation usually means delayed reminders and support ticket spikes.

Security note: if your organization must meet strict compliance requirements, add audit trail retention policies, hardened network boundaries (host firewall and private DB network), and quarterly credential rotation checks to your standard operating procedure.

Production Guide: Deploy Cal.com with Docker Compose + Nginx + PostgreSQL + Redis on Ubuntu
A production-first scheduling stack with TLS, worker isolation, backups, and operational guardrails for growing teams.