Database Options for Self-Hosted Applications
Notes on picking between SQLite, MySQL/MariaDB, and PostgreSQL for self-hosted apps. Short version: if a single process and a single machine are writing, SQLite is the right default. Move to MariaDB or Postgres when you have concurrent writers, multiple hosts, or specific features (full-text search, JSON, replication) that SQLite can’t cover.
Database Comparison
| Feature | SQLite | MySQL/MariaDB | PostgreSQL |
|---|---|---|---|
| Type | File-based | Client-server | Client-server |
| Deployment | Zero config | Moderate | Moderate |
| Concurrency | Limited | High | Very High |
| Scalability | Single server | Multi-server | Multi-server |
| Use Case | Small deployments | General purpose | Advanced features |
| Memory Usage | 10-50 MB | 100-500 MB | 100-500 MB |
| ACID Compliance | ✓ | ✓ | ✓ |
| Full Text Search | Basic | Good | Excellent |
| JSON Support | Limited | Good | Excellent |
SQLite Configuration
Vaultwarden Default Setup
# Default database location
DATABASE_URL=data/db.sqlite3 Advantages
- Zero configuration - Works out of the box
- Single file - Easy backup and migration
- Low resource usage - Perfect for small deployments
- Embedded - No separate database server needed
Limitations
- Concurrency - Limited write concurrency
- Scalability - Single server only
- Network access - No remote connections
Backup
# Online backup (no downtime)
sqlite3 /data/db.sqlite3 ".backup '/backups/db-backup.sqlite3'"
# Offline backup
docker stop vaultwarden
cp /vw-data/db.sqlite3 /backups/db-$(date +%Y%m%d).sqlite3
docker start vaultwarden Maintenance
# Optimize database
sqlite3 /data/db.sqlite3 "VACUUM;"
# Check integrity
sqlite3 /data/db.sqlite3 "PRAGMA integrity_check;" MySQL/MariaDB Configuration
Vaultwarden Setup
1. Create database:
CREATE DATABASE vaultwarden CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'vaultwarden'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON vaultwarden.* TO 'vaultwarden'@'%';
FLUSH PRIVILEGES; 2. Configure Vaultwarden:
DATABASE_URL=mysql://vaultwarden:password@mysql:3306/vaultwarden Docker Compose Integration
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
environment:
- DATABASE_URL=mysql://vaultwarden:password@mysql:3306/vaultwarden
depends_on:
- mysql
mysql:
image: mariadb:10
environment:
- MYSQL_DATABASE=vaultwarden
- MYSQL_USER=vaultwarden
- MYSQL_PASSWORD=password
- MYSQL_ROOT_PASSWORD=rootpassword
volumes:
- ./mysql-data:/var/lib/mysql Backup
# Full database dump
mysqldump -u vaultwarden -p vaultwarden > vaultwarden-$(date +%Y%m%d).sql
# Compressed backup
mysqldump -u vaultwarden -p vaultwarden | gzip > vaultwarden-$(date +%Y%m%d).sql.gz
# Automated script
#!/bin/bash
mysqldump -u vaultwarden -p$MYSQL_PASSWORD vaultwarden | \
gzip > /backups/vaultwarden-$(date +%Y%m%d).sql.gz Restore
# Restore from dump
mysql -u vaultwarden -p vaultwarden < vaultwarden-20250106.sql
# Restore compressed
gunzip < vaultwarden-20250106.sql.gz | mysql -u vaultwarden -p vaultwarden Maintenance
# Optimize tables
mysqlcheck -u vaultwarden -p --optimize vaultwarden
# Repair tables
mysqlcheck -u vaultwarden -p --repair vaultwarden
# Check tables
mysqlcheck -u vaultwarden -p --check vaultwarden PostgreSQL Configuration
Vaultwarden Setup
1. Create database:
CREATE DATABASE vaultwarden;
CREATE USER vaultwarden WITH PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE vaultwarden TO vaultwarden; 2. Configure Vaultwarden:
DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden Docker Compose Integration
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
environment:
- DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden
depends_on:
- postgres
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=vaultwarden
- POSTGRES_USER=vaultwarden
- POSTGRES_PASSWORD=password
volumes:
- ./postgres-data:/var/lib/postgresql/data Backup
# Full database dump
pg_dump -U vaultwarden vaultwarden > vaultwarden-$(date +%Y%m%d).sql
# Compressed backup
pg_dump -U vaultwarden vaultwarden | gzip > vaultwarden-$(date +%Y%m%d).sql.gz
# Custom format (faster restore)
pg_dump -U vaultwarden -Fc vaultwarden > vaultwarden-$(date +%Y%m%d).dump Restore
# Restore from SQL dump
psql -U vaultwarden vaultwarden < vaultwarden-20250106.sql
# Restore custom format
pg_restore -U vaultwarden -d vaultwarden vaultwarden-20250106.dump Maintenance
# Vacuum database
psql -U vaultwarden vaultwarden -c "VACUUM FULL;"
# Analyze database
psql -U vaultwarden vaultwarden -c "ANALYZE;"
# Reindex
psql -U vaultwarden vaultwarden -c "REINDEX DATABASE vaultwarden;" Bitwarden Database Configuration
SQL Server (Default)
Configuration (in config.yml):
database:
type: mssql
name: vault
username: bitwarden
# Password in environment variables Connection string:
globalSettings__sqlServer__connectionString=Data Source=mssql;Initial Catalog=vault;User ID=bitwarden;Password=db_password PostgreSQL (Alternative)
database:
type: postgres
name: vault
username: bitwarden
host: postgres
port: 5432 Migration Between Databases
SQLite to PostgreSQL
1. Export from SQLite:
sqlite3 db.sqlite3 .dump > export.sql 2. Convert and import:
# Use migration tool
pip install sqlite3-to-postgres
sqlite3-to-postgres --sqlite-file db.sqlite3 --postgres-uri postgresql://user:pass@host/db SQLite to MySQL
1. Export:
sqlite3 db.sqlite3 .dump > export.sql 2. Convert syntax:
# Remove SQLite-specific syntax
sed -i 's/AUTOINCREMENT/AUTO_INCREMENT/g' export.sql
# Import
mysql -u user -p database < export.sql Performance Tuning
SQLite Optimization
-- Enable WAL mode
PRAGMA journal_mode=WAL;
-- Increase cache size (in KB)
PRAGMA cache_size=-64000; -- 64MB
-- Optimize on close
PRAGMA optimize; MySQL/MariaDB Tuning
# /etc/mysql/my.cnf
[mysqld]
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
max_connections = 100
query_cache_size = 16M PostgreSQL Tuning
# /etc/postgresql/15/main/postgresql.conf
shared_buffers = 256MB
effective_cache_size = 1GB
maintenance_work_mem = 64MB
max_connections = 100 Monitoring
Database Size
SQLite:
ls -lh /data/db.sqlite3 MySQL:
SELECT table_schema AS "Database",
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS "Size (MB)"
FROM information_schema.tables
WHERE table_schema = 'vaultwarden'
GROUP BY table_schema; PostgreSQL:
SELECT pg_size_pretty(pg_database_size('vaultwarden')); Connection Monitoring
MySQL:
SHOW PROCESSLIST;
SHOW STATUS LIKE '%Connections%'; PostgreSQL:
SELECT * FROM pg_stat_activity;
SELECT count(*) FROM pg_stat_activity; Troubleshooting
SQLite Database Locked
Symptoms: Database is locked error
Causes:
- Multiple concurrent writes
- Long-running transactions
Solutions:
- Enable WAL mode:
PRAGMA journal_mode=WAL; - Reduce concurrent operations
- Migrate to PostgreSQL/MySQL
MySQL Connection Errors
Check connectivity:
mysql -u vaultwarden -p -h mysql -e "SELECT 1;" Common issues:
- Wrong credentials
- Database not created
- Network connectivity
PostgreSQL Connection Issues
Test connection:
psql -U vaultwarden -h postgres -d vaultwarden -c "SELECT 1;" Check pg_hba.conf permissions:
# Allow connections from Docker network
host all all 172.0.0.0/8 md5 Related Documentation
- Vaultwarden Self-Hosted Deployment - Vaultwarden database configuration
- Bitwarden Self-Hosted Deployment - Bitwarden database setup
- Docker Compose Configuration for Password Managers - Database container setup
- Password Manager Backup and Recovery Strategies - Database backup strategies