Skip to content

feat: docker-volume-backup-restore#74

Merged
RambokDev merged 4 commits into
mainfrom
feat/docker-volume-backup-restore
Jul 3, 2026
Merged

feat: docker-volume-backup-restore#74
RambokDev merged 4 commits into
mainfrom
feat/docker-volume-backup-restore

Conversation

@RambokDev

@RambokDev RambokDev commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for a new Docker volume–based database option, including backup, restore, and connectivity checks.
    • Improved restore handling for compressed archives, including direct support for volume backups.
    • Startup now cleans up leftover helper containers when Docker is available.
  • Bug Fixes

    • Restores now choose the correct extracted path in more cases.
    • Backup/restore workflows now better preserve container state around operations.

charles-gauthereau added 2 commits July 2, 2026 21:45
refactor: move build_tar to utils::compress

refactor: move choose_restore_path to utils::common

docs: remove docker-volume README section

fix: keep archive path for multi-file non-docker-volume restores

docs: trim docker-volume README section to essentials

refactor: run docker-volume backup/restore inside spawn_blocking like other providers

docs: enable docker socket, add volume example and security notes

feat: sweep orphaned ephemeral helper containers on startup

feat: docker-volume clean-replace restore via upload_to_container

fix: serialize env-var access in docker-volume tests to avoid setenv/getenv UB

feat: docker-volume backup via download_from_container

feat: docker-volume ping via inspect_volume

fix: return extraction dir for multi-file restore archives

feat: gzip already-tar inputs directly instead of double-wrapping

feat: docker helper (client, self-image, container lifecycle, sweep)

feat: docker-volume provider
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@RambokDev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a5849beb-38f4-47d1-af69-150160672691

📥 Commits

Reviewing files that changed from the base of the PR and between bbecca9 and d12817a.

📒 Files selected for processing (2)
  • .github/workflows/codecov.yml
  • src/tests/domain/docker_volume.rs
📝 Walkthrough

Walkthrough

This PR adds a Docker-volume-backed database backend that uses ephemeral helper containers for backup and restore. It introduces DockerVolumeDatabase, Docker orchestration utilities, config support for a new DockerVolume db_type, restore pipeline changes, startup cleanup of orphaned helpers, and related tests.

Changes

Docker Volume Backend

Layer / File(s) Summary
Docker helper container primitives
src/domain/docker_volume/docker.rs, src/domain/docker_volume/mod.rs, Cargo.toml
New module provides Docker client connection, container ID parsing, helper image resolution, helper container create/remove, start/stop wrappers, and ephemeral sweep; adds bollard dependency and tokio-util io feature.
Volume ping check
src/domain/docker_volume/ping.rs
Checks volume existence via inspect_volume, returning false on 404.
Backup workflow
src/domain/docker_volume/backup.rs
Stops target container, creates helper, streams /vol archive to .tar, restarts container, returns backup path.
Restore workflow
src/domain/docker_volume/restore.rs
Stops target container, creates helper, wipes /vol, uploads archive, restarts container.
Database trait wiring and factory
src/domain/docker_volume/database.rs, src/domain/mod.rs, src/domain/factory.rs
DockerVolumeDatabase implements Database with per-id locking, delegating to ping/backup/restore; wired into DatabaseFactory.
Config support for docker-volume
src/services/config.rs, databases.json, src/tests/domain/*, src/tests/services/config_tests.rs
Adds DbType::DockerVolume, volume_name/container_name fields, optional host/port/database handling; test configs and new config tests updated.
Restore pipeline archive selection
src/services/restore/archive.rs, src/services/restore/executor.rs, src/utils/common.rs, src/utils/compress.rs, tests
prepare_archive accepts db_type, special-cases DockerVolume gunzip, adds choose_restore_path and gunzip_to_file with tests.
Startup sweep and docker-compose
src/main.rs, docker-compose.yml, src/tests/domain/docker_volume.rs
Startup best-effort sweep of orphaned helper containers; enables Docker socket mount; adds integration tests for ping/backup/restore/sweep.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DockerVolumeDatabase
  participant BackupRun as backup::run
  participant Docker
  participant TarFile
  DockerVolumeDatabase->>BackupRun: run(cfg, backup_dir, logger)
  BackupRun->>Docker: stop_container (optional)
  BackupRun->>Docker: create_helper(volume)
  BackupRun->>Docker: stream /vol archive
  Docker-->>TarFile: write bytes
  BackupRun->>Docker: remove_helper
  BackupRun->>Docker: start_container (optional)
  BackupRun-->>DockerVolumeDatabase: PathBuf
Loading
sequenceDiagram
  participant DockerVolumeDatabase
  participant RestoreRun as restore::run
  participant Docker
  participant Archive
  DockerVolumeDatabase->>RestoreRun: run(cfg, archive, logger)
  RestoreRun->>Docker: stop_container (optional)
  RestoreRun->>Docker: create_helper + start
  RestoreRun->>Docker: wipe /vol
  RestoreRun->>Archive: upload_to_container
  RestoreRun->>Docker: remove_helper
  RestoreRun->>Docker: start_container (optional)
Loading

Possibly related PRs

  • Portabase/agent#22: Both PRs modify RestoreService::prepare_archive in src/services/restore/archive.rs, overlapping at the same function.
  • Portabase/agent#60: Adds a docker_volume backend whose backup/restore routines depend on JobLogger/Database trait signature changes introduced there.
  • Portabase/agent#51: Both PRs modify shared DbType/DatabaseFactory plumbing in src/services/config.rs and src/domain/factory.rs.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Docker-volume backup and restore support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/docker-volume-backup-restore

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/tests/domain/mysql.rs (1)

24-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a Default impl for DatabaseConfig to avoid repetitive test edits.

Every new field added to DatabaseConfig (here volume_name/container_name) requires touching every test's config literal (mysql.rs, postgres.rs, redis.rs, valkey.rs, firebird.rs, mariadb.rs, mongodb.rs, mssql.rs). Implementing Default for DatabaseConfig and using ..Default::default() in these test builders would isolate future field additions to a single place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/domain/mysql.rs` around lines 24 - 38, The test config literals for
DatabaseConfig are duplicated across many domain tests, so adding fields forces
repeated edits. Add a Default implementation for DatabaseConfig that sets
sensible baseline values, then update the test builders (including mysql.rs and
the other database tests) to construct the struct with ..Default::default() and
only override the fields each test actually needs. This will centralize future
field additions and keep the test setup stable.
src/utils/common.rs (1)

5-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused _extraction_dir parameter.

The parameter is never used in the selection logic; the multi-file branch just falls back to the raw archive path. If this is intentional (mirroring prior inline behavior), consider dropping the parameter for a cleaner API, or wire it in if extraction-dir-based selection was intended for future dump formats (e.g. directory-format multi-file dumps).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/common.rs` around lines 5 - 14, The choose_restore_path function
has an unused _extraction_dir parameter and the current multi-file branch just
returns the archive path. Either remove the parameter from choose_restore_path
and update its call sites if it is intentionally unused, or incorporate
_extraction_dir into the path-selection logic if extraction-dir-based resolution
is expected for multi-file dump formats. Keep the behavior consistent with the
existing single-file and fallback cases.
src/tests/domain/docker_volume.rs (1)

27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Gate the Docker-daemon integration tests so they don't hard-fail CI.

docker_volume_ping_true_for_existing_volume, docker_volume_backup_captures_files, docker_volume_restore_is_clean_replace, and sweep_removes_labeled_helpers all call client().expect("docker daemon required..."). In any environment without a reachable Docker socket these panic and fail the test run rather than being skipped. Consider #[ignore] (run explicitly in a docker-enabled job) or a runtime skip when the daemon is unavailable.

♻️ Example: mark daemon-dependent tests as ignored
 #[tokio::test]
+#[ignore = "requires a running Docker daemon"]
 async fn docker_volume_ping_true_for_existing_volume() {

Also applies to: 103-103, 147-147, 228-228

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/domain/docker_volume.rs` at line 27, The Docker-daemon integration
tests are hard-failing when no socket is available because
`docker_volume_ping_true_for_existing_volume`,
`docker_volume_backup_captures_files`, `docker_volume_restore_is_clean_replace`,
and `sweep_removes_labeled_helpers` all use `client().expect(...)`. Update these
tests to be gated instead of panicking, either by marking the affected test
functions as ignored for explicit Docker-enabled runs or by adding a runtime
skip when `client()` cannot connect; keep the change localized to the test
module so the daemon-dependent paths still run where Docker is present.
docker-compose.yml (1)

14-14: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Docker socket mount grants root-equivalent host access.

Mounting /var/run/docker.sock gives this container full control of the host Docker daemon. It's required for the helper-container workflow, but treat it as a significant privilege escalation surface: keep it scoped to environments that need backup/restore, and consider a socket proxy with a restricted API allowlist for production.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` at line 14, The service is mounting the host Docker
socket, which exposes root-equivalent access to the daemon; keep this mount only
for the helper-container workflow that actually needs backup/restore, and gate
it behind a dedicated profile or environment-specific override so it is not
enabled broadly. If production must use it, replace the direct
/var/run/docker.sock bind in docker-compose with a socket proxy or similarly
restricted endpoint and limit the allowed Docker API surface.
src/domain/docker_volume/backup.rs (1)

16-17: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Remove the blocking wrapper around this async backup flow.
run is already async, and the work inside is just await-driven bollard/tokio I/O. spawn_blocking + block_on only burns a blocking thread here; run the body directly unless client() or helper setup has a separate blocking step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domain/docker_volume/backup.rs` around lines 16 - 17, The backup flow in
run is unnecessarily wrapped in tokio::task::spawn_blocking and
futures::executor::block_on even though it is already async and the
bollard/tokio operations can be awaited directly. Remove the blocking wrapper
from the backup path in the run function, keep the async body as normal awaits,
and only introduce blocking code if a specific helper such as client() truly
requires it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker-compose.yml`:
- Line 22: Remove the hardcoded EDGE_KEY from the docker-compose configuration
and source it from an external secret store or untracked environment file using
environment interpolation instead. Update the compose setup to reference the
variable by name rather than embedding the base64 payload, and ensure the secret
is rotated since the current masterKeyB64 has been exposed.

In `@src/domain/docker_volume/backup.rs`:
- Around line 29-56: The cleanup in the backup flow is only happening after the
happy path in the async block, so the helper container created by create_helper
can leak if File::create, the stream loop, write_all, or flush fails. Refactor
the logic in backup.rs so remove_helper is guaranteed to run whenever
create_helper succeeds, even on early returns from the download/write path; keep
the cleanup tied to the helper.id lifecycle rather than the end of the success
path.

In `@src/domain/docker_volume/database.rs`:
- Around line 32-44: The `backup` and `restore` methods in `Database` currently
let `FileLock::release(...).await?` override the actual result from
`backup::run`/`restore::run`, which can hide the real operation outcome. Update
these methods so the result from `run` is preserved and any release failure is
handled separately, ideally by logging it while still returning the original
backup/restore result. Use the existing `backup`, `restore`, and
`FileLock::release` symbols to locate the change.

In `@src/domain/docker_volume/restore.rs`:
- Around line 30-89: The helper container cleanup in restore should run even
when an earlier step fails, because `remove_helper` in `restore` is only
executed on the success path after `upload_to_container`. Move the cleanup for
the helper created by `create_helper` into a guaranteed teardown path (for
example, a guard/Drop-based cleanup or a `finally`-style pattern around the
async block) so it executes after `start_container`, `create_exec`/`start_exec`,
`File::open`, or `upload_to_container` errors. Keep the existing success
logging, but ensure the helper identified by `helper.id` is always removed once
created.

---

Nitpick comments:
In `@docker-compose.yml`:
- Line 14: The service is mounting the host Docker socket, which exposes
root-equivalent access to the daemon; keep this mount only for the
helper-container workflow that actually needs backup/restore, and gate it behind
a dedicated profile or environment-specific override so it is not enabled
broadly. If production must use it, replace the direct /var/run/docker.sock bind
in docker-compose with a socket proxy or similarly restricted endpoint and limit
the allowed Docker API surface.

In `@src/domain/docker_volume/backup.rs`:
- Around line 16-17: The backup flow in run is unnecessarily wrapped in
tokio::task::spawn_blocking and futures::executor::block_on even though it is
already async and the bollard/tokio operations can be awaited directly. Remove
the blocking wrapper from the backup path in the run function, keep the async
body as normal awaits, and only introduce blocking code if a specific helper
such as client() truly requires it.

In `@src/tests/domain/docker_volume.rs`:
- Line 27: The Docker-daemon integration tests are hard-failing when no socket
is available because `docker_volume_ping_true_for_existing_volume`,
`docker_volume_backup_captures_files`, `docker_volume_restore_is_clean_replace`,
and `sweep_removes_labeled_helpers` all use `client().expect(...)`. Update these
tests to be gated instead of panicking, either by marking the affected test
functions as ignored for explicit Docker-enabled runs or by adding a runtime
skip when `client()` cannot connect; keep the change localized to the test
module so the daemon-dependent paths still run where Docker is present.

In `@src/tests/domain/mysql.rs`:
- Around line 24-38: The test config literals for DatabaseConfig are duplicated
across many domain tests, so adding fields forces repeated edits. Add a Default
implementation for DatabaseConfig that sets sensible baseline values, then
update the test builders (including mysql.rs and the other database tests) to
construct the struct with ..Default::default() and only override the fields each
test actually needs. This will centralize future field additions and keep the
test setup stable.

In `@src/utils/common.rs`:
- Around line 5-14: The choose_restore_path function has an unused
_extraction_dir parameter and the current multi-file branch just returns the
archive path. Either remove the parameter from choose_restore_path and update
its call sites if it is intentionally unused, or incorporate _extraction_dir
into the path-selection logic if extraction-dir-based resolution is expected for
multi-file dump formats. Keep the behavior consistent with the existing
single-file and fallback cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00d78fdc-f85b-4389-85d5-8700062e5402

📥 Commits

Reviewing files that changed from the base of the PR and between 2c257c5 and bbecca9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • Cargo.toml
  • databases.json
  • docker-compose.yml
  • src/domain/docker_volume/backup.rs
  • src/domain/docker_volume/database.rs
  • src/domain/docker_volume/docker.rs
  • src/domain/docker_volume/mod.rs
  • src/domain/docker_volume/ping.rs
  • src/domain/docker_volume/restore.rs
  • src/domain/factory.rs
  • src/domain/mod.rs
  • src/main.rs
  • src/services/config.rs
  • src/services/restore/archive.rs
  • src/services/restore/executor.rs
  • src/tests/domain/cluster/database.rs
  • src/tests/domain/cluster/mod.rs
  • src/tests/domain/docker_volume.rs
  • src/tests/domain/firebird.rs
  • src/tests/domain/mariadb.rs
  • src/tests/domain/mod.rs
  • src/tests/domain/mongodb.rs
  • src/tests/domain/mssql.rs
  • src/tests/domain/mysql.rs
  • src/tests/domain/postgres.rs
  • src/tests/domain/redis.rs
  • src/tests/domain/valkey.rs
  • src/tests/services/config_tests.rs
  • src/tests/utils/common_tests.rs
  • src/tests/utils/compress_tests.rs
  • src/utils/common.rs
  • src/utils/compress.rs

Comment thread docker-compose.yml
Comment thread src/domain/docker_volume/backup.rs
Comment thread src/domain/docker_volume/database.rs
Comment thread src/domain/docker_volume/restore.rs
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

@RambokDev RambokDev merged commit 1dafdca into main Jul 3, 2026
3 checks passed
@RambokDev RambokDev deleted the feat/docker-volume-backup-restore branch July 3, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant