Docker Compose Configuration for Password Managers

6 October 2025 ยท Updated 6 October 2025

dockerdocker-composepassword-managerself-hostedcontainers

Ready-to-adapt Docker Compose files for running Vaultwarden and Bitwarden in containers, with networks, volumes, reverse proxy integration, and backup containers alongside.

Vaultwarden Basic Configuration

Minimal Setup

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - ROCKET_PORT=8080
    volumes:
      - ./vw-data:/data
    ports:
      - "8080:8080"

Production Configuration

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      # Domain configuration
      - DOMAIN=https://vault.example.com

      # Security settings
      - SIGNUPS_ALLOWED=false
      - INVITATIONS_ALLOWED=true
      - SHOW_PASSWORD_HINT=false

      # WebSocket for live sync
      - WEBSOCKET_ENABLED=true

      # SMTP configuration
      - SMTP_HOST=smtp.example.com
      - SMTP_FROM=vaultwarden@example.com
      - SMTP_PORT=587
      - SMTP_SECURITY=starttls
      - SMTP_USERNAME=vaultwarden@example.com
      - SMTP_PASSWORD=smtp_password

      # Admin panel
      - ADMIN_TOKEN=your_secure_admin_token

      # Logging
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=info

    volumes:
      - ./vw-data:/data
    ports:
      - "8080:80"
      - "3012:3012"  # WebSocket port

Vaultwarden with Traefik

Complete Stack with Traefik Reverse Proxy

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - WEBSOCKET_ENABLED=true
      - SIGNUPS_ALLOWED=false
    volumes:
      - ./vw-data:/data
    labels:
      # Enable Traefik
      - "traefik.enable=true"

      # Main application
      - "traefik.http.routers.vaultwarden.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"

      # WebSocket
      - "traefik.http.routers.vaultwarden-ws.rule=Host(`vault.example.com`) && Path(`/notifications/hub`)"
      - "traefik.http.routers.vaultwarden-ws.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt"
      - "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012"
    networks:
      - traefik

networks:
  traefik:
    external: true

Vaultwarden with External Database

With PostgreSQL

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden
      - SIGNUPS_ALLOWED=false
    volumes:
      - ./vw-data:/data
    depends_on:
      - postgres
    ports:
      - "8080:80"

  postgres:
    image: postgres:15-alpine
    container_name: vaultwarden-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=vaultwarden
      - POSTGRES_USER=vaultwarden
      - POSTGRES_PASSWORD=password
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

With MySQL/MariaDB

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - DATABASE_URL=mysql://vaultwarden:password@mysql:3306/vaultwarden
      - SIGNUPS_ALLOWED=false
    volumes:
      - ./vw-data:/data
    depends_on:
      - mysql
    ports:
      - "8080:80"

  mysql:
    image: mariadb:10
    container_name: vaultwarden-db
    restart: unless-stopped
    environment:
      - MYSQL_DATABASE=vaultwarden
      - MYSQL_USER=vaultwarden
      - MYSQL_PASSWORD=password
      - MYSQL_ROOT_PASSWORD=rootpassword
    volumes:
      - ./mysql-data:/var/lib/mysql

Environment Variable Management

Using .env File

Create .env file:

# Domain
DOMAIN=https://vault.example.com

# Security
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true
ADMIN_TOKEN=your_secure_random_token

# SMTP
SMTP_HOST=smtp.example.com
SMTP_FROM=vaultwarden@example.com
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_USERNAME=vaultwarden@example.com
SMTP_PASSWORD=smtp_password

# Database (if using external)
DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden

Reference in compose file:

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    env_file: .env
    volumes:
      - ./vw-data:/data
    ports:
      - "8080:80"

Container Resource Limits

Memory and CPU Limits

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
    volumes:
      - ./vw-data:/data
    ports:
      - "8080:80"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Health Checks

Container Health Monitoring

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
    volumes:
      - ./vw-data:/data
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/alive"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Multi-Stack Deployment

Vaultwarden + Reverse Proxy + Database

docker-compose.yml:

version: '3'

services:
  # Reverse Proxy
  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - vaultwarden

  # Vaultwarden Application
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden
      - WEBSOCKET_ENABLED=true
      - SIGNUPS_ALLOWED=false
    volumes:
      - ./vw-data:/data
    depends_on:
      - postgres

  # Database
  postgres:
    image: postgres:15-alpine
    container_name: postgres-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=vaultwarden
      - POSTGRES_USER=vaultwarden
      - POSTGRES_PASSWORD=password
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

networks:
  default:
    name: vaultwarden-network

Management Commands

Starting Services

# Start all services
docker compose up -d

# Start specific service
docker compose up -d vaultwarden

# View logs
docker compose logs -f vaultwarden

# Check status
docker compose ps

Updating Containers

# Pull latest images
docker compose pull

# Recreate containers with new images
docker compose up -d

# Or combine both
docker compose pull && docker compose up -d

Backup with Compose

# Stop services
docker compose stop vaultwarden

# Backup volumes
tar -czf backup-$(date +%Y%m%d).tar.gz ./vw-data/

# Restart services
docker compose start vaultwarden

Volume Management

Named Volumes vs Bind Mounts

Bind mounts (recommended for backups):

volumes:
  - ./vw-data:/data           # Easy to backup
  - ./config.json:/data/config.json:ro  # Read-only config

Named volumes (Docker-managed):

services:
  vaultwarden:
    volumes:
      - vaultwarden-data:/data

volumes:
  vaultwarden-data:
    driver: local

Security Best Practices

Secrets Management

Using Docker secrets (Swarm mode):

version: '3.8'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    environment:
      - DOMAIN=https://vault.example.com
      - ADMIN_TOKEN_FILE=/run/secrets/admin_token
    secrets:
      - admin_token
    volumes:
      - ./vw-data:/data

secrets:
  admin_token:
    file: ./secrets/admin_token.txt

Network Isolation

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    networks:
      - frontend
      - backend
    environment:
      - DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden

  postgres:
    image: postgres:15-alpine
    networks:
      - backend  # Only accessible to vaultwarden

  nginx:
    image: nginx:alpine
    networks:
      - frontend  # Only nginx is exposed
    ports:
      - "443:443"

networks:
  frontend:
  backend:
    internal: true  # No external access

Troubleshooting

View Container Logs

# All logs
docker compose logs

# Specific service
docker compose logs vaultwarden

# Follow logs
docker compose logs -f vaultwarden

# Last 100 lines
docker compose logs --tail=100 vaultwarden

Restart Services

# Restart specific service
docker compose restart vaultwarden

# Recreate container (rebuild)
docker compose up -d --force-recreate vaultwarden

Debug Container

# Execute shell in container
docker compose exec vaultwarden sh

# Check environment variables
docker compose exec vaultwarden env

# Check processes
docker compose exec vaultwarden ps aux