This guide provides Docker Compose configurations for Bitwarden deployments on Linux servers. Covers both Bitwarden Standard (enterprise) and Bitwarden Lite (personal/home lab).
Bitwarden offers two Docker deployment options:
| Edition | Image | Use Case | Containers |
|---|---|---|---|
| Standard | ghcr.io/bitwarden/bitwarden:2026.2.0 |
Enterprise, Organizations | 11 services |
| Lite | ghcr.io/bitwarden/lite:2026.2.0 |
Personal, Home Lab | 1 container |
Single-container deployment released in December 2025.
sudo mkdir -p /opt/bitwarden-lite
sudo chown $USER:$USER /opt/bitwarden-lite
cd /opt/bitwarden-lite
Create docker-compose.yml:
version: "3.8"
services:
bitwarden-lite:
image: ghcr.io/bitwarden/lite:latest
container_name: bitwarden-lite
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080" # Bind to localhost only
volumes:
- ./data:/etc/bitwarden
environment:
# === Required Settings ===
BW_DOMAIN: bitwarden.example.com
# Installation credentials (register at bitwarden.com/host)
BW_INSTALLATION_ID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
BW_INSTALLATION_KEY: "xxxxxxxxxxxxxxxxxxxxxxxx"
# === Database Configuration ===
BW_DB_PROVIDER: sqlite # Default, stores vault.db in data directory
# Optional: External PostgreSQL
# BW_DB_PROVIDER: postgresql
# BW_DB_CONNECTION_STRING: "Host=postgres;Database=bitwarden;Username=bw;Password=secret"
# Optional: External MySQL/MariaDB
# BW_DB_PROVIDER: mysql
# BW_DB_CONNECTION_STRING: "Server=mysql;Database=bitwarden;User=bw;Password=secret;"
# === SMTP Configuration (Recommended) ===
BW_SMTP_HOST: smtp.example.com
BW_SMTP_PORT: 587
BW_SMTP_SSL: "false"
BW_SMTP_STARTTLS: "true"
BW_SMTP_FROM: bitwarden@example.com
BW_SMTP_USERNAME: smtp-username
BW_SMTP_PASSWORD: smtp-password
# === Optional Settings ===
# User permissions for volume mounts
PUID: 1000
PGID: 1000
# Admin API token (optional)
# BW_ADMIN_TOKEN: "your-secure-admin-token"
# Global equivalent settings (alternative naming)
# globalSettings__domain: "https://bitwarden.example.com"
# Security hardening
security_opt:
- no-new-privileges:true
read_only: false # Must be false for Lite to write to data directory
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Logging configuration
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
docker compose up -d
# Check container status
docker compose ps
# View logs
docker compose logs -f bitwarden-lite
# Test health endpoint
curl http://localhost:8080/health
Full deployment with 11 Docker containers for organizations.
Create directory structure:
sudo mkdir -p /opt/bitwarden
sudo chown $USER:$USER /opt/bitwarden
cd /opt/bitwarden
Create docker-compose.yml:
version: "3.8"
services:
# === Core Application Services ===
bitwarden-api:
image: ghcr.io/bitwarden/bitwarden:2026.2.0
container_name: bw-api
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__baseServiceUri__internalApi=http://bw-api:8080
- globalSettings__baseServiceUri__internalIdentity=http://bw-identity:8080
- globalSettings__baseServiceUri__internalNotifications=http://bw-notifications:8080
- globalSettings__baseServiceUri__internalEvents=http://bw-events:8080
- globalSettings__baseServiceUri__internalAdmin=http://bw-admin:8080
- globalSettings__baseServiceUri__internalSso=http://bw-sso:8080
- globalSettings__domain: "https://bitwarden.example.com"
# Database connection
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
# Redis connection
- globalSettings__connectionStrings__redis: "bw-redis:6379,ssl=false,abortConnect=false"
# Installation credentials
- globalSettings__installation__id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
- globalSettings__installation__key: "xxxxxxxxxxxxxxxxxxxxxxxx"
# SMTP configuration
- globalSettings__mail__smtp__host: "smtp.example.com"
- globalSettings__mail__smtp__port: "587"
- globalSettings__mail__smtp__ssl: "false"
- globalSettings__mail__smtp__username: "smtp-username"
- globalSettings__mail__smtp__password: "smtp-password"
- globalSettings__mail__replyToEmail: "noreply@example.com"
# Optional: YubiKey OTP validation
# - globalSettings__yubico__clientId: "your-yubi-client-id"
# - globalSettings__yubico__key: "your-yubi-key"
# Optional: Duo 2FA
# - globalSettings__duo__apiEnvironment: "API"
# - globalSettings__duo__akey: "your-duo-akey"
# - globalSettings__duo__ikey: "your-duo-ikey"
# - globalSettings__duo__skey: "your-duo-skey"
volumes:
- ./bw-data/core:/etc/bitwarden
- ./bw-data/attachments:/etc/bitwarden/core/Attachments
- ./bw-data/sends:/etc/bitwarden/core/Sends
depends_on:
bw-mssql:
condition: service_healthy
bw-redis:
condition: service_started
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
bitwarden-web:
image: ghcr.io/bitwarden/web-vault:2026.2.0
container_name: bw-web
restart: unless-stopped
environment:
- ENV=production
- SELF_HOST=true
- URL=https://bitwarden.example.com
- API_URL=https://bitwarden.example.com/api
- IDENTITY_URL=https://bitwarden.example.com/identity
- NOTIFICATIONS_URL=wss://bitwarden.example.com/hub
depends_on:
- bw-api
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
# === Identity and Authentication ===
bw-identity:
image: ghcr.io/bitwarden/identity:2026.2.0
container_name: bw-identity
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__baseServiceUri__internalIdentity=http://bw-identity:8080
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
- globalSettings__connectionStrings__redis: "bw-redis:6379,ssl=false,abortConnect=false"
depends_on:
bw-mssql:
condition: service_healthy
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
bw-sso:
image: ghcr.io/bitwarden/sso:2026.2.0
container_name: bw-sso
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__baseServiceUri__internalSso=http://bw-sso:8080
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
depends_on:
bw-mssql:
condition: service_healthy
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
# === Notifications and Events ===
bw-notifications:
image: ghcr.io/bitwarden/notifications:2026.2.0
container_name: bw-notifications
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
- globalSettings__connectionStrings__redis: "bw-redis:6379,ssl=false,abortConnect=false"
depends_on:
- bw-mssql
- bw-redis
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
bw-events:
image: ghcr.io/bitwarden/events:2026.2.0
container_name: bw-events
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
- globalSettings__connectionStrings__redis: "bw-redis:6379,ssl=false,abortConnect=false"
depends_on:
- bw-mssql
- bw-redis
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
# === Admin and Migrations ===
bw-admin:
image: ghcr.io/bitwarden/admin:2026.2.0
container_name: bw-admin
restart: unless-stopped
environment:
- ASPNETCORE_URLS=http://+:8080
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
depends_on:
bw-mssql:
condition: service_healthy
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
bw-migrations:
image: ghcr.io/bitwarden/migrations:2026.2.0
container_name: bw-migrations
restart: "no" # Run once and exit
environment:
- globalSettings__connectionStrings__database: "Server=bw-mssql;Database=bitwarden;User=sa;Password=YourStrongPassword123!;TrustServerCertificate=true"
depends_on:
bw-mssql:
condition: service_healthy
networks:
- bitwarden-net
# === Database and Cache ===
bw-mssql:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: bw-mssql
restart: unless-stopped
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=YourStrongPassword123!
- MSSQL_USER=sa
volumes:
- ./mssql-data:/var/opt/mssql
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'YourStrongPassword123!' -Q 'SELECT 1'"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
bw-redis:
image: redis:7-alpine
container_name: bw-redis
restart: unless-stopped
volumes:
- ./redis-data:/data
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
# === Reverse Proxy ===
bw-nginx:
image: nginx:alpine
container_name: bw-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./bw-data/nginx/cache:/var/cache/nginx
depends_on:
- bw-api
- bw-web
networks:
- bitwarden-net
security_opt:
- no-new-privileges:true
# === Networks ===
networks:
bitwarden-net:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
Create nginx/nginx.conf:
events {
worker_connections 1024;
}
http {
# Upstream definitions
upstream api {
server bw-api:8080;
}
upstream web {
server bw-web:8080;
}
upstream identity {
server bw-identity:8080;
}
upstream notifications {
server bw-notifications:8080;
}
# Rate limiting
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/s;
# HTTP to HTTPS redirect
server {
listen 80;
server_name bitwarden.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name bitwarden.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Logging
access_log /var/log/nginx/bitwarden_access.log;
error_log /var/log/nginx/bitwarden_error.log;
# API routes
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Identity routes
location /identity/ {
limit_req zone=auth_limit burst=10 nodelay;
proxy_pass http://identity;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket for notifications
location /hub {
proxy_pass http://notifications;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# Web vault (default)
location / {
proxy_pass http://web;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Add these security options to all services:
security_opt:
- no-new-privileges:true
# For non-root execution (if supported by image)
user: "1000:1000"
# Drop unnecessary capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if needed
For enhanced security (where applicable):
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100m
- /run:noexec,nosuid,size=50m
networks:
bitwarden-net:
internal: true # No external access except via proxy
Prevent resource exhaustion:
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '0.5'
memory: 512M
| Variable | Description | Required | Example |
|---|---|---|---|
BW_DOMAIN |
Domain name for the instance | ✅ | bitwarden.example.com |
BW_INSTALLATION_ID |
Installation ID from bitwarden.com/host | ✅ | xxxxxxxx-xxxx-... |
BW_INSTALLATION_KEY |
Installation Key from bitwarden.com/host | ✅ | xxxxxxxxxxxxxxxx |
BW_DB_PROVIDER |
Database provider | ❌ | sqlite, postgresql, mysql |
BW_DB_CONNECTION_STRING |
External database connection string | ❌ | Host=db;Database=bw;... |
BW_SMTP_HOST |
SMTP server hostname | ❌ | smtp.example.com |
BW_SMTP_PORT |
SMTP server port | ❌ | 587 |
BW_SMTP_SSL |
Use SSL for SMTP | ❌ | true, false |
BW_SMTP_FROM |
From address for emails | ❌ | bitwarden@example.com |
BW_SMTP_USERNAME |
SMTP authentication username | ❌ | smtp-user |
BW_SMTP_PASSWORD |
SMTP authentication password | ❌ | smtp-pass |
PUID |
Process user ID | ❌ | 1000 |
PGID |
Process group ID | ❌ | 1000 |
| Variable | Description | Required |
|---|---|---|
globalSettings__domain |
Public domain URL | ✅ |
globalSettings__installation__id |
Installation ID | ✅ |
globalSettings__installation__key |
Installation Key | ✅ |
globalSettings__connectionStrings__database |
MSSQL connection string | ✅ |
globalSettings__connectionStrings__redis |
Redis connection string | ✅ |
globalSettings__mail__smtp__host |
SMTP host | ❌ |
globalSettings__mail__smtp__port |
SMTP port | ❌ |
globalSettings__mail__smtp__username |
SMTP username | ❌ |
globalSettings__mail__smtp__password |
SMTP password | ❌ |
Create backup.sh:
#!/bin/bash
set -e
BACKUP_DIR="/backup/bitwarden"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
mkdir -p $BACKUP_DIR
echo "Starting Bitwarden backup at $DATE"
# Backup Lite data directory
if [ -d "/opt/bitwarden-lite/data" ]; then
tar -czf $BACKUP_DIR/bw-lite-data-$DATE.tar.gz \
-C /opt/bitwarden-lite data
echo "Lite data backed up"
fi
# Backup Standard data directories
if [ -d "/opt/bitwarden/bw-data" ]; then
tar -czf $BACKUP_DIR/bw-standard-data-$DATE.tar.gz \
-C /opt/bitwarden bw-data
echo "Standard data backed up"
fi
# Backup MSSQL database (if using Docker)
docker exec bw-mssql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'YourStrongPassword123!' \
-Q "BACKUP DATABASE [bitwarden] TO DISK = '/var/opt/mssql/backup/bitwarden-$DATE.bak'"
docker cp bw-mssql:/var/opt/mssql/backup/bitwarden-$DATE.bak \
$BACKUP_DIR/bw-mssql-$DATE.bak
echo "Database backed up"
# Cleanup old backups
find $BACKUP_DIR -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.bak" -mtime +$RETENTION_DAYS -delete
echo "Backup completed successfully"
# Stop containers
docker compose down
# Restore data directory
tar -xzf backup/bw-lite-data-20260216_120000.tar.gz -C /opt/bitwarden-lite
# Restore database
docker compose up -d bw-mssql
sleep 30
docker exec -i bw-mssql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'YourStrongPassword123!' \
-Q "RESTORE DATABASE [bitwarden] FROM DISK = '/var/opt/mssql/backup/bitwarden-20260216_120000.bak'"
# Start all services
docker compose up -d
# Check all containers
docker compose ps
# View specific service logs
docker compose logs -f bw-api
# Check database connectivity
docker compose exec bw-mssql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'YourStrongPassword123!' \
-Q "SELECT name FROM sys.databases"
Add container exporter for monitoring:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "8080:8080"
Container fails to start:
# Check logs
docker compose logs bitwarden-lite
# Verify port availability
sudo netstat -tlnp | grep :8080
# Check resource usage
docker stats
Database connection errors:
# Test MSSQL connectivity
docker compose exec bw-mssql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'YourPassword' \
-Q "SELECT @@VERSION"
# Check network
docker compose exec bw-api ping bw-mssql
SSL/TLS issues:
# Verify certificates
openssl s_client -connect bitwarden.example.com:443 -servername bitwarden.example.com
# Check certificate expiry
openssl x509 -in /etc/nginx/ssl/fullchain.pem -noout -dates
High memory usage:
# Check MSSQL memory
docker exec bw-mssql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'YourPassword' \
-Q "SELECT * FROM sys.dm_os_process_memory"
# Limit MSSQL memory in compose file
# environment:
# - MSSQL_MEMORY_LIMIT_MB=2048
Any questions?
Feel free to contact us. Find all contact information on our contact page.