Skip to content

depado/buoy

Repository files navigation

buoy

A label-driven, compose-aware backup daemon with hooks, notifications, and automatic retention - powered by restic, on your schedule.

CI Release License Last Commit Stars Contributors Issues container image

Warning

Work in progress. buoy is experimental, under active development and testing. APIs, labels, and behavior may change without notice. Use at your own risk.

Features

  • Label-driven - Configure every aspect via Docker labels. No config files, no CLI per container.
  • Compose-aware - Detects compose stacks, respects depends_on ordering for stop/start sequences, and batches containers sharing the same schedule into a single coordinated backup cycle.
  • Multi-repo - Back up to multiple restic repositories at once. Store copies locally, on S3, SFTP, Backblaze B2, or any rclone backend - ready for 3-2-1.
  • Repo registry - Maintains a persistent registry of all known restic repositories, so you can list, check, and run retention on repos even when their containers are down.
  • Hooks - Run shell commands on the host or inside the container before and after each backup.
  • Stop-first - Optionally stop containers before backup for data consistency, then restart them automatically. One label to opt in.
  • Notifications - Success and failure alerts via shoutrrr: Slack, Discord, Telegram, Pushover, email, Gotify, and more.
  • Retention - Automatic restic forget and restic prune with per-container policies (keep-daily, keep-weekly, keep-monthly, keep-yearly, keep-within).
  • Real-time discovery - Watches Docker events. New containers are picked up immediately; removed containers are cleaned up.
  • Selective backup - Include or exclude volumes and mounts by name or path. Use restic file patterns to back up only what matters.
  • Stack lifecycle - When a container opts into buoy.stop-before, buoy cascades the stop to its dependents, backs up, then restarts everything in dependency order - waiting for each to be healthy before starting the next.

How It Works

┌───────────────────────────────────────────────────────────┐
│                       buoy daemon                         │
│                                                           │
│  1. Discover containers with buoy.enabled=true            │
│  2. Parse labels → backup schedule, repo, retention       │
│  3. Register cron job for each container                  │
│                                                           │
│  When a schedule fires:                                   │
│  ┌───────────────────────────────────────────────────┐    │
│  │  pre-hooks → stop (ordered, cascade) →            │    │
│  │  restic backup → start (ordered + health wait) →  │    │
│  │  post-hooks → forget → prune                      │    │
│  └───────────────────────────────────────────────────┘    │
│                                                           │
│  Reacts to Docker events in real-time:                    │
│  - Container start → schedule it                          │
│  - Container stop  → remove from schedule                 │
└───────────────────────────────────────────────────────────┘

buoy uses restic's --json scripting API for structured output and per-container repositories for isolation. Snapshots use clean relative paths for portable restores across hosts and storage backends.

Quick Start

1. Deploy buoy

# compose.yaml
services:
  buoy:
    image: ghcr.io/depado/buoy:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/lib/docker/volumes:/var/lib/docker/volumes:ro
      - /srv/data:/srv/data:ro # bind mounts you want backed up
      - buoy_data:/data # state persistence
    environment:
      - BUOY_RESTIC_PASSWORD=your-secure-password
      - BUOY_RESTIC_REPOS=/backup
      - BUOY_DAEMON_CONCURRENCY=2
    restart: unless-stopped

volumes:
  buoy_data:

2. Label a container for backup

services:
  myapp:
    image: myapp:latest
    volumes:
      - app_data:/data
    labels:
      buoy.enabled: "true"
      buoy.schedule: "0 3 * * *"

volumes:
  app_data:

That's it. buoy discovers the container, initializes a restic repo at /backup/<project>/<service> at backup time, and backs it up daily at 3 AM.

See Examples for more advanced setups with hooks, file patterns, and compose stacks.

Tip

For compose stacks, buoy reads depends_on ordering and batches containers sharing the same schedule into one coordinated stop/start cycle. See Compose Stack Awareness.

Label Reference

Label Default Description
buoy.enabled - Set to "true" to enable backup (required)
buoy.schedule Global default_schedule Cron expression. Falls back to global default. Containers sharing the same schedule in a compose stack are batched together.
buoy.repos Global repos Comma-separated repo URLs, overrides the global list
buoy.retention Global default_retention Retention rules (see below). Falls back to global default.
buoy.stop-before "false" Stop the container before backing up. Defaults to false - opt-in to container stops.
buoy.stop-timeout "30s" Timeout for container stop
buoy.include - Comma-separated mount identifiers (volume names or paths). Optional name=value syntax for per-mount backup overrides. Volume names are automatically used as the per-mount key when matched.
buoy.exclude - Comma-separated mount identifiers (volume names or paths) to skip
buoy.backup.files - Comma-separated file patterns to back up (uses --files-from). When set, only matching files are backed up, not the whole mount. Supports globs (*.sql) and ! negation.
buoy.backup.exclude - Comma-separated restic exclude patterns (e.g., "*.log,*.tmp")
buoy.backup.tags - Comma-separated restic snapshot tags
buoy.backup.<name>.* - Per-mount overrides for named include entries. <name>.files, <name>.exclude replace globals for that mount; <name>.tags are appended.
buoy.hook.pre.cmd - Shell command to run on the host before backup
buoy.hook.post.cmd - Shell command to run on the host after backup
buoy.hook.pre.exec - Command to run inside the container before backup (docker exec)
buoy.hook.post.exec - Command to run inside the container after backup (docker exec)

Important

A container is skipped if it has no schedule (neither buoy.schedule nor daemon.default_schedule) or if no repos are resolved (neither buoy.repos nor restic.repos).

Schedule Format

Standard 5-field cron (minute hour day-of-month month day-of-week) or @every interval.

Shorthands:

  • @yearly / @annually - midnight, January 1st
  • @monthly - midnight, first day of the month
  • @weekly - midnight between Saturday and Sunday
  • @daily / @midnight - midnight every day
  • @hourly - start of every hour
  • @every 1h30m - fixed interval (any duration accepted by Go's time.ParseDuration)

See the robfig/cron v3 docs for full syntax.

Retention Format

Comma-separated key:value pairs. Supported keys:

Key Restic Flag Example
keep-daily --keep-daily N keep-daily:7
keep-weekly --keep-weekly N keep-weekly:4
keep-monthly --keep-monthly N keep-monthly:6
keep-yearly --keep-yearly N keep-yearly:1
keep-within --keep-within DURATION keep-within:30d

All keys are optional. Omitted keys are not passed to restic.

Include/Exclude Syntax

buoy.include and buoy.exclude accept comma-separated mount identifiers. An identifier can be a volume name, a host source path, or a container destination path — buoy matches each entry against all three fields of every mount.

Basic filtering

# Back up only these mounts
buoy.include: "db_data, /srv/uploads"

# Back up everything except these
buoy.exclude: "/tmp, cache_vol"

Named entries for per-mount backup options

Prefix an include entry with name= to assign it a name. Per-mount labels at buoy.backup.<name>.<option> then apply to that mount, overriding the global defaults. See the Named includes example.

buoy.include: "src=/app/code, data=/app/data, /var/log"

# Per-mount: back up only .go files from /app/code
buoy.backup.src.files: "*.go,*.ts"

# Per-mount: exclude temp files from /app/data
buoy.backup.src.exclude: "*.log,*.tmp"

# Per-mount: append tags to the global set
buoy.backup.src.tags: "source-code"

# /var/log has no name — it uses global backup defaults
Syntax Behavior
name=value Named entry. name used as key for buoy.backup.<name>.* overrides.
Bare value Unnamed entry. If it matches a volume by its Docker name, that name is automatically used for per-mount overrides. Otherwise (bind mount), no per-mount overrides apply — uses globals.
Mixed Both named and unnamed entries can appear in the same buoy.include value.

Per-mount override semantics:

Option Per-mount behavior
files Replaces global buoy.backup.files
exclude Replaces global buoy.backup.exclude
tags Appended to global buoy.backup.tags

Compose Stack Awareness

buoy reads com.docker.compose.project, com.docker.compose.service, and com.docker.compose.depends_on labels that Docker Compose sets automatically.

Scheduling works the same as standalone containers - a container is backed up if it has a schedule, from either buoy.schedule or the global default_schedule. When multiple containers in the same stack share the same schedule, buoy batches them into one coordinated stop/start cycle. Jobs arriving while a stack backup is running wait in a per-stack queue and run immediately after.

Stop set: buoy stops containers with buoy.stop-before=true plus any container that transitively depends on a stopped container. If the database stops, the API also stops rather than crashing on a lost connection.

Start order: buoy restarts containers in dependency order (database before API) and waits for health checks before starting dependents - same behavior as docker compose up.

See Examples for a full compose stack setup.

Repo paths follow <base>/<project>/<service>:

/backup/myapp/db
/backup/myapp/api
/backup/myapp/cache

Examples

PostgreSQL with dump hooks

Run pg_dumpall before backup to create a consistent SQL dump, then back up only the dump file. Clean up after.

Click to expand
services:
  postgres:
    image: postgres:16
    volumes:
      - postgres_data:/var/lib/postgresql/data
    labels:
      buoy.enabled: "true"
      buoy.schedule: "0 3 * * *"
      buoy.backup.files: "dump.sql"
      buoy.hook.pre.exec: "pg_dumpall -U postgres -f /var/lib/postgresql/data/dump.sql"
      buoy.hook.post.exec: "rm /var/lib/postgresql/data/dump.sql"

volumes:
  postgres_data:

Named includes with per-mount options

A web application with two mount points: source code and user uploads. Different backup strategies for each mount using named include entries.

Click to expand
services:
  webapp:
    image: myapp:latest
    volumes:
      - ./src:/app/src
      - uploads:/app/uploads
    labels:
      buoy.enabled: "true"
      buoy.schedule: "@daily"

      # Named mount entries — volume names auto-derive their per-mount name
      buoy.include: "code=./src, uploads"

      # Global defaults — applied to both mounts
      buoy.backup.tags: "production,webapp"

      # Per-mount: src — back up only source files
      buoy.backup.code.files: "*.go,*.ts,*.js,*.css"
      buoy.backup.code.tags: "source-code"

      # Per-mount: uploads — exclude temp and cache
      buoy.backup.uploads.exclude: "*.tmp,*.cache,*.log,thumbs/"
      buoy.backup.uploads.tags: "user-data"
  • code=./src creates a named entry code. Only .go, .ts, .js, .css files are backed up from /app/src. Tags production,webapp,source-code are applied.
  • uploads is a bare entry matching the volume by its Docker name. Per-mount overrides use the volume name automatically — buoy.backup.uploads.*. Everything under /app/uploads is backed up except *.tmp, *.cache, *.log, and thumbs/. Tags production,webapp,user-data are applied.
  • Global buoy.backup.tags apply to both mounts, with per-mount tags appended.

Compose stack with dependencies

Three services: DB (stop before backup), Cache, and API (depends on both). All share the same schedule, so buoy batches them into one coordinated cycle.

Click to expand
services:
  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
    labels:
      buoy.enabled: "true"
      buoy.schedule: "0 3 * * *"
      buoy.stop-before: "true"

  cache:
    image: redis:7
    volumes:
      - cache_data:/data
    labels:
      buoy.enabled: "true"
      buoy.schedule: "0 3 * * *"

  api:
    image: myapi:latest
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - api_data:/app/data
    labels:
      buoy.enabled: "true"
      buoy.schedule: "0 3 * * *"

volumes:
  db_data:
  cache_data:
  api_data:

At 3 AM: all three fire. DB has stop-before=true, API depends on DB → {DB, API} stop transitively. Cache stays running. Each service backed up. Restart DB, wait healthy, restart API.

Configuration

buoy is configured via a YAML file (conf.yaml), environment variables (BUOY_ prefix, dots → underscores), or CLI flags. The table below lists every available key with its default value.

When a container label refers to a global value (buoy.scheduledaemon.default_schedule, buoy.retentiondaemon.default_retention, buoy.reposrestic.repos), the default shown here applies.

Config file (conf.yaml)

The values shown are the defaults — copy this to get started.

log:
  level: info
  format: json
  source: false
  color: auto

daemon:
  concurrency: 2
  default_schedule: ""
  default_retention: "keep-within:7d,keep-daily:7,keep-weekly:4,keep-monthly:6,keep-yearly:3"
  resync_interval: "5m"
  exec_timeout: "5m"
  health_wait_timeout: "5m"
  backup_timeout: "1h"
  check_schedule: "@weekly"
  db_path: "./buoy.db"

docker:
  host: unix:///var/run/docker.sock

restic:
  binary_path: restic
  password: ""
  compression: auto
  repos:
    - /backup

api:
  enabled: true
  host: "0.0.0.0"
  port: 8080
  token: ""

notify:
  urls: []
  level: error

Reference

Key Default Env / CLI
log.level info BUOY_LOG_LEVEL / --log.level
log.format json BUOY_LOG_FORMAT / --log.format
log.source false BUOY_LOG_SOURCE / --log.source
log.color auto BUOY_LOG_COLOR / --log.color
daemon.concurrency 2 BUOY_DAEMON_CONCURRENCY / --daemon.concurrency
daemon.default_schedule "" BUOY_DAEMON_DEFAULT_SCHEDULE / --daemon.default_schedule
daemon.default_retention keep-within:7d,keep-daily:7,keep-weekly:4,keep-monthly:6,keep-yearly:3 BUOY_DAEMON_DEFAULT_RETENTION / --daemon.default_retention
daemon.resync_interval 5m BUOY_DAEMON_RESYNC_INTERVAL / --daemon.resync_interval
daemon.exec_timeout 5m BUOY_DAEMON_EXEC_TIMEOUT / --daemon.exec_timeout
daemon.health_wait_timeout 5m BUOY_DAEMON_HEALTH_WAIT_TIMEOUT / --daemon.health_wait_timeout
daemon.backup_timeout 1h BUOY_DAEMON_BACKUP_TIMEOUT / --daemon.backup_timeout
daemon.check_schedule @weekly BUOY_DAEMON_CHECK_SCHEDULE / --daemon.check_schedule
daemon.db_path ./buoy.db BUOY_DAEMON_DB_PATH / --daemon.db_path
docker.host unix:///var/run/docker.sock BUOY_DOCKER_HOST / --docker.host
restic.binary_path restic BUOY_RESTIC_BINARY_PATH / --restic.binary_path
restic.password (required) BUOY_RESTIC_PASSWORD / --restic.password
restic.compression auto BUOY_RESTIC_COMPRESSION / --restic.compression
restic.repos (none) BUOY_RESTIC_REPOS / --restic.repos
api.enabled true BUOY_API_ENABLED / --api.enabled
api.host 0.0.0.0 BUOY_API_HOST / --api.host
api.port 8080 BUOY_API_PORT / --api.port
api.token "" BUOY_API_TOKEN / --api.token
notify.urls (none) BUOY_NOTIFY_URLS / --notify.urls
notify.level error BUOY_NOTIFY_LEVEL / --notify.level

Note

Concurrency is I/O-bound. Each backup spawns a restic process that reads from disk and writes to storage. Setting concurrency higher than your I/O capacity can degrade performance across all running backups. Start low (1–2) and increase only if your storage backend and disk I/O can handle it.

Password

buoy requires a restic repository password to start. Set it via config, env var, or CLI flag. The password is global — all per-container repos use the same one. buoy passes it to restic via a temporary --password-file rather than the RESTIC_PASSWORD environment variable.

Notifications

buoy can send failure notifications via shoutrrr, supporting 50+ services including Slack, Discord, Telegram, email, and Gotify. Configure one or more shoutrrr URLs and set the notification level:

  • error - notify on backup failures only (default)
  • all - notify on all backup events
  • none - disable notifications (or omit config)

Each URL encodes both the service and its credentials. Examples:

Service URL format
Slack slack://hook:tokenA-tokenB-tokenC@webhook
Discord discord://token@channel
Telegram telegram://token@telegram?chats=@channel
Gotify gotify://host:port/token
Email (SMTP) smtp://user:pass@host:port/?from=sender@example.com&to=recipient@example.com

See shoutrrr's documentation for the full list of services and URL formats.

Note

Notifications are best-effort — a failure logs a warning but never blocks or fails a backup.

Periodic Repository Check

buoy can periodically verify the structural integrity of all restic repositories with restic check. This is a lightweight verification that reads the repository index and ensures all pack files are referenced correctly.

Configure via daemon.check_schedule (default: @weekly). Set to "" to disable.

daemon:
  check_schedule: "@weekly"

When the check runs, buoy reads known repositories from its persistent state database (buoy.db). Failures are logged and optionally trigger notifications. This is a structural check only - it does not read pack file data (use the CLI or API for restic check --read-data if needed).

State Persistence

buoy maintains a bbolt database at the path configured by daemon.db_path (default ./buoy.db). This database tracks every repository buoy has ever managed: when it was created, when it was last backed up, and whether the associated container still exists.

This enables:

  • Orphaned repo detection: repos belonging to removed containers are tracked rather than forgotten, so you can still run retention, integrity checks, or manually clean them up
  • Cross-restart awareness: the repository list survives daemon restarts

The database is a single file. Mount a volume or bind mount at the directory containing it to persist state across container recreates.

HTTP API

buoy exposes a read/write HTTP API on api.host:api.port (default 0.0.0.0:8080) for querying and operating on repositories. Authentication is via a Bearer token (api.token); when the token is empty, no authentication is required.

Method Path Description
GET /api/v1/health Health check (no auth)
GET /api/v1/scheduled List currently scheduled backups
GET /api/v1/repos List all known repos. ?orphaned=true to show only orphaned
POST /api/v1/repos/check Run restic check on all repos. ?read-data=true for full check
POST /api/v1/repos/stats Aggregate restic stats across all repos
POST /api/v1/repos/unlock Unlock all repos
POST /api/v1/repos/forget Run restic forget with ?retention=keep-daily:7,...
POST /api/v1/repos/prune Run restic prune on all repos

Connect to the API from a local or remote buoy CLI, or use it as the backend for a dashboard/aggregator.

CLI

buoyctl repo

buoyctl provides a repo subcommand for querying and operating on managed repositories. The CLI communicates with a running buoy daemon via its HTTP API.

Set --api.url and --api.token per command, or use the BUOY_URL / BUOY_TOKEN environment variables. Defaults to http://127.0.0.1:8080.

buoyctl repo list --all                 # list all non-orphaned repos
buoyctl repo list --orphaned            # show only orphaned repos
buoyctl repo list --repo /backup/myapp  # show a specific repo
buoyctl repo check --all                # structural integrity check
buoyctl repo check --read-data --all    # full data integrity check
buoyctl repo stats --all                # storage usage across all repos
buoyctl repo unlock --repo /backup/myapp
buoyctl repo forget --retention keep-daily:7,keep-weekly:4 --all
buoyctl repo prune --orphaned

# JSON output
buoyctl repo list --all --json
buoyctl repo stats --all --json

# Remote daemon with auth
export BUOY_URL=https://buoy.internal.example.com
export BUOY_TOKEN=secret123
buoyctl repo stats --all

Flags:

Flag Availability Description
--json all Output as JSON
--api.url all Daemon API URL (defaults to BUOY_URL env)
--api.token all API bearer token (defaults to BUOY_TOKEN env)
--orphaned all Operate on orphaned repos only
--all all Operate on all non-orphaned repos
--repo all Operate on a specific repository URL
--read-data check Read all pack files for full integrity check
--retention forget Retention policy (e.g. keep-daily:7)

Warning

Destructive operations (unlock, forget, prune) return an error if any backup is currently in progress, preventing accidental lock conflicts or corruption. Read-only commands (check, stats) are not gated and may run alongside backups.

buoyctl list

Lists all currently active scheduled backups known to the daemon.

buoyctl list            # render as a table
buoyctl list --json     # JSON output

Shows container name, compose project/service, schedule expression, repos (if overridden), and whether the container is stopped before backup. Empty output means no containers are currently scheduled.

buoyctl discover

Scans a directory recursively for Docker Compose files and lists the volumes and bind mounts buoy would need access to — so you can configure your buoy container with the right host mounts before deploying.

Uses gorich tables with styled output. Bind mounts from enabled services are highlighted green in the table. Built-in mounts (/var/run/docker.sock, /var/lib/docker/volumes) are shown in plain text since buoy already needs them.

# Scan current directory (unlimited depth)
buoyctl discover .

# Scan a specific directory, two levels deep
buoyctl discover /opt/stacks --depth 2

# Unlimited depth
buoyctl discover /opt/stacks --depth -1

# Custom glob pattern for compose files
buoyctl discover /opt/stacks --pattern "stack.*.yml"

# JSON output
buoyctl discover . --json

# Resolve ${VAR} and ${VAR:-default} from .env and the environment
buoyctl discover /opt/stacks --resolve-env

Flags:

Flag Default Description
--json false Output as JSON
--depth -1 Maximum directory depth (-1 for unlimited)
--pattern compose.y*ml,docker-compose.y*ml Comma-separated glob patterns for compose file names
--resolve-env false Resolve ${VAR} and ${VAR:-default} from .env files and process environment

Respects buoy labels. The buoy.enabled, buoy.include/buoy.exclude labels defined in compose files are honored, including both map and list syntax:

labels:
  buoy.enabled: "true"         # map syntax
  buoy.backup.tags: "production"

labels:
  - "buoy.enabled=true"        # list syntax
  - "traefik.enable=true"
  • Services with buoy.enabled: "true" show [green]yes[/green] in the table; disabled or unlabeled services show [red]no[/red].
  • buoy.include / buoy.exclude filter mounts by volume name, source path, or destination path. include supports optional name=value syntax for per-mount backup overrides (e.g. src=/app/code).
  • Bind mounts from enabled services are highlighted [bold green] — these are the ones that need to be added to buoy's compose service. Built-in mounts (/var/run/docker.sock, /var/lib/docker/volumes) are never highlighted.

Output sections:

  1. Per-file table — each compose file gets a green title and a table with service, enabled state, type, source (original compose path), destination, and read/write mode. With --resolve-env, ${VAR:-default} syntax is resolved from .env files and the process environment. Without the flag, variables appear as-is.
  2. YAML snippet — ready-to-paste volumes: block for buoy's compose service, using resolved absolute paths and :ro mode.

Example output:

       /opt/stacks/webapp/compose.yaml                                   
                                                                         
  Service  Enabled  Type    Source            Destination     Mode       
 ─────────────────────────────────────────────────────────────────────── 
  db       yes      volume  db_data           /var/lib/mysql   rw        
  db       yes      bind    /srv/backups      /backups         rw        
  api      yes      bind    /var/run/docker…  /var/run/docker… ro        
  web      no       bind    ./html            /usr/share/nginx ro        
                                                                         
       /opt/stacks/worker/compose.yaml                                   
                                                                         
  Service  Enabled  Type    Source     Destination  Mode                 
 ─────────────────────────────────────────────────────────────────────── 
  worker   yes      bind    ./jobs     /jobs        rw                   
                                                                         

Add to buoy's compose service volumes:
volumes:
  - /opt/stacks/webapp/backups:/opt/stacks/webapp/backups:ro
  - /opt/stacks/worker/jobs:/opt/stacks/worker/jobs:ro
  - /var/run/docker.sock:/var/run/docker.sock:ro
  - /var/lib/docker/volumes:/var/lib/docker/volumes:ro

Repository Layout

Each container gets a separate restic repository under each configured base repo. For a container in compose project myapp service db with repos [/backup, s3:s3.amazonaws.com/my-bucket], buoy creates:

  • /backup/myapp/db
  • s3:s3.amazonaws.com/my-bucket/myapp/db

For standalone containers, the URL is <repo>/<container_name>.

Repos are initialized automatically at backup time.

Snapshots use clean relative paths: buoy changes into each mount's source directory and backs up individual entries, so snapshots contain file.db, logs/ instead of /var/lib/docker/volumes/<name>/_data/file.db. Mounts are tagged (mount:<name>) for correct parent snapshot selection.

Deployment

As a Docker container (recommended)

services:
  buoy:
    image: ghcr.io/depado/buoy:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/lib/docker/volumes:/var/lib/docker/volumes:ro
      - /srv/app-data:/srv/app-data:ro # each bind mount explicitly
      - buoy_data:/data # state persistence
    environment:
      - BUOY_RESTIC_PASSWORD=${RESTIC_PASSWORD:?required}
      - BUOY_RESTIC_REPOS=/backup
      # - BUOY_NOTIFY_URLS=slack://tokenA/tokenB/tokenC
      # - BUOY_NOTIFY_LEVEL=error
    restart: unless-stopped

volumes:
  buoy_data:

Each path buoy needs to read must be mounted explicitly:

  • Named/anonymous volumes: Mount /var/lib/docker/volumes:/var/lib/docker/volumes:ro (or your Docker data root). Covers all Docker-managed volumes.
  • Bind mounts: Mount each host source path at the same location inside buoy. For example, if a container bind-mounts /srv/app-data:/app/data, mount /srv/app-data:/srv/app-data:ro in buoy's compose file.

If a mount source doesn't exist inside buoy, it's skipped with a warning.

Restart policy caveat

Warning

Containers with buoy.stop-before=true must not have restart: always (or similar). If they do, Docker restarts them immediately after buoy stops them, causing a race with the backup. Use restart: "no" or omit the restart policy on containers that buoy stops.

Signal handling

On Unix systems, pressing Ctrl+C sends SIGINT to the entire foreground process group, which includes restic child processes. buoy prevents this by placing restic in its own process group (Setpgid). This means backups complete cleanly even when buoy receives a shutdown signal.

When building from source on Windows, this protection is not applied. If you encounter partial backups during shutdown on Windows, consider running buoy inside the Linux container image instead.

Restoring

buoy does not have a built-in restore command. Use restic directly:

# List snapshots
restic -r /backup/myapp/postgres snapshots

# Restore a specific snapshot
restic -r /backup/myapp/postgres restore <snapshot-id> --target /tmp/restore

# Restore the latest snapshot
restic -r /backup/myapp/postgres restore latest --target /tmp/restore

Since buoy uses clean relative paths, restored files go directly into the target directory without the full host path prefix.

For cloud backends, include the relevant credentials:

export RESTIC_PASSWORD=your-password
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
restic -r s3:s3.amazonaws.com/my-bucket/myapp/postgres restore latest --target /tmp/restore

Backends

buoy supports all restic backends:

  • Local filesystem: /backup
  • Amazon S3: s3:s3.amazonaws.com/bucket
  • Backblaze B2: b2:bucketname:/path
  • Azure Blob: azure:container:/path
  • Google Cloud: gs:bucket:/path
  • SFTP: sftp:user@host:/path
  • REST server: rest:https://host:8000/
  • rclone: rclone:remote:path

Configure one or more repos for 3-2-1 backup strategy. Each container backs up to all repos simultaneously. Cloud credentials are passed via standard restic environment variables.

restic:
  repos:
    - /backup # local copy
    - s3:s3.amazonaws.com/my-bucket # offsite copy
    - b2:my-bucket:/buoy # different media

Development

go mod tidy
make build
./buoy run

Run with debug logging:

./buoy run --log.level debug --log.format text --log.color always

# Buoyctl commands talk to the running daemon
./buoyctl repo list --all

About

Backup docker container volumes and bind mounts using restic and labels with support for stacks and hooks

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages