backupctl provides 16 commands for managing backups, restores, health checks, configuration, networking, upgrades, and direct restic access. Every command returns structured exit codes suitable for scripting and CI/CD pipelines.
CLI shortcuts (recommended):
backupctl <command> # production
backupctl-dev <command> # developmentInstall these with ./scripts/install-cli.sh. See Installation → CLI Shortcuts.
Docker exec (without shortcuts):
docker exec backupctl node dist/cli.js <command> # production
docker exec backupctl-dev npx ts-node -r tsconfig-paths/register src/cli.ts <command> # devAll examples in this document use the shorthand backupctl for brevity.
Add -v or --verbose to any command to see detailed NestJS bootstrap logs, module initialization, and debug-level messages. Useful for diagnosing slow startup or connectivity issues.
backupctl -v health
backupctl --verbose run myproject --dry-runWithout verbose, the CLI only shows warnings and errors during bootstrap. With verbose, you see every step: module loading, DB connection, GPG key imports, notifier registration, etc.
- run — trigger backup or simulate with dry run
- status — backup status overview
- health — system health checks
- restore — restore files from snapshot
- snapshots — list restic snapshots
- prune — manual restic prune
- logs — query audit logs
- config — validate, show, reload, import GPG keys
- cache — restic cache management
- restic — restic passthrough
- network — Docker network management
- Global Behaviors — exit codes, concurrency, logging
Trigger a backup for a single project or all enabled projects. Supports dry run mode for pre-flight validation without executing the actual backup.
backupctl run <project> [--dry-run]
backupctl run --all
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes (unless --all) |
Project name as defined in config/projects.yml |
--dry-run |
No | Validate config and connectivity without executing backup |
--all |
No | Run backups for all enabled projects sequentially |
| Code | Meaning |
|---|---|
0 |
Backup completed successfully |
1 |
Backup failed |
2 |
Backup already in progress (lock held) |
3 |
Configuration validation error |
4 |
Connectivity error (DB, SSH, restic) |
5 |
Partial success (with --all: some projects succeeded, some failed) |
Dry run — all checks pass:
Dry run — failure detected:
$ backupctl run vinsware --dry-run
=== Dry Run: vinsware ===
Validating config and connectivity without executing backup.
✅ Config loaded — Project "vinsware" configuration is valid
✅ Database dumper — Adapter found for database type: postgres
✅ Notifier — Adapter found for notification type: slack
❌ Restic repo — Cannot access repository at /backups/vinsware: repository does not exist
✅ Disk space — 42.0 GB free (minimum: 5 GB)
✅ GPG key — Key found for recipient: vinsware-backup@company.com
⚠️ Asset paths — 1 of 2 path(s) missing: /data/vinsware/assets
❌ 2 check(s) failed — vinsware is NOT ready for backup.
Single project backup:
All projects — mixed results:
$ backupctl run --all
[2026-03-18 01:00:00] Running backups for 3 enabled project(s)...
[2026-03-18 01:00:00] [1/3] vinsware — starting...
[2026-03-18 01:01:19] [1/3] vinsware — ✅ completed in 1m 19s
[2026-03-18 01:01:20] [2/3] project-x — starting...
[2026-03-18 01:02:45] [2/3] project-x — ✅ completed in 1m 25s
[2026-03-18 01:02:46] [3/3] project-y — starting...
[2026-03-18 01:02:52] [3/3] project-y — ❌ failed at stage Dump: connection refused
=== Summary ===
✅ vinsware — success (1m 19s)
✅ project-x — success (1m 25s)
❌ project-y — failed (Dump: connection refused)
⚠️ Partial success: 2 of 3 projects completed. Exit code: 5
Lock collision:
$ backupctl run vinsware
❌ Backup already in progress for vinsware.
Lock held since 2026-03-18 00:00:05 (PID: 1234)
Use "backupctl status vinsware" to check progress.
Exit code: 2
Display backup status for all projects or detailed history for a single project.
backupctl status
backupctl status <project> [--last <n>]
| Argument / Option | Required | Description |
|---|---|---|
<project> |
No | Show detailed history for a specific project |
--last <n> |
No | Number of recent runs to display (default: 10) |
| Code | Meaning |
|---|---|
0 |
Status retrieved successfully |
1 |
Failed to retrieve status |
3 |
Unknown project name |
All projects summary:
Single project history:
In-progress backup:
$ backupctl status vinsware
=== vinsware — Current Status ===
🔄 Backup in progress (run: a1b2c3d4)
Started: 2026-03-18 00:00:05 (35s ago)
Current stage: Sync (6/11)
Lock held by PID: 1234
Run comprehensive health checks against all infrastructure dependencies. No arguments required.
backupctl health
None.
| Code | Meaning |
|---|---|
0 |
All checks passed |
1 |
One or more checks failed |
4 |
Connectivity error (DB, SSH, or restic unreachable) |
Healthy system:
Degraded system:
$ backupctl health
=== System Health Check ===
✅ Audit DB — Connected (PostgreSQL 16.2, 142 records)
❌ Disk space — 3.2 GB free (minimum: 5 GB)
✅ SSH — Connection to u123456.your-storagebox.de successful
✅ Restic repo (vinsware) — Repository OK, 42 snapshots
❌ Restic repo (project-x) — Lock detected, may need unlock
✅ Restic repo (project-y) — Repository OK, 14 snapshots
⚠️ 2 check(s) failed. Run "backupctl restic project-x unlock" for stale locks.
Exit code: 1
Restore files from a restic snapshot to a target directory. Supports selective restore (--only db or --only assets) and provides human-readable import instructions with --guide.
backupctl restore <project> <snapshot-id> <target-path> [--only db|assets] [--decompress] [--guide]
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes | Project name |
<snapshot-id> |
Yes | Restic snapshot ID (short hash or latest) |
<target-path> |
Yes | Directory to extract files into |
--only db |
No | Restore only the database dump |
--only assets |
No | Restore only asset directories |
--decompress |
No | Decompress dump file after restore |
--guide |
No | Print database import instructions after restore |
| Code | Meaning |
|---|---|
0 |
Restore completed successfully |
1 |
Restore failed |
3 |
Configuration error (unknown project) |
4 |
Connectivity error (restic repo unreachable) |
Basic restore:
$ backupctl restore vinsware abc12345 /tmp/restore
Restoring snapshot abc12345 for vinsware...
Source: sftp:u123456@u123456.your-storagebox.de:/backups/vinsware
Target: /tmp/restore
Restoring files...
restored /tmp/restore/vinsware_db_20260318_000032.sql.gz
restored /tmp/restore/uploads/ (1,248 files)
restored /tmp/restore/assets/ (346 files)
✅ Restore complete. 3 items restored to /tmp/restore
Latest snapshot with decompress and guide:
$ backupctl restore vinsware latest /tmp/restore --decompress --guide
Restoring latest snapshot (abc12345) for vinsware...
Source: sftp:u123456@u123456.your-storagebox.de:/backups/vinsware
Target: /tmp/restore
Restoring files...
restored /tmp/restore/vinsware_db_20260318_000032.sql.gz
restored /tmp/restore/uploads/ (1,248 files)
restored /tmp/restore/assets/ (346 files)
Decompressing dump...
vinsware_db_20260318_000032.sql.gz → vinsware_db_20260318_000032.sql (487 MB)
✅ Restore complete.
=== Database Import Guide (postgres) ===
The dump file is a pg_dump custom-format archive. To import:
1. Create the target database (if it doesn't exist):
createdb -h <host> -U <user> vinsware_db
2. Restore the dump:
pg_restore -h <host> -U <user> -d vinsware_db /tmp/restore/vinsware_db_20260318_000032.sql
3. If restoring to an existing database, add --clean to drop objects first:
pg_restore -h <host> -U <user> -d vinsware_db --clean /tmp/restore/vinsware_db_20260318_000032.sql
Note: The dump was originally encrypted with GPG. It was decrypted
automatically during restore. The .sql file is ready for import.
Selective restore — database only:
$ backupctl restore vinsware abc12345 /tmp/restore --only db
Restoring snapshot abc12345 for vinsware (database only)...
Source: sftp:u123456@u123456.your-storagebox.de:/backups/vinsware
Target: /tmp/restore
Restoring files...
restored /tmp/restore/vinsware_db_20260318_000032.sql.gz
✅ Restore complete. Database dump restored to /tmp/restore
Selective restore — assets only:
$ backupctl restore vinsware abc12345 /tmp/restore --only assets
Restoring snapshot abc12345 for vinsware (assets only)...
Source: sftp:u123456@u123456.your-storagebox.de:/backups/vinsware
Target: /tmp/restore
Restoring files...
restored /tmp/restore/uploads/ (1,248 files)
restored /tmp/restore/assets/ (346 files)
✅ Restore complete. Asset directories restored to /tmp/restore
List restic snapshots for a project. Displays snapshot ID, timestamp, tags, and size.
backupctl snapshots <project> [--last <n>]
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes | Project name |
--last <n> |
No | Number of recent snapshots to display (default: 20) |
| Code | Meaning |
|---|---|
0 |
Snapshots listed successfully |
1 |
Failed to list snapshots |
3 |
Unknown project name |
4 |
Restic repo unreachable |
Combined snapshot mode:
Separate snapshot mode:
$ backupctl snapshots project-x --last 5
=== project-x — Restic Snapshots (separate mode) ===
SNAPSHOT DATE TAGS SIZE
aaa11111 2026-03-18 01:31:15 backupctl:db, project:project-x 82.4 MB
bbb22222 2026-03-18 01:31:45 backupctl:assets:/data/projectx/storage, ... 63.1 MB
ccc33333 2026-03-17 01:31:10 backupctl:db, project:project-x 81.9 MB
ddd44444 2026-03-17 01:31:38 backupctl:assets:/data/projectx/storage, ... 62.8 MB
eee55555 2026-03-16 01:31:12 backupctl:db, project:project-x 82.1 MB
Showing 5 of 56 snapshots. Repository size: 2.4 GB
Manually trigger restic prune for a project or all projects. Applies the retention policy defined in the project's YAML config.
backupctl prune <project>
backupctl prune --all
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes (unless --all) |
Project name |
--all |
No | Prune all enabled projects sequentially |
| Code | Meaning |
|---|---|
0 |
Prune completed successfully |
1 |
Prune failed |
3 |
Unknown project name |
4 |
Restic repo unreachable |
5 |
Partial success (with --all) |
Single project:
$ backupctl prune vinsware
Pruning vinsware with retention: keep_daily=7, keep_weekly=4, keep_monthly=0
Removed 3 snapshots
Freed 412.5 MB
✅ Prune complete for vinsware. Repository: 1.4 GB (was 1.8 GB)
All projects:
$ backupctl prune --all
[1/3] vinsware — pruning...
Removed 3 snapshots, freed 412.5 MB ✅
[2/3] project-x — pruning...
Removed 5 snapshots, freed 287.3 MB ✅
[3/3] project-y — pruning...
Removed 1 snapshot, freed 45.0 MB ✅
=== Summary ===
Total removed: 9 snapshots
Total freed: 744.8 MB
Query the audit trail for a project's backup history.
backupctl logs <project> [--last <n>] [--failed]
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes | Project name |
--last <n> |
No | Number of recent log entries (default: 20) |
--failed |
No | Show only failed backup runs |
| Code | Meaning |
|---|---|
0 |
Logs retrieved successfully |
1 |
Failed to retrieve logs |
3 |
Unknown project name |
4 |
Audit DB unreachable |
Recent logs:
Failed runs only:
$ backupctl logs vinsware --last 10 --failed
=== vinsware — Failed Runs ===
RUN ID STARTED DURATION FAILED STAGE ERROR
e7f8a9b0 2026-03-14 00:00:03 45s Dump connection timeout
f1a2b3c4 2026-03-08 00:00:04 2m 10s Sync SSH connection refused
d5e6f7a8 2026-03-01 00:00:05 12s PreHook curl: connection refused
Showing 3 of 3 failed runs.
Manage project configuration: validate syntax, display resolved config, reload from disk, and import GPG keys.
backupctl config validate
backupctl config show <project>
backupctl config reload
backupctl config import-gpg-key <file>
| Subcommand | Description |
|---|---|
validate |
Check all project configs for syntax and semantic errors |
show <project> |
Display the fully resolved config for a project (secrets masked) |
reload |
Reload config/projects.yml and .env without restarting the container |
import-gpg-key <file> |
Import a GPG public key for encryption |
| Code | Meaning |
|---|---|
0 |
Operation successful |
1 |
Operation failed |
3 |
Configuration validation error (with validate) |
Validate — all valid:
Validate — errors found:
$ backupctl config validate
Validating config/projects.yml...
✅ vinsware — valid
❌ project-x — 2 error(s):
• database.password: unresolved variable ${PROJECTX_DB_PASSWORD}
• retention.keep_daily: must be a non-negative integer
✅ project-y — valid
1 of 3 project(s) have errors. Exit code: 3
Show resolved config (secrets masked):
Reload config:
$ backupctl config reload
Reloading configuration...
Loaded 3 project(s) from config/projects.yml
Resolved environment variables from .env
Updated cron schedules
✅ Configuration reloaded. Changes take effect on next backup run.
Import GPG key:
$ backupctl config import-gpg-key /app/gpg-keys/vinsware-backup.pub
Importing GPG key from /app/gpg-keys/vinsware-backup.pub...
Key ID: 0xABCDEF1234567890
User ID: vinsware-backup@company.com
Fingerprint: 1234 5678 ABCD EF01 2345 6789 ABCD EF12 3456 7890
✅ GPG key imported successfully.
View or clear the restic cache for a project. Useful when restic operations are slow or the cache is corrupted.
backupctl cache <project> [--clear]
backupctl cache --clear-all
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes (unless --clear-all) |
Project name |
--clear |
No | Clear the cache for the specified project |
--clear-all |
No | Clear cache for all projects |
| Code | Meaning |
|---|---|
0 |
Operation successful |
1 |
Operation failed |
3 |
Unknown project name |
Show cache info:
Clear single project cache:
$ backupctl cache vinsware --clear
Clearing restic cache for vinsware...
Removed 28.5 MB from /root/.cache/restic/abc123def456
✅ Cache cleared for vinsware.
Clear all caches:
$ backupctl cache --clear-all
Clearing restic cache for all projects...
vinsware — 28.5 MB cleared
project-x — 15.2 MB cleared
project-y — 12.1 MB cleared
✅ Cache cleared for 3 project(s). Total freed: 55.8 MB
Execute restic commands directly against a project's repository. The repository path, password, and SFTP credentials are injected automatically from the project's config — you only supply the restic subcommand and its arguments.
backupctl restic <project> <cmd> [args...]
| Argument / Option | Required | Description |
|---|---|---|
<project> |
Yes | Project name (used to resolve repo path and credentials) |
<cmd> |
Yes | Restic subcommand to execute |
[args...] |
No | Additional arguments passed through to restic |
| Code | Meaning |
|---|---|
0 |
Restic command succeeded |
1 |
Restic command failed |
3 |
Unknown project name |
4 |
Repository unreachable |
List snapshots:
Check repository integrity:
Repository statistics:
List files in latest snapshot:
Find a specific file across snapshots:
Unlock stale locks:
$ backupctl restic vinsware unlock
repository abc12345 opened (version 2, compression auto)
successfully removed 1 locks
Initialize a new repository:
$ backupctl restic vinsware init
created restic repository abc12345 at sftp:u123456@u123456.your-storagebox.de:/backups/vinsware
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
Mount repository for browsing (interactive):
$ backupctl restic vinsware mount /mnt/restic
repository abc12345 opened (version 2, compression auto)
Now serving the repository at /mnt/restic
Use another terminal or file manager to browse the snapshots.
When finished, press Ctrl-C or send SIGINT to quit.
Manage Docker network connectivity for the backupctl container. Connects the container to project-specific Docker networks so it can reach database containers by hostname.
backupctl network connect [project]
| Subcommand | Description |
|---|---|
connect |
Connect the backupctl container to project Docker networks |
connect <project> |
Connect to a specific project's Docker network |
When no project name is given, connect iterates all projects and connects to each one that has a docker_network defined. Projects without docker_network are skipped.
The Docker socket must be mounted and the container must have permission to access it. Add both to docker-compose.yml:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
group_add:
- '${DOCKER_GID:-999}'If the default GID 999 doesn't match your host, check with stat -c '%g' /var/run/docker.sock and set DOCKER_GID in .env. See Network for full setup details.
| Code | Meaning |
|---|---|
0 |
All connections successful (or already connected) |
1 |
All connections failed |
5 |
Partial success (some connected, some failed) |
Connect to all project networks:
Connect to a specific project:
Network does not exist:
Check for available updates and display upgrade instructions. This command queries the GitHub Releases API, compares against the currently installed version, and shows how to upgrade if a newer release exists.
backupctl upgrade
- Clears any cached upgrade information
- Queries the GitHub releases for the latest version
- Compares the latest release against the installed version
- If an update is available, shows the release URL and upgrade instructions
- If already on the latest version, confirms it
| Code | Meaning |
|---|---|
0 |
Check completed successfully |
4 |
Connectivity error (GitHub API unreachable) |
Update available:
$ backupctl upgrade
Current version: v0.1.8
Latest version: v0.2.0
A new version is available!
Release: https://github.com/vineethkrishnan/backupctl/releases/tag/v0.2.0
To upgrade, run on the host machine:
backupctl-manage.sh upgrade
Already up to date:
$ backupctl upgrade
Current version: v0.2.0
Latest version: v0.2.0
You are on the latest version.
backupctl automatically checks for updates on the first CLI command after each deployment. If a newer version is available, a notice appears at the end of the command output:
┌──────────────────────────────────────────────────────┐
│ Update available: v0.1.8 → v0.2.0 │
│ Run on host: backupctl-manage.sh upgrade │
└──────────────────────────────────────────────────────┘
The check result is cached in ${BACKUP_BASE_DIR}/.upgrade-info so subsequent commands read from cache without hitting the GitHub API.
Suppressed when:
- Development mode (
NODE_ENV=development) - Non-interactive output (piped or redirected stderr)
- Opt-out via
BACKUPCTL_NO_UPDATE_CHECK=1 - Scheduled (cron) backups — these use the HTTP entry point, not the CLI
To upgrade after seeing the notice, run on the host machine:
backupctl-manage.sh upgradeThis pulls the latest code, rebuilds the container, runs migrations, and clears the upgrade check cache.
All commands follow a consistent exit code scheme:
| Code | Meaning | Commands |
|---|---|---|
0 |
Success | All commands |
1 |
General failure | All commands |
2 |
Backup already in progress (lock held) | run |
3 |
Configuration validation error | run, status, restore, snapshots, prune, logs, config, cache, restic |
4 |
Connectivity error (DB, SSH, restic) | run, health, restore, snapshots, prune, logs, restic, upgrade |
5 |
Partial success | run --all, prune --all |
Use exit codes for scripting:
backupctl run vinsware
case $? in
0) echo "Backup succeeded" ;;
1) echo "Backup failed" ;;
2) echo "Already running" ;;
3) echo "Config error" ;;
4) echo "Connectivity issue" ;;
esacbackupctl uses per-project file-based locks to prevent concurrent backups of the same project:
- Lock location:
{BACKUP_BASE_DIR}/{project}/.lock - Cron-triggered overlap: If a scheduled backup fires while a previous run is still active, the new run queues behind it and starts when the lock is released.
- CLI-triggered collision: If you manually trigger
backupctl run <project>while a backup is already running, the command rejects immediately with exit code2. run --all: Projects are backed up sequentially in the order they appear inconfig/projects.yml. If one project fails, the next project still runs. Exit code5indicates partial success.- Stale locks: If the container crashes mid-backup,
RecoverStartupUseCasecleans up orphaned.lockfiles on the next start.
- Console: All commands write structured output to stdout. Errors go to stderr.
- Verbose mode: Add
-vor--verboseto any command to see NestJS bootstrap logs, module initialization, DB connections, and debug messages. Without it, the CLI suppresses all bootstrap noise and only shows warnings/errors. - Log files: The background service writes JSON-formatted logs (via Winston) to
{LOG_DIR}/backupctl-YYYY-MM-DD.logwith daily rotation. - Log level: Controlled by the
LOG_LEVELenvironment variable (default:info). The-vflag overrides this todebugfor the current invocation. - Max size / rotation: Controlled by
LOG_MAX_SIZE(default:10m) andLOG_MAX_FILES(default:5).
All timestamps in CLI output, log files, audit records, and file names use the timezone defined by the TIMEZONE environment variable (default: Europe/Berlin). Set this in your .env file to match your operational timezone.
- Command not working? — Troubleshooting covers common errors
- Setup issues? — FAQ for SSH, GPG, Docker networking, and restic problems
- Still stuck? — Report an issue on GitHub
- Understand the backup pipeline — Backup Flow explains each of the 11 steps.
- Recover from a snapshot — Restore Guide covers browsing snapshots and importing dumps.
- Host management — Bash Scripts documents
deploy.shandbackupctl-manage.sh. - Quick commands — Cheatsheet for copy-paste daily operations.

















