Secure Linux backups for files, Docker Compose and databases, with encryption and cloud storage.
221
Your last line of data defense. Back up deliberately. Restore with confidence.
Recovery-first backups for Linux files, Docker Compose applications and databases.
Backfort creates full backup bundles, optionally compresses, encrypts and signs them, publishes independent copies to local or cloud storage, and exits.
No resident daemon. No hidden source discovery. No automatic overwrite of production data.
Whether you need a quick recovery point before an upgrade, scheduled offsite backups, or a controlled migration of a Compose application, Backfort keeps the backup plan explicit and the recovery process inspectable.
Source code · Documentation · Examples · Docker deployment guide · Report an issue
A backup is useful only when you can recover from it.
Backfort is built around that principle:
.complete marker. Interrupted uploads do not appear as recoverable backups.The container runs the same Backfort implementation and uses the same YAML configuration and backup format as a native installation.
Protect application uploads, website files, documents and explicitly selected configuration directories.
Supported capabilities include:
gzip, zstd, or uncompressed archives.Create a recoverable bundle containing explicitly selected:
Backfort does not guess what belongs in a backup. It does not automatically copy every volume, .env file or path mounted by an application.
| Engine | Backup artifact |
|---|---|
| PostgreSQL | Custom-format dump, or plain SQL when configured; optional globals export |
| MySQL / MariaDB | Logical SQL dump with routines, events and triggers |
| Microsoft SQL Server | Native .bak backup |
| Oracle | Oracle Data Pump export |
Database tools and required privileges must be available inside the selected database container. Dump and import operations also require an in-container timeout command.
A live database-volume archive is not a substitute for an engine-aware dump. Multi-service or application-wide consistency requires your own reviewed quiescing procedure.
Docker Hub:
docker pull shellharbor/backfort:1.2.0
docker run --rm shellharbor/backfort:1.2.0 --version
The project also distributes the image through GitHub Container Registry:
docker pull ghcr.io/shellharbor/backfort:1.2.0
Release builds target:
linux/amd64linux/arm64The release pipeline publishes exact version tags and moving stable aliases:
1.2.0 — exact release.1.2 — latest stable release in that minor series.1 — latest stable release in that major series.latest — latest stable release.Pin an exact version or image digest for production. Moving aliases are convenient for evaluation, but they are not reproducible deployment references.
The image includes GNU tar, Bash, core utilities, gzip, zstd, age, GnuPG, Minisign, rclone, notification tools, and the Docker CLI with Compose support. Features remain opt-in through configuration.
The following example backs up /srv/application on a Linux host.
Run the host setup and Compose commands from a trusted root shell, or adapt ownership and permissions for your dedicated backup account with Docker access.
install -d -m 0700 /srv/backfort /srv/backups/backfort
cd /srv/backfort
The application source directory must already exist. Keep the backup destination outside the source tree.
config.yamlAll paths in this configuration are container paths:
version: 1
settings:
host_id: server-01
state_directory: /var/lib/backfort
temp_directory: /var/tmp/backfort
lock_file: /var/lib/backfort/backfort.lock
min_free_mb: 512
destinations:
- name: local
type: local
path: /backups
jobs:
- name: application-files
source:
type: files
paths:
- /source
exclude:
- "*.log"
- "*.tmp"
- "*/cache/*"
follow_symlinks: false
destinations:
- local
success:
min_copies: 1
compression:
method: gzip
level: 6
encryption:
method: none
signing:
method: none
retention:
keep_last: 7
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
min_keep: 1
max_age_days: 90
version: 1 is the configuration schema version, not the application release number.
Choose a stable, unique host_id for each server writing to shared storage. Automatic discovery and retention are scoped to that identity.
This introductory example is not encrypted. Configure encryption before storing sensitive data offsite.
docker-compose.ymlservices:
backfort:
image: shellharbor/backfort:1.2.0
restart: "no"
init: true
read_only: true
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:rw,nosuid,nodev,noexec,size=64m
volumes:
- ./config.yaml:/etc/backfort/config.yaml:ro
- backfort-state:/var/lib/backfort
- backfort-work:/var/tmp/backfort
- ${BACKFORT_SOURCE_DIR:?Set an absolute source directory}:/source:ro
- ${BACKFORT_BACKUP_DIR:?Set an absolute backup directory}:/backups
command: ["-c", "/etc/backfort/config.yaml", "doctor"]
volumes:
backfort-state:
backfort-work:
export BACKFORT_SOURCE_DIR=/srv/application
export BACKFORT_BACKUP_DIR=/srv/backups/backfort
docker compose pull
docker compose run --rm backfort doctor
docker compose run --rm backfort --dry-run run
docker compose run --rm backfort run
doctor checks configuration and prerequisites. A dry run validates and displays the plan without creating a backup.
The default container command runs doctor and exits. A stopped container is expected: Backfort is a one-shot job, not a long-running service.
| Container path | Purpose | Recommended access |
|---|---|---|
/etc/backfort/config.yaml | Backup configuration | Read-only |
/source | Application source files | Read-only |
/backups | Local backup bundles | Read-write, persistent |
/var/lib/backfort | State and shared lock | Read-write, persistent |
/var/tmp/backfort | Archive working space | Read-write, sufficient capacity |
/tmp | Short-lived scratch files | Small writable tmpfs |
Working space must hold the complete working bundle. Do not size it as though Backfort only streams a few small temporary files.
Keep state persistent across invocations so independent runs share the same lock. Do not casually use docker compose down -v, which removes named volumes.
List completed backups:
docker compose run --rm backfort list --job application-files
Perform full verification:
docker compose run --rm backfort \
verify latest --job application-files --full
Quick verification checks the final payload checksum and any configured signature. Full verification additionally processes the archive and checks the available per-file hashes.
To rehearse recovery, prepare a separate empty directory:
install -d -m 0700 /srv/backfort-recovery
docker compose run --rm \
-v /srv/backfort-recovery:/restore \
backfort restore latest \
--job application-files \
--to /restore
Inspect the restored tree before copying anything into production.
Restore requires a new or empty target. There is no general force-overwrite or automatic in-place recovery mode.
For recovery on another server, preserve the original job configuration and host_id, or deliberately select a full backup ID. Changing the identity changes what latest discovers.
Encrypted full verification and restore require the corresponding recovery keys.
Need a recovery point without first defining another persistent job?
docker compose run --rm \
-e BACKFORT_QUICK_HOST_ID=server-01 \
backfort quick /source \
--name before-upgrade \
--to /backups \
--state-directory /var/lib/backfort \
--exclude "*.log" \
--exclude "*/cache/*"
This uses the normal backup pipeline and saves a non-secret recovery configuration in persistent state:
/var/lib/backfort/quick/before-upgrade.yaml
Reuse it for later operations:
docker compose run --rm backfort \
-c /var/lib/backfort/quick/before-upgrade.yaml \
list --job before-upgrade
A stable BACKFORT_QUICK_HOST_ID avoids tying quick backups to changing container hostnames.
Quick commands are convenience workflows. Use a saved YAML job when you need configured encryption, signing, lifecycle hooks or Prometheus metrics.
Backfort uses rclone for remote destinations, including:
Configure the provider and credentials in rclone first. Backfort references the remote name; it does not replace provider-specific setup.
Add a destination to config.yaml:
destinations:
- name: local
type: local
path: /backups
- name: s3-offsite
type: rclone
remote: company-s3
path: production-backups/backfort/server-01
For an S3-compatible remote, production-backups is the bucket name.
Update the job’s copy policy:
destinations: [local, s3-offsite]
success:
min_copies: 2
Mount the protected rclone configuration and merge these settings into the existing Compose service, retaining its other mounts:
environment:
RCLONE_CONFIG: /run/secrets/rclone.conf
volumes:
- ./rclone.conf:/run/secrets/rclone.conf:ro
Run doctor again after changing credentials or destinations.
Backfort publishes individual bundle objects rather than running a broad rclone sync. The completion marker is published last.
Use separate, appropriately scoped credentials for backup publication and pruning where your operating model permits it.
Replace the job’s encryption block:
encryption:
method: age
recipient_env: BACKFORT_AGE_RECIPIENT
identity_file_env: BACKFORT_AGE_IDENTITY_FILE
Supply the public recipient through your deployment environment. For example, merge this into the Compose service:
environment:
BACKFORT_AGE_RECIPIENT: "${BACKFORT_AGE_RECIPIENT:?Set an age public recipient}"
The backup writer needs only the public recipient. Keep the private identity on a separate trusted recovery host whenever practical.
For recovery, mount the identity file read-only and set BACKFORT_AGE_IDENTITY_FILE to its container path.
A public-key-only writer can create encrypted backups and perform quick verification, but cannot decrypt, fully verify or restore them.
Backfort also supports:
Checksums detect corruption. Signatures additionally detect replacement by an attacker who lacks the signing key. Neither protects against an attacker who controls the backup writer and its private signing material.
Never place passwords, tokens or private keys in tracked configuration or image layers.
Encryption and signing documentation
A Compose-source job needs two additional mounts:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /srv/crm:/srv/crm:ro
Merge them into the existing Backfort service.
The project directory must appear inside Backfort at the same absolute path as on the Docker host. This lets the containerized client and host daemon resolve project paths consistently.
Docker socket access is host-root-equivalent. Mounting the socket read-only does not remove that authority. Grant it only to a trusted Compose backup job, never to an ordinary files-only deployment.
For a quick migration recovery point, the following example selects:
compose.yaml.uploads named volume.data/uploads bind-mount directory.crm PostgreSQL database.Before running it, configure the database credentials through an approved secret source and choose a trusted, already-pulled helper image containing GNU tar and timeout. Pin that helper by digest for reproducibility.
docker compose run --rm \
-e BACKFORT_QUICK_HOST_ID=server-01 \
-e BACKFORT_PG_PASSWORD \
backfort quick-compose /srv/crm \
--name crm-before-migration \
--file compose.yaml \
--to /backups \
--state-directory /var/lib/backfort \
--volume uploads \
--volume-helper-image "${BACKFORT_VOLUME_HELPER_IMAGE:?Set a trusted pre-pulled helper image}" \
--bind uploads:data/uploads \
--db crm-postgres:postgres:postgres:backfort:BACKFORT_PG_PASSWORD:crm
Adapt the service, volume, path, database and user names to your actual project.
Repeat --to to publish additional independent copies:
--to rclone:company-s3:production-backups/backfort/crm
Add --min-copies 2 when both destinations are required.
quick-compose emits plain SQL dumps for PostgreSQL, MySQL and MariaDB. The dumps are stored inside the normal backup bundle, not uploaded as unrelated bare SQL objects.
This is a backup of selected project data—not a container checkpoint, image export or automatic capture of a container’s ephemeral writable layer.
The generated recovery configuration is saved under:
/var/lib/backfort/quick-compose/crm-before-migration.yaml
Stage the backup into a separate empty host directory:
install -d -m 0700 /srv/backfort-crm-recovery
docker compose run --rm \
-v /srv/backfort-crm-recovery:/restore \
backfort \
-c /var/lib/backfort/quick-compose/crm-before-migration.yaml \
restore-compose latest \
--job crm-before-migration \
--to /restore
By default, restore-compose verifies and stages the bundle, then prints recovery actions. It does not start services, create volumes or import databases.
An optional import assistant supports PostgreSQL, MySQL and MariaDB against an explicitly prepared target Compose project. It requires both --apply --confirm and an explicit --project-dir.
Treat that import as a destructive operation against the selected target database. Use an isolated recovery project and review the staged artifacts first.
PostgreSQL globals, SQL Server backups and Oracle exports require reviewed manual recovery procedures.
Compose migration runbook · Compose and database examples
Retention is evaluated separately for each job and destination.
retention:
keep_last: 7
keep_daily: 14
keep_weekly: 8
keep_monthly: 12
min_keep: 1
max_age_days: 90
Backfort combines the configured last, daily, weekly and monthly selections. Optional age expiry removes older unpinned copies, except for the min_keep recovery floor.
Pruning is a separate command. Creating a backup does not automatically prune existing versions.
Preview first:
docker compose run --rm backfort --dry-run prune
docker compose run --rm backfort prune
Pruning applies to configured local and rclone destinations, including S3-compatible storage.
Protect an important completed recovery point:
docker compose run --rm backfort pin BACKUP_ID \
--reason "Before database migration"
Replace BACKUP_ID with an ID returned by list.
Pinned copies remain outside normal GFS retention and age expiry. Review them periodically: pins can keep storage indefinitely.
Remove a pin when it is no longer needed:
docker compose run --rm backfort unpin BACKUP_ID
Date-range deletion is also available through delete, with explicit UTC boundaries and confirmation. Always inspect its dry-run output before authorizing a purge.
Backfort does not run an internal scheduler. YAML defines backup jobs and policies; cron, systemd timers or Kubernetes CronJobs determine when commands run.
Example host cron entries:
15 2 * * * cd /srv/backfort && BACKFORT_SOURCE_DIR=/srv/application BACKFORT_BACKUP_DIR=/srv/backups/backfort docker compose run --rm backfort run
45 3 * * * cd /srv/backfort && BACKFORT_SOURCE_DIR=/srv/application BACKFORT_BACKUP_DIR=/srv/backups/backfort docker compose run --rm backfort prune
These use the host cron timezone. Adjust the windows to your backup duration and preserve the process exit status.
The shared persistent lock protects against overlapping mutating commands. Keep restart: "no": a container restart should not create an unexpected additional backup.
Use the dead man’s switch to detect a missing or stale completed backup:
docker compose run --rm backfort \
watchdog --job application-files --max-age 24
Run this from an independent monitoring schedule. A backup job that never starts cannot reliably report its own absence.
Saved YAML jobs can publish results through the node_exporter textfile collector:
metrics:
prometheus:
textfile_directory: /metrics
Mount an existing writable collector directory at /metrics and configure node_exporter separately.
Metrics cover run outcome, exit code, completion timestamp, duration, payload size and successful or failed destination counts.
Backfort does not expose an HTTP metrics server. Textfile metrics are produced by persistent run jobs, not by quick backup commands.
Optional event notifications support Telegram, ntfy, webhooks and email delivery.
Saved jobs can also run executable pre/post lifecycle hooks with literal arguments and bounded timeouts. Use them for a reviewed application quiescing and cleanup procedure—not arbitrary shell strings.
Post-cleanup is attempted on supported failure and termination paths, but cannot compensate for uncatchable termination or a host power loss. Design hook cleanup to be idempotent.
Monitoring guide · Hooks and notifications
The project includes a Helm 3 chart for Kubernetes 1.31+.
Its scope is explicit mounted PVC files, backed up to a separate PVC or configured rclone destination. It uses the same image, configuration and recovery bundles.
From a matching repository checkout:
cp examples/kubernetes/pvc-local.values.yaml values.yaml
# Adapt host_id, PVC names, storage sizes and storage classes.
helm lint charts/backfort --strict -f values.yaml
helm upgrade --install backfort ./charts/backfort \
--namespace application \
-f values.yaml \
--set image.repository=shellharbor/backfort \
--set image.tag=1.2.0
The deployment provides:
Source PVCs and referenced Secrets must already exist in the release namespace. Review your CSI driver, access modes, permissions and admission policies before enabling schedules.
This is not a cluster backup product. It does not discover Kubernetes resources, orchestrate CSI snapshots or dump database Pods. The Docker Compose adapter requires a Docker host and is not supported by this chart.
Use the actual command result in your scheduler and monitoring:
| Code | Meaning |
|---|---|
0 | Complete success |
1 | Some destinations succeeded and some failed |
2 | Invalid arguments, configuration, dependencies or environment |
3 | Operational failure or required copy policy not met |
A failed minimum-copy policy does not erase copies that were successfully completed. Inspect the result instead of assuming that every nonzero exit means no recovery copy exists.
Backfort currently creates full backups only. Incremental backup chains and deduplication are not implemented.
The release pipeline is configured to smoke-test both supported image architectures and publish OCI metadata, SBOM and provenance attestations. These are supply-chain information, not a guarantee that every storage driver or application recovery scenario is automatically supported.
Backfort is open-source software released under the MIT License.
Keep your backups independent. Keep your recovery rehearsed.
Content type
Image
Digest
sha256:9c3a0e03f…
Size
106.1 MB
Last updated
5 days ago
docker pull shellharbor/backfort