BackVault is a lightweight Dockerized multi-architecture service that periodically backs up your Bitwarden or Vaultwarden vaults into password-protected encrypted files. Itβs designed for hands-free, secure, and automated backups using the official Bitwarden CLI.
- π Securely exports your vault using your Bitwarden credentials
- π Supports both interval-based and cron-based backup scheduling
- πΎ Password-protected backup files using AES encryption
- π§Ή Automated Cleanup: Automatically deletes old backups based on a configurable retention period.
- β¨ Two Encryption Modes: Choose between Bitwarden's native encrypted format or a portable, standard AES-256-GCM encrypted format.
- π Works with both Bitwarden Cloud and self-hosted Bitwarden/Vaultwarden
- π³ Runs fully containerized β no setup or local dependencies required
You can run BackVault directly using the published Docker image, no build required.
You can use either the GitHub Registry image (ghcr.io/mvfc/backvault) or the Docker Hub image (mvflc/backvault).
All tags up from v2.0.0 are multi-architecture images and can be deployed to Linux/AMD64, Linux/ARM64 and Linux/ARM/v7 systems by just pointing to latest or the corresponding version tag.
If you're mounting the db to a NFS mount, make sure your NFS server has the export configured with no_root_squash and your clients mounts with local_lock=all,sync,intr.
docker run -d \
--name backvault \
-e BW_SERVER="https://vault.yourdomain.com" \
-e BACKUP_ENCRYPTION_MODE="raw" \
-e BACKUP_INTERVAL_HOURS=12 \
-e TZ="Europe/Amsterdam" \
-v /path/to/backup:/app/backups \
-v /path/to/db:/app/db \
-p 8080:8080 \
mvflc/backvault:latestπ Important: The container uses the official Bitwarden CLI internally. Your credentials are only used to generate the export β they are never stored persistently and never sent anywhere else.
BackVault now includes a secure one-time web-based setup UI that replaces the need to pass sensitive credentials via environment variables. When you start the container for the first time, it will:
- Detect that no secure configuration exists yet.
- Launch a local-only setup UI (FastAPI) on port
8080. - Prompt you to enter your Bitwarden credentials and backup password.
- Encrypt and store them in an SQLCipher-encrypted SQLite database, using a generated pragma key.
- Securely store the encryption key and database inside the container volume, accessible only to the containerβs internal user.
Once setup is complete:
- The UI automatically shuts down.
- The container transitions into normal mode and begins scheduled backups.
- All sensitive data is encrypted at rest β no plaintext secrets remain.
You can safely restart or update the container later without re-entering credentials, as long as the /app/db volume persists.
π§© Tip: You can mount the
/app/dbdirectory to your host if you want to persist encrypted credentials across container updates.
The new version of BackVault is built around principle of least privilege and container-isolated secrets:
- π§± Non-root container: The service runs under an unprivileged user (Default
UID 1000). - π Encrypted credential store: All secrets (Bitwarden credentials, file encryption key and master password) are stored in an SQLCipher database using AES-256 encryption.
- π No plaintext environment secrets: You no longer need to define sensitive values like
BW_PASSWORDorBW_CLIENT_SECRETas environment variables. - πΆοΈ Ephemeral setup interface: The setup UI is automatically destroyed after configuration to minimize attack surface and idle resource usage.
Together, these changes make BackVault one of the most secure self-hosted Bitwarden backup utilities available β suitable even for multi-user and shared-host environments.
Hereβs how to set it up with Docker Compose for easy management:
services:
backvault:
image: mvflc/backvault:latest
container_name: backvault
restart: unless-stopped
environment:
BW_SERVER: "https://vault.yourdomain.com"
BACKUP_ENCRYPTION_MODE: "raw" # Use 'bitwarden' for the default format
BACKUP_INTERVAL_HOURS: 12
NODE_TLS_REJECT_UNAUTHORIZED: 0
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam # Set to your timezone according to this list https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
volumes:
- ./backups:/app/backups
- ./db:/app/db
ports:
- "8080:8080"Then run:
docker compose up -dBackVault will automatically:
- Log in to your Bitwarden/Vaultwarden instance
- Export your vault
- Encrypt it using the chosen file password
- Store the backup in
/app/backups(mounted to your host directory) - Logout after every backup
| Variable | Description | Required | Example |
|---|---|---|---|
BW_SERVER |
Bitwarden or Vaultwarden server URL | β | https://vault.example.com |
BACKUP_INTERVAL_HOURS |
Alternative to cron expression (integer hours) | β | 12 |
BACKUP_ENCRYPTION_MODE |
bitwarden (default) or raw for portable AES-256-GCM encryption. |
β | raw |
RETAIN_DAYS |
Days to keep backups. 7 by default. Set to 0 to disable cleanup. |
β | 7 |
CRON_EXPRESSION |
Cron string to schedule backups | β | 0 */12 * * * |
NODE_TLS_REJECT_UNAUTHORIZED |
Set to 0 for self-signed certs |
β | 0 |
TZ |
Timezone for the container according to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | β | UTC |
PUID |
BackVault supports two encryption modes, set by the BACKUP_ENCRYPTION_MODE environment variable. The decryption method depends on which mode was used to create the backup.
This mode uses Bitwarden's native encrypted JSON format. It's secure but proprietary, meaning you must use the Bitwarden CLI to decrypt it.
How to Decrypt:
-
Install the official Bitwarden CLI: bitwarden.com/help/cli/
-
Config the CLI to point to your server with
bw config server. -
Log in using
bw login. -
Run the
importcommand. This will decrypt the file and import it into your vault.# This command decrypts the file and imports it into a vault. bw import bitwardenjson /path/to/backup.encYou will be prompted to enter your encryption password before the import can complete.
This method can be used to restore your vault into the same or a different Bitwarden account. The encryption is self-contained.
This mode exports the vault as raw JSON and then encrypts it in-memory using a standard, portable format: AES-256-GCM with a key derived using PBKDF2-SHA256.
The main advantage is that you do not need the Bitwarden CLI to decrypt your data, making it ideal for disaster recovery. You can use standard tools like Python or OpenSSL.
File Structure:
The resulting .enc file contains: [16-byte salt][12-byte nonce][encrypted data + 16-byte auth tag]
How to Decrypt (Python Script):
Here is a simple Python script to decrypt the file. You only need the cryptography library.
- Save the code below as
decrypt.py. - Install the dependency:
pip install cryptography. - Run the script:
python decrypt.py /path/to/backup.enc "YOUR_FILE_PASSWORD"
# decrypt.py
import os
import sys
from getpass import getpass
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidTag
SALT_SIZE = 16
KEY_SIZE = 32
PBKDF2_ITERATIONS = 600000
def decrypt_data(encrypted_data: bytes, password: str) -> bytes:
salt = encrypted_data[:SALT_SIZE]
nonce = encrypted_data[SALT_SIZE:SALT_SIZE+12]
ciphertext_with_tag = encrypted_data[SALT_SIZE+12:]
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=KEY_SIZE,
salt=salt,
iterations=PBKDF2_ITERATIONS
)
key = kdf.derive(password.encode("utf-8"))
aesgcm = AESGCM(key)
return aesgcm.decrypt(nonce, ciphertext_with_tag, None)
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: python {sys.argv[0]} <encrypted_file> [password]")
sys.exit(1)
file_path = sys.argv[1]
password = sys.argv[2] if len(sys.argv) > 2 else getpass("Enter backup password: ")
if file_path.endswith('.enc'):
output_path = file_path[:-4] + '.json'
else:
output_path = file_path + '.json'
try:
with open(file_path, "rb") as f:
encrypted_contents = f.read()
decrypted_json = decrypt_data(encrypted_contents, password)
with open(output_path, "wb") as f:
f.write(decrypted_json)
print(f"Decryption successful. Output saved to: {output_path}", file=sys.stderr)
except InvalidTag:
print("Decryption failed: Invalid password or corrupted file.", file=sys.stderr)
except Exception as e:
print(f"An error occurred: {e}", file=sys.stderr)- Store your backup file password securely β itβs required for restoring backups.
- You can run this container alongside Vaultwarden on the same host or a separate machine.
- Combine with tools like
resticorrcloneto push backups to cloud storage.
To update to the latest version:
docker pull mvflc/backvault:latestIf using docker compose:
docker compose pull
docker compose up -dThis project is licensed under the AGPL-3.0 License. See LICENSE for details.
BackVault is provided "as is" and without any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose.
The maintainer and contributors assume no liability for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
You are solely responsible for verifying the integrity and restorability of your backups. Use of this software is entirely at your own risk.
Pull requests and issue reports are welcome! Feel free to open a PR or discussion on GitHub.
Some people asked me if they could buy me a coffee or something so I set up a BuyMeACoffee. But please don't feel obligated to, I do this on my free time because I enjoy it, but any support is appreciated and help me get more free time to devote to the codebase.
BackVault β secure, automated, encrypted vault backups.