Backups
How to backup the database, storage volume, and encryption secrets for Pro self-hosted — SQLite, PostgreSQL, uploads, and the install-generated .env.
Backing Up the Pro Self-Hosted Database
Password Pusher Pro self-hosted uses different databases depending on your plan:
| Plan | Database |
|---|---|
| Starter | SQLite3 |
| Advanced | SQLite3 |
| Enterprise | PostgreSQL |
Follow the section that matches your deployment. In Docker setups, the database file or data directory is typically on a volume — ensure your backup process can access that path (e.g. run backup from inside the container or from the host if the volume is bind-mounted).
Backup set (what you must keep)
A database dump alone is not a full restore. Keep this set together, off the app host:
| Piece | Starter / Advanced | Enterprise | Why |
|---|---|---|---|
.env |
Required | Required | Encryption and application secrets. Without it, restored data cannot be decrypted. |
Storage volume (pwpush-pro-data → /opt/PasswordPusher/storage) |
Required | Required if using local file storage | SQLite files (non-Enterprise) and uploaded files. |
| PostgreSQL | — | Required | App data lives in Postgres, not in the storage volume. |
docker-compose.yml |
If you customized it | If you customized it | Recreates ports, volumes, and image tag. |
If Admin → Settings → File Storage is S3, GCS, Azure Blob, or similar, file bytes live in that bucket. Back up or version the bucket as well; the storage volume then holds little or no upload data.
Default Compose uses a named volume. Named volumes sit in Docker’s volume directory, so host-disk backups often miss them unless you back the volume up explicitly (methods below).
Storage volume (uploads and local data)
The default install mounts:
volumes:
- pwpush-pro-data:/opt/PasswordPusher/storage
That directory holds:
- File uploads when local storage is selected
- SQLite databases on Starter and Advanced (
storage/db/production.sqlite3and related files) - Application temp files
Enterprise: still back up this volume if you store files locally. Postgres is a separate volume (pwpush-pro-postgres-data) — use PostgreSQL for the database.
Method 1: docker compose exec (recommended while running)
From the install directory (service name pwpush-pro):
docker compose exec pwpush-pro tar czf /tmp/storage-backup-$(date +%Y%m%d).tar.gz -C /opt/PasswordPusher storage
docker compose cp pwpush-pro:/tmp/storage-backup-$(date +%Y%m%d).tar.gz ./storage-backup-$(date +%Y%m%d).tar.gz
docker compose exec pwpush-pro rm /tmp/storage-backup-$(date +%Y%m%d).tar.gz
Copy that archive off the server. For Starter/Advanced this includes SQLite and files, but a live tar of SQLite can be slightly inconsistent. For production, also take a SQLite .backup (or stop the app briefly, then tar).
Method 2: volume helper if the container is stopped
CONTAINER_ID=$(docker compose ps -aq pwpush-pro)
docker run --rm \
--volumes-from "$CONTAINER_ID" \
-v "$(pwd)":/backup \
alpine tar czf /backup/storage-backup-$(date +%Y%m%d).tar.gz -C /opt/PasswordPusher storage
Method 3: bind mount on the host
If docker-compose.yml mounts a host path instead of a named volume, archive that path directly:
tar czf storage-backup-$(date +%Y%m%d).tar.gz /host/path/to/storage
Check with docker compose config or the volumes: section of docker-compose.yml.
SQLite (Starter & Advanced)
These methods create a consistent snapshot of your SQLite database, suitable for production and safe to run while the application is in use.
1. Online Backup API / .backup (recommended)
The safest and most official way to get a consistent snapshot while the database may still be in use.
From inside the app container (Docker Compose):
docker compose exec pwpush-pro sqlite3 /opt/PasswordPusher/storage/db/production.sqlite3 ".backup '/tmp/backup-$(date +%Y%m%d).db'"
docker compose cp pwpush-pro:/tmp/backup-$(date +%Y%m%d).db ./backup-$(date +%Y%m%d).db
docker compose exec pwpush-pro rm /tmp/backup-$(date +%Y%m%d).db
A .backup of the SQLite file is a consistent DB snapshot. It does not include uploaded files — keep a storage volume archive as well (or tar the whole storage directory after a short stop).
In Python (e.g. for a backup script or cron job):
import sqlite3
from datetime import datetime
def backup_sqlite(db_path: str, backup_dir: str):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{backup_dir}/backup_{timestamp}.db"
source = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) # read-only is safest
dest = sqlite3.connect(backup_path)
with dest:
source.backup(dest) # online backup API
source.close()
dest.close()
print(f"Backup created: {backup_path}")
Advantages: Consistent snapshot with other connections writing; no need to stop the app; fast on local storage.
2. VACUUM INTO (production / defragmented backup)
Good when you also want a smaller, defragmented copy. Requires SQLite 3.27+.
sqlite3 /path/to/production.sqlite3 "VACUUM INTO '/backups/pwpush-2026-02-25-vacuumed.db';"
When to prefer over .backup: You want a smaller backup file, you already run periodic VACUUM, or you have high write concurrency.
3. Logical backup (.dump — SQL script)
Creates a portable, plain-text SQL file (schema + data).
sqlite3 /path/to/production.sqlite3 .dump > full-dump-$(date +%Y-%m-%d).sql
# Data only (no schema):
sqlite3 /path/to/production.sqlite3 .dump --data-only > data-only.sql
Best for: Moving between SQLite versions, auditing, long-term archives. Downsides: Slower to create and restore; larger unless gzipped.
PostgreSQL (Enterprise)
Enterprise deployments use PostgreSQL. Use logical backups for portability and simplicity, or physical/base backups if you need point-in-time recovery.
1. Logical backup with pg_dump (recommended for most)
Creates a single file (custom, directory, or plain SQL). Run from a host that can connect to your Postgres (e.g. app container or admin host).
Custom format (compressed, flexible restore):
pg_dump -Fc -h <host> -U <user> -d <database_name> -f pwpush-$(date +%Y-%m-%d).dump
Plain SQL (human-readable, restorable with psql):
pg_dump -h <host> -U <user> -d <database_name> > pwpush-$(date +%Y-%m-%d).sql
Restore:
# Custom format
pg_restore -d <database_name> pwpush-2026-02-25.dump
# Plain SQL
psql -h <host> -U <user> -d <database_name> < pwpush-2026-02-25.sql
Use the same database name and credentials as your Pro app configuration.
From Compose (bundled db service):
docker compose exec db pg_dump -U pwpush-pro -Fc -d pwpush-pro -f /tmp/pwpush-$(date +%Y%m%d).dump
docker compose cp db:/tmp/pwpush-$(date +%Y%m%d).dump ./pwpush-$(date +%Y%m%d).dump
Adjust user/database if you changed them in .env. Also archive pwpush-pro-data if file uploads are on local disk.
2. Continuous backup and point-in-time recovery (PITR)
For minimal data loss, use PostgreSQL’s WAL archiving plus a base backup:
- Base backup (e.g.
pg_basebackupor a consistentpg_dump). - WAL archiving — set
archive_mode=onandarchive_commandso WAL segments are copied to safe storage (e.g. S3, NFS).
Then you can restore to any point in time using the base backup plus replayed WAL. Configure this in your PostgreSQL server (or managed service) and test restores regularly.
3. Managed PostgreSQL (e.g. RDS, Cloud SQL)
If your Enterprise deployment uses a managed Postgres service, use its built-in backup and snapshot features and follow the provider’s restore and PITR documentation.
Best Practices (both SQLite and PostgreSQL)
| Practice | Why it matters | Recommendation |
|---|---|---|
| Prefer online/safe methods | .backup / VACUUM INTO for SQLite; pg_dump for Postgres |
Use by default |
| 3-2-1 rule | 3 copies, 2 different media types, 1 off-site | Critical for production |
| Automate + timestamp | Daily or hourly depending on how often data changes | Strong |
| Test restores | Backups are useless if restores fail | Mandatory — use the restore checklist |
| Include the storage volume | Named volumes are not in ordinary host backups; they hold SQLite and/or uploads | Same schedule as the database |
| Store off the same disk | Protects against disk failure and ransomware | Very strong |
| Compress | Use gzip or zstd — databases compress well |
Recommended |
| Encrypt if sensitive | Use age, rclone crypt, or provider encryption | If PII/financial |
| Don’t copy live WAL files manually (SQLite) | Risk of corruption — use SQLite tools only | Never |
Quick decision guide:
- Starter/Advanced (SQLite), simple setup → Storage-volume tar +
.envoff-site (includes SQLite and local files). - Starter/Advanced, production → SQLite
.backup(orVACUUM INTO) plus a storage-volume tar for uploads +.env; consider Litestream for the database. - Enterprise (PostgreSQL) → Scheduled
pg_dump(or managed backups) + storage-volume tar if files are local +.env; enable WAL archiving and PITR if you need minimal RPO.
Encryption keys and .env (all plans)
The install command you run after purchase (curl ... install.sh | sh) downloads docker-compose.yml and a newly generated .env file into your install directory. That .env is created with fresh secrets used for encryption and application security. Without those values, a database backup alone cannot decrypt push payloads or restore a working app—treat .env as part of your backup set.
What to back up
- The whole
.envfile from the directory where you rundocker compose(same folder asdocker-compose.yml). It holds the generated keys and any other install-time configuration the script wrote. - Optionally
docker-compose.ymlif you customize it—so you can recreate the stack exactly.
Do not rely on only backing up volumes or the DB file if you might ever need to restore on a new host or after losing the install directory.
Best practices
| Practice | Why |
|---|---|
Copy .env off the server |
Disk failure or ransomware on the host can wipe both DB and .env if they’re only on that machine. |
| Store with the same rigor as the database | 3-2-1, off-site, encrypted at rest (e.g. password manager vault, encrypted backup, or secrets manager)—not in email or chat. |
Never commit .env to git |
Prevents accidental leaks via repos or CI logs. |
| Restrict file permissions | On the server, keep .env readable only by the account that runs Compose (e.g. chmod 600 .env). |
| Document where backups live | During a restore, you need database + storage volume (local files) + .env (and compose file if customized). |
| Rotate keys only with a plan | Changing encryption secrets without re-encrypting data can make existing ciphertext unreadable—follow product guidance before rotating. |
Restore checklist
Practice this on a spare host or after a snapshot — before you need it.
Do not run docker compose down -v. The -v flag deletes named volumes.
- Install directory — Same layout as production:
docker-compose.ymlplus the same.env(keys must match the backup). - Create volumes —
docker compose up -dthendocker compose stopso named volumes exist but nothing is writing. - Restore data
-
Starter/Advanced: Unpack the storage archive into the volume (tarball was created with
-C /opt/PasswordPusher storage):CONTAINER_ID=$(docker compose ps -aq pwpush-pro) docker run --rm \ --volumes-from "$CONTAINER_ID" \ -v "$(pwd)":/backup \ alpine tar xzf /backup/storage-backup-YYYYMMDD.tar.gz -C /opt/PasswordPusherIf you used SQLite
.backupinstead of a full volume tar, copy that file tostorage/db/production.sqlite3and still restore uploaded files from a storage archive. - Enterprise: Restore Postgres (
pg_restore/psql, or restorepwpush-pro-postgres-data) and restorepwpush-pro-dataif you used local file storage. PointDATABASE_URLat the restored database. - Cloud file storage: Confirm bucket credentials in Admin settings still reach the same bucket.
-
- Start —
docker compose up -d - Health —
docker compose psshould show healthy;curl -f http://127.0.0.1/up(or your published HTTP port) returns 200. See Operations — Healthcheck. - Smoke test — Sign in as an admin. Open a known push. If you use file pushes, download an attachment.
- If payloads look empty or decrypt fails —
.envdoes not match this database. Stop, restore the correct.env, start again.
For background on what the keys protect, see Application encryption (OSS doc; concepts align with how Pro uses secrets at install time).
See Also
- How it works — Install flow;
.envcreated byinstall.sh - Overview — Pro Self-Hosted plans and features
- Operations — Updates,
/uphealthcheck, logs - Docker volumes — Where data lives in container deployments
- Migrate from OSS to Pro — Deployment and configuration context