SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

Baza danych Vaultwardena: SQLite, PostgreSQL czy MariaDB

Czy domyślne SQLite wystarczy dla Vaultwardena w rodzinie lub małej firmie? Kiedy PostgreSQL albo MariaDB ma sens i jak bezpiecznie przenieść sejf krok po kroku.

Baza danych Vaultwardena: krótka odpowiedź

Baza danych Vaultwardena to domyślnie jeden plik SQLite: db.sqlite3 w katalogu /data. Jeśli z jednej instancji korzysta rodzina albo kilkuosobowa firma, to w zupełności wystarcza. PostgreSQL albo MariaDB ma sens dopiero wtedy, gdy taki serwer już utrzymujesz dla innych usług. Wtedy Vaultwarden może wejść do tej samej procedury kopii zapasowych.

Ten poradnik zakłada, że Vaultwarden już działa w Dockerze na Twoim VPS. Jeśli dopiero zaczynasz, najpierw zrób instalację Vaultwardena na VPS z Docker Compose. Tutaj pokazuję, jak wybrać bazę i jak zapisać ją w DATABASE_URL. Pokazuję też, jak przenieść dane z SQLite bez utraty sejfu. Przykłady używają obrazu vaultwarden/server:1.37.3 (stan na październik 2026). Katalog roboczy to /opt/vaultwarden, a jego właścicielem jest Twój użytkownik. Wszystkie polecenia uruchamiasz sam, na swoim serwerze.

Dlaczego SQLite wystarcza w małej instalacji

Vaultwarden to jeden proces. Ten proces sam otwiera plik db.sqlite3. Między aplikacją a bazą nie ma więc sieci ani osobnego kontenera, który może nie wstać po restarcie. Rodzina z kilkoma telefonami albo biuro z dziesięcioma pracownikami zapisuje do bazy niewiele. Klienty Bitwarden synchronizują sejf, ale większość tej pracy to odczyty.

Vaultwarden domyślnie włącza tryb WAL (write-ahead log, czyli dziennik zapisu z wyprzedzeniem). W tym trybie odczyty nie czekają na zapis, więc dobrze pasuje do takiego ruchu. Ustawienie ENABLE_DB_WAL z pliku .env.template dotyczy tylko SQLite.

Kopia zapasowa SQLite to też jeden plik. Wiki projektu zaleca polecenie sqlite3 ... ".backup ...". Korzysta ono z mechanizmu kopii online, więc daje spójny plik także przy działającym serwerze. Gdzie SQLite sprawdza się na serwerze, a gdzie nie, opisuję w tekście o SQLite w produkcji na VPS.

Zmiana bazy nie wpływa na bezpieczeństwo samych haseł. Klient szyfruje wpisy, zanim trafią na serwer. W każdej z tych baz leżą więc te same zaszyfrowane dane. Szerzej piszę o tym w tekście o tym, czy Vaultwarden jest bezpieczny.

Kiedy PostgreSQL albo MariaDB naprawdę się opłaca

Baza serwerowa to dodatkowa usługa, którą trzeba aktualizować i kopiować. Warto ją wziąć, gdy spełniasz przynajmniej jeden z tych warunków:

  • Na tym samym VPS albo w tej samej sieci już działa PostgreSQL lub MariaDB, którą znasz i regularnie aktualizujesz.
  • Masz jedną procedurę kopii zapasowych dla wszystkich baz w firmie. Chcesz, żeby Vaultwarden był jej częścią, a nie wyjątkiem.
  • Masz replikację albo zapasowy serwer bazy. Chcesz, żeby sejf był chroniony tak samo jak reszta danych.
  • Planujesz więcej niż jedną instancję Vaultwardena. Kilka serwerów nie może bezpiecznie współdzielić pliku SQLite, więc baza serwerowa jest tu warunkiem wstępnym.

Jeśli żaden punkt Cię nie dotyczy, zostań przy SQLite. Każda migracja niesie ryzyko, a w małej instalacji zwykle nic nie zyskujesz. Jeśli zastanawiasz się, gdzie uruchomić samą bazę serwerową, pomoże porównanie bazy w kontenerze i bazy na hoście.

W pliku .env.template przy PostgreSQL jest taki komentarz:

## When using PostgreSQL, specify an appropriate connection URI (recommended)
## or keyword/value connection string.

Słowo „recommended” nie znaczy, że projekt zaleca PostgreSQL zamiast SQLite. Dotyczy tylko formy zapisu połączenia. PostgreSQL przyjmuje adres w postaci URI, na przykład postgresql://user:password@host/baza. Przyjmuje też pary klucz=wartość, na przykład host=... user=... dbname=.... Zalecana jest forma URI. W tym samym pliku sekcja o SQLite opisuje domyślną ścieżkę do bazy i nigdzie nie sugeruje, że trzeba ją zmienić. Nie powołuj się więc na ten komentarz jako na rekomendację PostgreSQL, bo to nadinterpretacja.

Jak DATABASE_URL wybiera bazę

Vaultwarden wybiera silnik bazy po schemacie adresu w zmiennej DATABASE_URL. Adres zaczynający się od sqlite:// oznacza SQLite. Adres mysql:// oznacza MySQL lub MariaDB. Adres postgresql:// oznacza PostgreSQL. Według notatek do wydania 1.37.0 Vaultwarden odrzuca nierozpoznany adres i nie przełącza się już po cichu na SQLite. Literówka w schemacie zatrzyma więc start kontenera. Przyczynę zobaczysz w docker compose logs vaultwarden.

Szablon mówi wprost, że dla SQLite należy użyć schematu sqlite:// i ścieżki do pliku. Domyślna wartość to sqlite://%DATA_FOLDER%/db.sqlite3. Gołą ścieżkę bez schematu Vaultwarden przyjmuje tylko dla zgodności wstecznej i tylko wtedy, gdy plik bazy już istnieje. W nowej instalacji pisz więc pełny adres.

services:
  vaultwarden:
    image: vaultwarden/server:1.37.3
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.pl"
      DATABASE_URL: "sqlite:///data/db.sqlite3"
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"

Jeśli pominiesz DATABASE_URL, dostaniesz tę samą bazę w /data/db.sqlite3. Jawny wpis ma jedną zaletę: każdy, kto czyta plik compose, od razu widzi, gdzie leżą dane.

Plik compose dla PostgreSQL

Najpierw wygeneruj hasło złożone tylko z cyfr i liter od a do f. Takiego hasła nie trzeba kodować ani w adresie URL, ani w pliku compose.

cd /opt/vaultwarden
umask 077
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" >> .env

Compose wczytuje plik .env z katalogu projektu i wstawia jego wartość w miejsce ${POSTGRES_PASSWORD}. Więcej o tym mechanizmie piszę w tekście o plikach .env i sekretach w Docker Compose.

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: vaultwarden
    volumes:
      - ./pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vaultwarden -d vaultwarden"]
      interval: 10s
      timeout: 5s
      retries: 5

  vaultwarden:
    image: vaultwarden/server:1.37.3
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DOMAIN: "https://vault.example.pl"
      DATABASE_URL: "postgresql://vaultwarden:${POSTGRES_PASSWORD}@postgres:5432/vaultwarden"
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"

Przy pierwszym starcie obraz postgres tworzy użytkownika vaultwarden i pustą bazę o tej samej nazwie. W praktyce zastępuje to kroki CREATE USER i CREATE DATABASE ... OWNER z wiki. Wiki testowało migrację na wersji 16, dlatego przypinam tę wersję. Warunek service_healthy sprawia, że Vaultwarden startuje dopiero wtedy, gdy pg_isready zgłasza, że baza jest gotowa. Dlaczego sam depends_on bez healthchecku nie wystarcza, wyjaśniam w tekście o healthchecku PostgreSQL w Docker Compose.

Plik compose dla MariaDB: utf8mb4 w bazie i w tabelach

Wiki Vaultwardena wymaga zestawu znaków utf8mb4 z porównywaniem utf8mb4_unicode_ci. Wymaga go dla bazy i dla każdej tabeli. Jeśli tabele mają różne ustawienia, klucze obce między nimi mogą się nie utworzyć. Najprościej ustawić te wartości jako domyślne dla całego serwera.

cd /opt/vaultwarden
umask 077
printf 'MARIADB_ROOT_PASSWORD=%s\nMARIADB_PASSWORD=%s\n' \
  "$(openssl rand -hex 24)" "$(openssl rand -hex 24)" >> .env
services:
  mariadb:
    image: mariadb:11.4
    restart: unless-stopped
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      MARIADB_DATABASE: vaultwarden
      MARIADB_USER: vaultwarden
      MARIADB_PASSWORD: ${MARIADB_PASSWORD}
    volumes:
      - ./mariadb-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

  vaultwarden:
    image: vaultwarden/server:1.37.3
    restart: unless-stopped
    depends_on:
      mariadb:
        condition: service_healthy
    environment:
      DOMAIN: "https://vault.example.pl"
      DATABASE_URL: "mysql://vaultwarden:${MARIADB_PASSWORD}@mariadb:3306/vaultwarden"
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"

Nie zakładaj, że te ustawienia zadziałały. Gdy Vaultwarden raz utworzy tabele, sprawdź je zapytaniami z wiki. Otwórz konsolę bazy:

docker compose exec mariadb sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" vaultwarden'

Następnie wklej oba zapytania:

SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = "vaultwarden";
SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = "vaultwarden" AND CHARACTER_SET_NAME IS NOT NULL;

Każdy wiersz powinien pokazywać utf8mb4 i utf8mb4_unicode_ci. Jeśli widzisz inne porównywanie, użyj poleceń z wiki. Wiki podaje ALTER DATABASE i zapytanie, które dla każdej tabeli generuje ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci. Wykonaj je, zanim zaimportujesz dane.

Jedna uwaga do poleceń z wiki. Wiki używa klienta mysql. W oficjalnym obrazie mariadb od wersji 11 tej nazwy już nie ma. Klient nazywa się mariadb i przyjmuje te same opcje. Dlatego w tym poradniku wszędzie piszę mariadb.

Hasło ze znakami specjalnymi w DATABASE_URL

DATABASE_URL to adres URL. Znaki takie jak @, :, /, # czy ? mają w nim specjalne znaczenie. W haśle Zaq1@wsx część po @ zostanie odczytana jako nazwa hosta, więc połączenie się nie uda. Dla obu baz wiki każe stosować kodowanie procentowe (percent-encoding). Najczęstsze zamiany:

  • @ zapisz jako %40, a : jako %3A.
  • / zapisz jako %2F, a ? jako %3F.
  • # zapisz jako %23, a sam znak % jako %25.
  • Polskie litery koduje się bajtami UTF-8. Na przykład ą to %C4%85, a ł to %C5%82.

Hasło Zaq1@wsx# zapisane w adresie wygląda więc tak: Zaq1%40wsx%23.

Docker Compose ma jeszcze jedną pułapkę. Znak $ oznacza w pliku compose zmienną, więc Compose próbuje wstawić w to miejsce jej wartość. Hasło Zaq1$wsx wpisane wprost do pliku compose daje ostrzeżenie The "wsx" variable is not set. Defaulting to a blank string., a do kontenera trafia skrócone hasło. Hasło z openssl rand -hex 24 nie ma żadnego z tych problemów, dlatego używam go w przykładach.

Zanim cokolwiek zmienisz: sprawdzona kopia zapasowa

Wiki opisuje obie migracje jako działanie na własne ryzyko. Przy PostgreSQL pisze wprost: „you are using this at your own risk and you are strongly advised to backup your installation and data! This is unsupported and has not been robustly tested.” Przy MariaDB stoi to samo ostrzeżenie o ryzyku i kopii. Kopia zapasowa nie jest więc dodatkiem. To pierwszy krok i bez niego nie zaczynaj.

Zatrzymaj Vaultwardena, a potem skopiuj plik bazy i cały katalog danych:

cd /opt/vaultwarden
sudo apt update && sudo apt install -y sqlite3
docker compose stop vaultwarden
sudo sqlite3 vw-data/db.sqlite3 ".backup 'db-przed-migracja.sqlite3'"
sudo sqlite3 db-przed-migracja.sqlite3 'PRAGMA integrity_check'
sudo tar czf vw-data-przed-migracja.tgz vw-data

Gdy plik kopii jest spójny, PRAGMA integrity_check zwraca jedno słowo: ok. Każdy inny wynik oznacza, że nie masz jeszcze dobrej kopii. Skopiuj oba pliki poza serwer. Zapisz też liczbę wierszy w najważniejszych tabelach. Po migracji porównasz z nią nową bazę:

sudo sqlite3 vw-data/db.sqlite3 'SELECT count(*) FROM users; SELECT count(*) FROM ciphers;'

Wiki ostrzega też przed starą bazą. Jeśli Twoja baza SQLite pochodzi ze starej wersji Vaultwardena, najpierw zaktualizuj Vaultwardena, nadal na SQLite. Uruchom go raz, żeby zaktualizował schemat bazy, a dopiero potem przenoś dane. Jeśli tego nie zrobisz, nowa baza będzie miała kolumny, których nie ma w starym zrzucie, i import się nie powiedzie.

Migracja z SQLite do PostgreSQL krok po kroku

Wiki podaje dla tej migracji przetestowane wersje: „Tested with SQLite3 3.37.2, PostgreSQL 16.10 and Vaultwarden 1.34.3”. Twoje wersje będą nowsze, więc tym bardziej trzymaj się kolejności kroków i sprawdzaj wynik.

  1. Dodaj usługę postgres do pliku compose, ale w Vaultwardenie zostaw na razie stary DATABASE_URL. Uruchom samą bazę poleceniem docker compose up -d postgres. Baza jest pusta.
  2. Zmień DATABASE_URL na adres postgresql:// i uruchom Vaultwardena raz poleceniem docker compose up -d vaultwarden. Obserwuj docker compose logs -f vaultwarden. Vaultwarden tworzy teraz schemat w pustej bazie. Nie loguj się i nie zakładaj kont.
  3. Zatrzymaj Vaultwardena poleceniem docker compose stop vaultwarden.
  4. Zainstaluj pgloader na hoście: sudo apt install -y pgloader.
  5. Wyłącz WAL w pliku SQLite, tak jak każe wiki.
  6. Przygotuj plik bitwarden.load i uruchom pgloader.
  7. Sprawdź liczby wierszy i uruchom Vaultwardena.

Krok 5 wygląda tak:

sudo sqlite3 vw-data/db.sqlite3 'PRAGMA journal_mode=delete;'
sudo sqlite3 vw-data/db.sqlite3 'PRAGMA journal_mode'

Drugie polecenie powinno wypisać delete. Jeśli wypisuje wal, baza nadal używa dziennika WAL. Część ostatnich zapisów może wtedy leżeć w osobnym pliku db.sqlite3-wal.

pgloader działa na hoście, a PostgreSQL w sieci Dockera. Host nie zna nazwy postgres z tej sieci. Dlatego na czas migracji udostępnij port bazy tylko pod adresem 127.0.0.1. Dodaj do usługi postgres sekcję ports z wpisem "127.0.0.1:5432:5432" i ponownie wykonaj docker compose up -d postgres.

Plik bitwarden.load to przykład z wiki. Wpisz w nim swoją ścieżkę i hasło z .env:

load database
     from sqlite:///opt/vaultwarden/vw-data/db.sqlite3
     into postgresql://vaultwarden:TWOJE_HASLO@127.0.0.1:5432/vaultwarden
     WITH data only, include no drop, reset sequences
     EXCLUDING TABLE NAMES LIKE '__diesel_schema_migrations'
     ALTER SCHEMA 'bitwarden' RENAME TO 'public'
;

data only oznacza, że pgloader kopiuje tylko dane. Trafiają one do schematu, który przed chwilą utworzył Vaultwarden. Tabela __diesel_schema_migrations jest pominięta, bo nowa baza ma już własny zapis migracji. Uruchom import:

sudo pgloader bitwarden.load

Na końcu pgloader wyświetla podsumowanie dla każdej tabeli, łącznie z liczbą błędów. Przeczytaj je, zanim pójdziesz dalej. Potem porównaj liczby wierszy z tymi zapisanymi przed migracją:

docker compose exec postgres psql -U vaultwarden -d vaultwarden -c 'SELECT count(*) FROM users;' -c 'SELECT count(*) FROM ciphers;'

Jeśli liczby się zgadzają, usuń sekcję ports z usługi postgres. Usuń też plik bitwarden.load, bo zawiera hasło do bazy. Następnie uruchom całość poleceniem docker compose up -d.

Migracja z SQLite do MariaDB krok po kroku

Strona wiki o MariaDB nie podaje przetestowanych wersji. Mówi tylko, że korzystasz z tej metody na własne ryzyko i powinieneś mieć kopię zapasową. Kolejność kroków jest taka sama jak przy PostgreSQL.

  1. Dodaj usługę mariadb i uruchom ją poleceniem docker compose up -d mariadb. Poczekaj, aż docker compose ps pokaże stan healthy.
  2. Zmień DATABASE_URL na mysql://, uruchom Vaultwardena raz i poczekaj w logach, aż wystartuje. Wiki mówi: „Do not do anything else.”
  3. Zatrzymaj Vaultwardena i sprawdź zestaw znaków zapytaniami z sekcji o pliku compose.
  4. Zrzuć z SQLite same polecenia INSERT.
  5. Załaduj zrzut do MariaDB i sprawdź liczby wierszy.

Krok 4 to polecenia z wiki. Pliki zrzutu zawierają cały sejf. umask 077 sprawia, że może je czytać tylko Twój użytkownik.

umask 077
sudo sqlite3 vw-data/db.sqlite3 .dump | grep "^INSERT INTO" | grep -v "__diesel_schema_migrations" > sqlitedump.sql
echo "SET FOREIGN_KEY_CHECKS=0;" > mysqldump.sql
cat sqlitedump.sql >> mysqldump.sql

Filtr grep "^INSERT INTO" zostawia same dane, bo schemat w MariaDB Vaultwarden utworzył już w kroku 2. SET FOREIGN_KEY_CHECKS=0; na początku pliku wyłącza sprawdzanie kluczy obcych. Dzięki temu kolejność tabel w zrzucie nie ma znaczenia. Wiki ładuje plik poleceniem mysql --force --password --user=vaultwarden --database=vaultwarden < mysqldump.sql. W kontenerze mariadb:11.4 ten sam import wygląda tak:

docker compose exec -T mariadb sh -c 'mariadb --force --user="$MARIADB_USER" --password="$MARIADB_PASSWORD" --database=vaultwarden' < mysqldump.sql

Przez --force klient nie przerywa importu przy pierwszym błędzie. To wygodne, ale łatwo wtedy przeoczyć pojedynczy odrzucony wiersz. Wiki pisze, że z opcją --show-warnings zobaczysz ostrzeżenia o obcinaniu pól z datą i godziną, i że to „seems to be okay”. Jeśli MariaDB zgłasza, że liczba wartości w wierszu się nie zgadza, wróć do ostrzeżenia o starej bazie. Zaktualizuj Vaultwardena na SQLite i powtórz migrację.

Sprawdź liczby wierszy:

docker compose exec mariadb sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" vaultwarden -e "SELECT count(*) FROM users; SELECT count(*) FROM ciphers;"'

Jeśli się zgadzają, usuń sqlitedump.sql i mysqldump.sql. Potem uruchom Vaultwardena poleceniem docker compose up -d vaultwarden.

Jak sprawdzić, czy migracja naprawdę się udała

Zgodne liczby wierszy to dopiero początek. Zaloguj się do sejfu w przeglądarce i otwórz kilka wpisów. Pobierz jeden załącznik i sprawdź, czy widzisz kolekcje organizacji. Na telefonie uruchom synchronizację ręcznie. Załączniki leżą w vw-data/attachments, a nie w bazie. Działają więc tylko wtedy, gdy katalog /data jest nadal podłączony do kontenera.

Jeśli coś jest nie tak, łatwo wrócić do starej bazy, bo migracja nie usuwa pliku db.sqlite3. Przywróć stary DATABASE_URL z adresem sqlite:// i wykonaj docker compose up -d vaultwarden. Przy starcie Vaultwarden znowu włączy WAL, chyba że ustawisz ENABLE_DB_WAL=false. Pamiętaj tylko, że zmiany zapisane w nowej bazie po migracji nie wrócą do SQLite.

Ile zasobów zużywa każda baza? Zmierz to sam

Nie podaję tu liczb dotyczących pamięci ani czasu odpowiedzi. Zależą od wersji obrazu, ustawień bazy i liczby osób, które korzystają z sejfu. Zmierz je na swoim serwerze przed migracją i po niej:

docker stats --no-stream
sudo du -sh vw-data pg-data mariadb-data 2>/dev/null

docker stats pokazuje bieżące zużycie pamięci i procesora przez każdy kontener, w kolumnach MEM USAGE / LIMIT i CPU %. Uruchom je kilka razy, także po synchronizacji kilku klientów. Na małym VPS najważniejsze jest, ile pamięci zostaje dla innych usług. Kontener z bazą to dodatkowy proces, który musi się zmieścić w tym samym limicie. Porównaj wyniki z pomiarami przy SQLite i zdecyduj na podstawie własnych liczb.

Co zmienia się w kopiach zapasowych

Przy SQLite kopia bazy to jeden plik w /data. Przy PostgreSQL i MariaDB wiki mówi jasno, że zrzut bazy trzeba zrobić osobno i że wiki tego nie opisuje. Teraz to Twoje zadanie.

Dla PostgreSQL:

docker compose exec -T postgres pg_dump -U vaultwarden -d vaultwarden -Fc > vaultwarden-$(date +%F).dump

Dla MariaDB:

docker compose exec -T mariadb sh -c 'mariadb-dump --single-transaction -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" vaultwarden' > vaultwarden-$(date +%F).sql

--single-transaction daje spójny zrzut tabel InnoDB i nie blokuje przy tym zapisów. Zrzut, którego nikt nigdy nie odtworzył, nie jest jeszcze kopią. Co jakiś czas załaduj go do pustej bazy testowej i sprawdź liczby wierszy.

Baza to nie wszystko. Katalog /data nadal przechowuje pliki, których nie ma w żadnej bazie. Według wiki niezbędny jest katalog attachments. Wiki zaleca też kopię pliku config.json z ustawieniami panelu administratora oraz plików rsa_key*. Bez plików rsa_key* wszyscy użytkownicy zostaną wylogowani. Kopia katalogu sends jest opcjonalna, a pamięć podręczną ikon można pominąć. Pełną listę znajdziesz na stronie wiki Backing up your vault. Gotowy skrypt, który łączy zrzut bazy z kopią katalogu danych, opisuję w tekście o kopii zapasowej i odtwarzaniu Vaultwardena.

Jeśli przechowujesz sejf firmy, zapisz też, gdzie trafiają kopie i kto ma do nich dostęp. Zrzut bazy zawiera adresy e-mail użytkowników. W świetle RODO to dane osobowe, nawet jeśli same hasła są zaszyfrowane.

FAQ

Czy Vaultwarden zaleca PostgreSQL zamiast SQLite?

Nie. Słowo „(recommended)” w .env.template dotyczy formy zapisu połączenia z PostgreSQL: zalecany jest adres URI, a nie zapis klucz=wartość. Domyślną bazą Vaultwardena pozostaje SQLite. Dla jednej instancji, z której korzysta rodzina lub mała firma, w zupełności wystarcza.

Czy przejście na PostgreSQL lub MariaDB przyspieszy Vaultwardena?

W małej instalacji raczej nie zauważysz różnicy, ale nie zgaduj. Przed migracją i po niej zmierz zużycie zasobów poleceniem docker stats --no-stream i sprawdź czas synchronizacji klienta. Baza serwerowa ma sens głównie wtedy, gdy już ją utrzymujesz dla innych usług albo planujesz kilka instancji Vaultwardena.

Dlaczego Vaultwarden nie łączy się z bazą, chociaż hasło jest poprawne?

Najczęściej hasło zawiera znak specjalny, którego nie zakodowano w DATABASE_URL. Na przykład @ trzeba zapisać jako %40, a # jako %23. Inną częstą przyczyną jest znak $ w pliku compose, który Compose traktuje jak zmienną. Sprawdź docker compose logs vaultwarden. Rozważ też hasło wygenerowane przez openssl rand -hex 24, którego nie trzeba kodować.

Czy da się wrócić z PostgreSQL lub MariaDB do SQLite?

Wiki nie opisuje migracji w tę stronę. Najbezpieczniej wrócić do starego pliku db.sqlite3, który migracja zostawia na miejscu, albo do kopii zrobionej przed migracją. Przywróć adres sqlite:// w DATABASE_URL i uruchom kontener ponownie. Zmiany zapisane w nowej bazie po migracji przepadną.