A label-driven, compose-aware backup daemon with hooks, notifications, and automatic retention - powered by restic, on your schedule.
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
- How It Works
- Quick Start
- Label Reference
- Examples
- Configuration
- CLI
- Repository Layout
- Deployment
- Restoring
- Backends
- Development
- Label-driven - Configure every aspect via Docker labels. No config files, no CLI per container.
- Compose-aware - Detects compose stacks, respects
depends_onordering 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 forgetandrestic prunewith 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.
┌───────────────────────────────────────────────────────────┐
│ 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.
# 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: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 | 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).
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'stime.ParseDuration)
See the robfig/cron v3 docs for full syntax.
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.
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.
# Back up only these mounts
buoy.include: "db_data, /srv/uploads"
# Back up everything except these
buoy.exclude: "/tmp, cache_vol"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 |
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
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: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=./srccreates a named entrycode. Only.go,.ts,.js,.cssfiles are backed up from/app/src. Tagsproduction,webapp,source-codeare applied.uploadsis a bare entry matching the volume by its Docker name. Per-mount overrides use the volume name automatically —buoy.backup.uploads.*. Everything under/app/uploadsis backed up except*.tmp,*.cache,*.log, andthumbs/. Tagsproduction,webapp,user-dataare applied.- Global
buoy.backup.tagsapply to both mounts, with per-mount tags appended.
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.
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.schedule →
daemon.default_schedule, buoy.retention → daemon.default_retention,
buoy.repos → restic.repos), the default shown here applies.
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| 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.
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.
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 eventsnone- 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.
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).
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.
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.
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 --allFlags:
| 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.
Lists all currently active scheduled backups known to the daemon.
buoyctl list # render as a table
buoyctl list --json # JSON outputShows 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.
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-envFlags:
| 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.excludefilter mounts by volume name, source path, or destination path.includesupports optionalname=valuesyntax 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:
- 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.envfiles and the process environment. Without the flag, variables appear as-is. - YAML snippet — ready-to-paste
volumes:block for buoy's compose service, using resolved absolute paths and:romode.
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
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/dbs3: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.
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:roin buoy's compose file.
If a mount source doesn't exist inside buoy, it's skipped with a warning.
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.
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.
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/restoreSince 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/restorebuoy 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 mediago mod tidy
make build
./buoy runRun 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