Database Options for Self-Hosted Applications

6 October 2025 · Updated 7 October 2025

databasesqlitemysqlpostgresqlself-hosted

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

FeatureSQLiteMySQL/MariaDBPostgreSQL
TypeFile-basedClient-serverClient-server
DeploymentZero configModerateModerate
ConcurrencyLimitedHighVery High
ScalabilitySingle serverMulti-serverMulti-server
Use CaseSmall deploymentsGeneral purposeAdvanced features
Memory Usage10-50 MB100-500 MB100-500 MB
ACID Compliance
Full Text SearchBasicGoodExcellent
JSON SupportLimitedGoodExcellent

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:

  1. Enable WAL mode: PRAGMA journal_mode=WAL;
  2. Reduce concurrent operations
  3. 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