Skip to content

CLI Reference

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.

Entry Points

CLI shortcuts (recommended):

bash
backupctl <command>               # production
backupctl-dev <command>           # development

Install these with ./scripts/install-cli.sh. See Installation → CLI Shortcuts.

Docker exec (without shortcuts):

bash
docker exec backupctl node dist/cli.js <command>          # production
docker exec backupctl-dev npx ts-node -r tsconfig-paths/register src/cli.ts <command>  # dev

All examples in this document use the shorthand backupctl for brevity.

Global Option: --verbose / -v

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.

bash
backupctl -v health
backupctl --verbose run myproject --dry-run

Without 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.


Table of Contents

  • 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

run

Trigger a backup for a single project or all enabled projects. Supports dry run mode for pre-flight validation without executing the actual backup.

Syntax

backupctl run <project> [--dry-run]
backupctl run --all

Arguments & Options

Argument / OptionRequiredDescription
<project>Yes (unless --all)Project name as defined in config/projects.yml
--dry-runNoValidate config and connectivity without executing backup
--allNoRun backups for all enabled projects sequentially

Exit Codes

CodeMeaning
0Backup completed successfully
1Backup failed
2Backup already in progress (lock held)
3Configuration validation error
4Connectivity error (DB, SSH, restic)
5Partial success (with --all: some projects succeeded, some failed)

Examples

Dry run — all checks pass:

backupctl run --dry-run

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:

backupctl run

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

status

Display backup status for all projects or detailed history for a single project.

Syntax

backupctl status
backupctl status <project> [--last <n>]

Arguments & Options

Argument / OptionRequiredDescription
<project>NoShow detailed history for a specific project
--last <n>NoNumber of recent runs to display (default: 10)

Exit Codes

CodeMeaning
0Status retrieved successfully
1Failed to retrieve status
3Unknown project name

Examples

All projects summary:

backupctl status

Single project history:

backupctl status

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

health

Run comprehensive health checks against all infrastructure dependencies. No arguments required.

Syntax

backupctl health

Arguments & Options

None.

Exit Codes

CodeMeaning
0All checks passed
1One or more checks failed
4Connectivity error (DB, SSH, or restic unreachable)

Examples

Healthy system:

backupctl health

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

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.

Syntax

backupctl restore <project> <snapshot-id> <target-path> [--only db|assets] [--decompress] [--guide]

Arguments & Options

Argument / OptionRequiredDescription
<project>YesProject name
<snapshot-id>YesRestic snapshot ID (short hash or latest)
<target-path>YesDirectory to extract files into
--only dbNoRestore only the database dump
--only assetsNoRestore only asset directories
--decompressNoDecompress dump file after restore
--guideNoPrint database import instructions after restore

Exit Codes

CodeMeaning
0Restore completed successfully
1Restore failed
3Configuration error (unknown project)
4Connectivity error (restic repo unreachable)

Examples

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

snapshots

List restic snapshots for a project. Displays snapshot ID, timestamp, tags, and size.

Syntax

backupctl snapshots <project> [--last <n>]

Arguments & Options

Argument / OptionRequiredDescription
<project>YesProject name
--last <n>NoNumber of recent snapshots to display (default: 20)

Exit Codes

CodeMeaning
0Snapshots listed successfully
1Failed to list snapshots
3Unknown project name
4Restic repo unreachable

Examples

Combined snapshot mode:

backupctl snapshots

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

prune

Manually trigger restic prune for a project or all projects. Applies the retention policy defined in the project's YAML config.

Syntax

backupctl prune <project>
backupctl prune --all

Arguments & Options

Argument / OptionRequiredDescription
<project>Yes (unless --all)Project name
--allNoPrune all enabled projects sequentially

Exit Codes

CodeMeaning
0Prune completed successfully
1Prune failed
3Unknown project name
4Restic repo unreachable
5Partial success (with --all)

Examples

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

logs

Query the audit trail for a project's backup history.

Syntax

backupctl logs <project> [--last <n>] [--failed]

Arguments & Options

Argument / OptionRequiredDescription
<project>YesProject name
--last <n>NoNumber of recent log entries (default: 20)
--failedNoShow only failed backup runs

Exit Codes

CodeMeaning
0Logs retrieved successfully
1Failed to retrieve logs
3Unknown project name
4Audit DB unreachable

Examples

Recent logs:

backupctl 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.

config

Manage project configuration: validate syntax, display resolved config, reload from disk, and import GPG keys.

Syntax

backupctl config validate
backupctl config show <project>
backupctl config reload
backupctl config import-gpg-key <file>

Subcommands

SubcommandDescription
validateCheck all project configs for syntax and semantic errors
show <project>Display the fully resolved config for a project (secrets masked)
reloadReload config/projects.yml and .env without restarting the container
import-gpg-key <file>Import a GPG public key for encryption

Exit Codes

CodeMeaning
0Operation successful
1Operation failed
3Configuration validation error (with validate)

Examples

Validate — all valid:

backupctl config validate

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):

backupctl config show

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.

cache

View or clear the restic cache for a project. Useful when restic operations are slow or the cache is corrupted.

Syntax

backupctl cache <project> [--clear]
backupctl cache --clear-all

Arguments & Options

Argument / OptionRequiredDescription
<project>Yes (unless --clear-all)Project name
--clearNoClear the cache for the specified project
--clear-allNoClear cache for all projects

Exit Codes

CodeMeaning
0Operation successful
1Operation failed
3Unknown project name

Examples

Show cache info:

backupctl cache

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

restic

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.

Syntax

backupctl restic <project> <cmd> [args...]

Arguments & Options

Argument / OptionRequiredDescription
<project>YesProject name (used to resolve repo path and credentials)
<cmd>YesRestic subcommand to execute
[args...]NoAdditional arguments passed through to restic

Exit Codes

CodeMeaning
0Restic command succeeded
1Restic command failed
3Unknown project name
4Repository unreachable

Examples

List snapshots:

backupctl restic snapshots

Check repository integrity:

backupctl restic check

Repository statistics:

backupctl restic stats

List files in latest snapshot:

backupctl restic ls latest

Find a specific file across snapshots:

backupctl restic find

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.

network

Manage Docker network connectivity for the backupctl container. Connects the container to project-specific Docker networks so it can reach database containers by hostname.

Syntax

backupctl network connect [project]

Subcommands

SubcommandDescription
connectConnect 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.

Prerequisites

The Docker socket must be mounted and the container must have permission to access it. Add both to docker-compose.yml:

yaml
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.

Exit Codes

CodeMeaning
0All connections successful (or already connected)
1All connections failed
5Partial success (some connected, some failed)

Examples

Connect to all project networks:

backupctl network connect

Connect to a specific project:

backupctl network connect single

Network does not exist:

backupctl network connect failed


upgrade

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.

Syntax

backupctl upgrade

Behavior

  1. Clears any cached upgrade information
  2. Queries the GitHub releases for the latest version
  3. Compares the latest release against the installed version
  4. If an update is available, shows the release URL and upgrade instructions
  5. If already on the latest version, confirms it

Exit Codes

CodeMeaning
0Check completed successfully
4Connectivity error (GitHub API unreachable)

Examples

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.

Automatic Upgrade Notifications

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:

bash
backupctl-manage.sh upgrade

This pulls the latest code, rebuilds the container, runs migrations, and clears the upgrade check cache.


Global Behaviors

Exit Codes

All commands follow a consistent exit code scheme:

CodeMeaningCommands
0SuccessAll commands
1General failureAll commands
2Backup already in progress (lock held)run
3Configuration validation errorrun, status, restore, snapshots, prune, logs, config, cache, restic
4Connectivity error (DB, SSH, restic)run, health, restore, snapshots, prune, logs, restic, upgrade
5Partial successrun --all, prune --all

Use exit codes for scripting:

bash
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" ;;
esac

Concurrency Model

backupctl 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 code 2.
  • run --all: Projects are backed up sequentially in the order they appear in config/projects.yml. If one project fails, the next project still runs. Exit code 5 indicates partial success.
  • Stale locks: If the container crashes mid-backup, RecoverStartupUseCase cleans up orphaned .lock files on the next start.

Log Output

  • Console: All commands write structured output to stdout. Errors go to stderr.
  • Verbose mode: Add -v or --verbose to 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.log with daily rotation.
  • Log level: Controlled by the LOG_LEVEL environment variable (default: info). The -v flag overrides this to debug for the current invocation.
  • Max size / rotation: Controlled by LOG_MAX_SIZE (default: 10m) and LOG_MAX_FILES (default: 5).

Timezone

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.


Getting Help

What's Next

  • Understand the backup pipelineBackup Flow explains each of the 11 steps.
  • Recover from a snapshotRestore Guide covers browsing snapshots and importing dumps.
  • Host managementBash Scripts documents deploy.sh and backupctl-manage.sh.
  • Quick commandsCheatsheet for copy-paste daily operations.

Released under the MIT License.