Installation
This guide covers deploying backupctl on a fresh server. There are three paths:
- Quick install — one command, pulls a pre-built Docker image (fastest)
- Installation wizard — interactive setup that generates all config files
- Manual setup — step-by-step for full control
Prerequisites
| Requirement | Minimum version | Notes |
|---|---|---|
| Docker | 24+ | Docker Engine with BuildKit |
| Docker Compose | v2 (docker compose) | Plugin or standalone |
| SSH access | — | Hetzner Storage Box with SSH/SFTP enabled |
| GPG | 2.x | Optional — only if using dump encryption |
| Disk space | 5+ GB free | Configurable via HEALTH_DISK_MIN_FREE_GB |
| curl | — | For the quick installer |
The host OS should be Linux (Ubuntu 24.04 recommended). macOS works for local development but production deployments target Linux.
Docker Images
backupctl is published as a Docker image on every release:
# Docker Hub
docker pull vineethnkrishnan/backupctl:latest
# GitHub Container Registry
docker pull ghcr.io/vineethkrishnan/backupctl:latestTags follow semver: latest, 1, 1.0, 1.0.0.
Option A: Quick Install (Recommended)
A single command that pulls the pre-built image, generates docker-compose.yml, downloads management scripts, and sets up the directory structure.
curl -fsSL https://raw.githubusercontent.com/vineethkrishnan/backupctl/main/scripts/get-backupctl.sh | bashCustom install directory:
curl -fsSL https://raw.githubusercontent.com/vineethkrishnan/backupctl/main/scripts/get-backupctl.sh | bash -s -- --dir /opt/backupctlPin to a specific version:
curl -fsSL https://raw.githubusercontent.com/vineethkrishnan/backupctl/main/scripts/get-backupctl.sh | bash -s -- --version 1.0.0After the installer finishes, run the interactive setup wizard:
cd ~/backupctl
bash scripts/install.shThis generates .env and config/projects.yml from your answers, sets up SSH keys, initializes restic repos, and optionally starts the service.
Option B: Installation Wizard (From Source)
Clone the repository and run the interactive setup. This builds the Docker image locally instead of pulling a pre-built one.
git clone https://github.com/vineethkrishnan/backupctl.git && cd backupctl
./scripts/install.shThe wizard walks through the following sections:
1. Prerequisites Check
Verifies that Docker, Docker Compose, git, openssl, and ssh-keygen are installed and accessible.
=== backupctl Installation Wizard ===
[1/10] Checking prerequisites...
✓ Docker 27.3.1
✓ Docker Compose v2.30.3
✓ git 2.43.0
✓ openssl 3.0.13
✓ ssh-keygen (OpenSSH_9.6p1)
All prerequisites met.2. Application Settings
Prompts for core service configuration with sensible defaults.
[2/10] Application settings
APP_PORT [3100]:
TIMEZONE [Europe/Berlin]:
BACKUP_BASE_DIR [/data/backups]:
LOG_LEVEL (debug|info|warn|error) [info]:
HEALTH_DISK_MIN_FREE_GB [5]:3. Audit Database
Configures the PostgreSQL audit database. The password is auto-generated if left blank.
[3/10] Audit database
AUDIT_DB_HOST [backupctl-audit-db]:
AUDIT_DB_PORT [5432]:
AUDIT_DB_NAME [backup_audit]:
AUDIT_DB_USER [audit_user]:
AUDIT_DB_PASSWORD [auto-generated]: ********
Generated password: k8f2...a9d1 (saved to .env)4. Hetzner Storage Box
Configures SSH access, generates a key pair if needed, copies the public key, scans known_hosts, and verifies the connection.
[4/10] Hetzner Storage Box
HETZNER_SSH_HOST: u123456.your-storagebox.de
HETZNER_SSH_USER: u123456
SSH key not found at ./ssh-keys/id_ed25519
Generating Ed25519 key pair... done.
Copy public key to storage box? [Y/n]: Y
Copying... done.
Scanning known_hosts... done.
Testing SSH connection... ✓ Connected.5. Restic
Sets a global restic repository password and retry configuration.
[5/10] Restic configuration
RESTIC_PASSWORD: ********
BACKUP_RETRY_COUNT [3]:
BACKUP_RETRY_DELAY_MS [5000]:6. Notifications
Choose a global notification channel and enter channel-specific settings.
[6/10] Notification defaults
NOTIFICATION_TYPE (slack|email|webhook|none) [slack]: slack
SLACK_WEBHOOK_URL: https://hooks.slack.com/services/T.../B.../xxx
DAILY_SUMMARY_CRON [0 8 * * *]:For email, the wizard prompts for SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_TO, SMTP_FROM, and SMTP_PASSWORD. For webhook, it prompts for WEBHOOK_URL.
7. Encryption
Optionally enables GPG encryption globally and imports key files.
[7/10] Encryption
Enable GPG encryption? [y/N]: y
GPG_RECIPIENT: backup@company.com
Import GPG key file? [y/N]: y
Key file path: /path/to/backup-key.pub.gpg
Copied to ./gpg-keys/backup-key.pub.gpg8. Project Configuration
An interactive loop that builds config/projects.yml one project at a time.
[8/10] Project configuration
Add a project? [Y/n]: Y
Project name: locaboo
Docker network (empty = host/default): locaboo_locaboo-network
Database type (postgres|mysql|mongodb): postgres
Database host: postgres-locaboo
Database port [5432]:
Database name: locaboo_db
Database user: backup_user
Database password: ********
Cron schedule [0 2 * * *]: 0 0 * * *
Timeout minutes (blank for none): 30
Restic repo path [/backups/locaboo]:
Restic password (blank for global):
Snapshot mode (combined|separate) [combined]:
Asset paths (comma-separated, blank for none): /data/locaboo/uploads, /data/locaboo/assets
Enable verification? [Y/n]:
Pre-backup hook (blank for none): curl -s http://locaboo-app:3000/maintenance/on
Post-backup hook (blank for none): curl -s http://locaboo-app:3000/maintenance/off
Notification override? [y/N]:
✓ locaboo added.
Add another project? [Y/n]: N9. File Generation
Generates .env and config/projects.yml from all collected values.
[9/10] Generating configuration files
✓ .env created
✓ config/projects.yml created
✓ Directories created: config/, ssh-keys/, gpg-keys/10. Build and Deploy
Optionally builds the Docker image, starts containers, runs migrations, initializes restic repos, and runs a health check.
[11/13] Docker Setup
Build and start containers now? [Y/n]: Y
Building Docker image... done.
Starting containers... done.
Waiting for audit DB... ready.
Running migrations... done.
Initializing restic repo for locaboo... done.
Running health check...
✓ Audit DB: connected
✓ Restic (locaboo): accessible
✓ Disk: 42 GB free (threshold: 5 GB)
✓ SSH: connected11. CLI Shortcuts
Installs backupctl and backupctl-dev wrapper commands so you can run CLI commands from any directory without the docker exec prefix.
[12/13] CLI Shortcuts
Install CLI shortcuts? [Y/n]: Y
Install location:
1) ~/.local/bin (user only, no sudo)
2) /usr/local/bin (system-wide, requires sudo)
Choose [1]: 1
✔ Installed backupctl → ~/.local/bin/backupctl
✔ Installed backupctl-dev → ~/.local/bin/backupctl-devAfter installation:
backupctl health # instead of: docker exec backupctl node dist/cli.js health
backupctl run locaboo --dry-run # instead of: docker exec backupctl node dist/cli.js run locaboo --dry-run
backupctl-dev health # instead of: scripts/dev.sh cli healthYou can also install shortcuts separately at any time:
./scripts/install-cli.sh # interactive
./scripts/install-cli.sh --user # install to ~/.local/bin
./scripts/install-cli.sh --system # install to /usr/local/bin (sudo)
./scripts/install-cli.sh --uninstall # remove both commandsOption C: Manual Setup
1. Set Up the Project
Using the pre-built image (recommended):
mkdir backupctl && cd backupctl
mkdir -p config ssh-keys gpg-keysOr from source:
git clone https://github.com/vineethkrishnan/backupctl.git && cd backupctl
npm ci
mkdir -p config ssh-keys gpg-keys2. Create docker-compose.yml
If using the pre-built image, create a docker-compose.yml:
services:
backupctl:
container_name: backupctl
image: vineethnkrishnan/backupctl:latest
env_file: .env
environment:
AUDIT_DB_HOST: backupctl-audit-db
ports:
- '${APP_PORT:-3100}:${APP_PORT:-3100}'
volumes:
- ${BACKUP_BASE_DIR:-/data/backups}:/data/backups
- ./config:/app/config:ro
- ./ssh-keys:/home/node/.ssh:ro
- ./gpg-keys:/app/gpg-keys:ro
networks:
- backupctl-network
depends_on:
backupctl-audit-db:
condition: service_healthy
restart: unless-stopped
stop_grace_period: 120s
deploy:
resources:
limits:
memory: 2G
cpus: '2'
backupctl-audit-db:
container_name: backupctl-audit-db
image: postgres:16-alpine
environment:
POSTGRES_DB: ${AUDIT_DB_NAME:-backup_audit}
POSTGRES_USER: ${AUDIT_DB_USER:-audit_user}
POSTGRES_PASSWORD: ${AUDIT_DB_PASSWORD}
volumes:
- backupctl-audit-data:/var/lib/postgresql/data
networks:
- backupctl-network
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${AUDIT_DB_USER:-audit_user} -d ${AUDIT_DB_NAME:-backup_audit}']
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '1'
volumes:
backupctl-audit-data:
networks:
backupctl-network:
driver: bridgeIf using from source, the repository already includes a docker-compose.yml.
3. Configure .env
Copy the example and fill in required values:
cp .env.example .envMinimum required variables:
# Audit DB
AUDIT_DB_PASSWORD=<generate-a-strong-password>
# Hetzner Storage Box
HETZNER_SSH_HOST=u123456.your-storagebox.de
HETZNER_SSH_USER=u123456
# Restic
RESTIC_PASSWORD=<generate-a-strong-password>
# At least one project DB password
LOCABOO_DB_PASSWORD=<db-password>All other variables have sensible defaults. See Configuration for the full reference.
4. Configure projects.yml
Create config/projects.yml with at least one project:
projects:
- name: locaboo
enabled: true
cron: "0 2 * * *"
docker_network: locaboo_locaboo-network # optional — Docker network where DB lives
database:
type: postgres
host: postgres-locaboo
port: 5432
name: locaboo_db
user: backup_user
password: ${LOCABOO_DB_PASSWORD}
restic:
repository_path: backups/locaboo
snapshot_mode: combined
retention:
local_days: 7
keep_daily: 7
keep_weekly: 4
keep_monthly: 05. SSH Key Setup
Generate an SSH key pair, copy the public key to the Hetzner Storage Box, scan the host key, and create an SSH config file:
# Generate key (skip if you already have one)
ssh-keygen -t ed25519 -f ./ssh-keys/id_ed25519 -N ""
# Copy public key to storage box (requires the storage box password once)
cat ./ssh-keys/id_ed25519.pub | ssh -p 23 u123456@u123456.your-storagebox.de install-ssh-key
# Scan host key for non-interactive use
ssh-keyscan -p 23 u123456.your-storagebox.de > ./ssh-keys/known_hostsCreate an SSH config file at ssh-keys/config — this is required for restic to find the correct key, port, and known_hosts:
Host u123456.your-storagebox.de
User u123456
Port 23
IdentityFile /home/node/.ssh/id_ed25519
UserKnownHostsFile /home/node/.ssh/known_hosts
StrictHostKeyChecking accept-newVerify the connection:
ssh -F ./ssh-keys/config -i ./ssh-keys/id_ed25519 -p 23 u123456@u123456.your-storagebox.de lsWARNING
The known_hosts file and config file are both critical. Without them, SSH connections during cron-triggered backups will fail — there is no interactive host key confirmation in non-interactive mode. The UserKnownHostsFile directive in the config ensures restic's SSH subprocess uses the scanned host keys.
6. GPG Keys (Optional)
If you want GPG encryption, place public key files in gpg-keys/:
# Export an existing key
gpg --export --armor backup@company.com > ./gpg-keys/backup.pub.gpg
# Or generate a new key and export it
gpg --full-generate-key
gpg --export --armor backup@company.com > ./gpg-keys/backup.pub.gpgEnable encryption in .env or per-project in projects.yml:
ENCRYPTION_ENABLED=true
GPG_RECIPIENT=backup@company.comKeys are auto-imported into the container's GPG keyring on every startup.
7. Start the Service
Using the pre-built image:
docker compose up -dFrom source:
npm run build
docker compose up -d --build8. Initialize Restic Repos
Each project needs a one-time restic repository initialization. First, create the directory on the storage box via SFTP, then initialize the restic repo.
Step 1 — Create the directory on the storage box:
docker exec -i backupctl sftp -i /home/node/.ssh/id_ed25519 \
-P 23 -o StrictHostKeyChecking=accept-new \
u123456@u123456.your-storagebox.de <<'EOF'
mkdir backups
mkdir backups/locaboo
bye
EOFTIP
Hetzner Storage Box paths must be relative (e.g., backups/locaboo, not /backups/locaboo). The storage box chroots to the user's home directory, so /backups refers to a read-only system path.
Step 2 — Initialize the restic repository:
docker exec backupctl node dist/cli.js restic locaboo initExpected output:
created restic repository at sftp:u123456@u123456.your-storagebox.de:backups/locaboo
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.Repeat both steps for each project defined in projects.yml.
9. Run Health Check
Verify that all systems are connected and operational:
docker exec backupctl node dist/cli.js healthExpected output:
Health Check Results:
Audit DB: ✓ connected
Restic (locaboo): ✓ accessible
Disk space: ✓ 42 GB free (threshold: 5 GB)
SSH: ✓ connected to u123456.your-storagebox.de10. Verify with Dry Run
Run a dry-run backup to validate the full configuration without executing any destructive operations:
docker exec backupctl node dist/cli.js run locaboo --dry-runThis validates config loading, database connectivity, restic repo access, SSH connectivity, disk space, and GPG key availability (if encryption is enabled).
Post-Installation
After installation is complete, verify the system end-to-end:
# Trigger a real backup
docker exec backupctl node dist/cli.js run locaboo
# Check backup status
docker exec backupctl node dist/cli.js status locaboo
# View audit logs
docker exec backupctl node dist/cli.js logs locaboo --last 1
# Confirm snapshots exist on remote storage
docker exec backupctl node dist/cli.js snapshots locaboo --last 1The cron scheduler starts automatically when the container boots. Backups will run on their configured schedules without further intervention.
Updating
Image-based (quick install)
cd ~/backupctl
# Pull latest image
docker compose pull
# Restart with new image
docker compose up -d
# Run pending migrations (if any)
docker exec backupctl node dist/cli.js config reload
# Verify
docker exec backupctl node dist/cli.js healthFrom source (management script)
./scripts/backupctl-manage.sh updateThis pulls the latest code, rebuilds the container, runs any pending migrations, and performs a health check.
From source (manual)
git pull origin main
npm ci
npm run build
docker compose up -d --build
docker exec backupctl npx typeorm migration:run -d dist/db/datasource.js
docker exec backupctl node dist/cli.js healthUninstalling
Stop and Remove Containers
# Stop containers (preserves volumes)
docker compose down
# Stop and remove volumes (deletes audit DB data)
docker compose down -vRemove Local Data
# Remove backup data from host
rm -rf /data/backups
# Remove project directory
cd .. && rm -rf backupctlRemote restic snapshots on the Hetzner Storage Box are not affected by uninstalling the local service. To remove remote data, connect to the storage box directly and delete the repository directories.
Getting Help
If you run into issues during installation:
- Check the Troubleshooting guide for common errors
- Review the FAQ for setup-specific issues (SSH, GPG, restic, Docker networking)
- Run
backupctl healthandbackupctl run <project> --dry-runto diagnose connectivity - Report an issue on GitHub if you're stuck
What's Next
- Full config reference — Configuration documents every
.envvariable andprojects.ymlfield. - CLI commands — CLI Reference covers all 14 commands with flags, arguments, and examples.
- Quick reference — Cheatsheet has copy-paste commands for daily operations.