Skip to content

feat: add Docker Sandboxes runtime provider - #3987

Draft
mdelapenya wants to merge 24 commits into
testcontainers:mainfrom
mdelapenya:codex/sbx-provider-kit
Draft

mdelapenya wants to merge 24 commits into
testcontainers:mainfrom
mdelapenya:codex/sbx-provider-kit

Conversation

@mdelapenya

@mdelapenya mdelapenya commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Adds the optional modules/sbx runtime provider. Importing the module and selecting TESTCONTAINERS_PROVIDER=sbx (or provider=sbx) starts one owned, detached Docker Sandbox before Docker client discovery, so test containers and Ryuk use its Docker Engine. The module passes the published sbx/docker-tcp:latest kit to sbx, promotes the created sandbox with sbx run --detach --name <name>, then verifies the explicitly requested loopback API publication and waits for the Engine. Detached promotion prevents sbx's 30-second session auto-stop from interrupting longer test runs; keeping create first preserves collision-safe ownership.

A local Docker API proxy allocates host TCP/UDP ports before inner container creation and keeps outer and guest port numbers aligned. It manages port and bind-mount lifecycle, streaming and hijacked connections, and reconciliation after Ryuk removes containers. Native Compose uses the same managed endpoint. Unsupported network modes and automatic restart policies fail explicitly; sandbox teardown is available through sbx.Cleanup(ctx).

Host binds are limited to the workspace and directories explicitly allowed through TESTCONTAINERS_SBX_BIND_ROOTS. The provider inspects requested paths through os.Root, preserves configured aliases and nested-root permissions, and rejects parent components, escaping or absolute symlinks, and unsupported special files before forwarding. External binds, including temporary directories and Compose service volumes, require configuration before initialization. Root identity is checked before mounting, but pathname replacement can still race the external sbx mount operation.

Core file-copy archives now encode root-relative member names while preserving absolute container destinations and nested directories. This fixes the strict tar validation failure exposed by the sandbox Engine. The public copy APIs have fake-Engine regressions using strict tar readers; real sbx exec assertions use the documented stream-decoding option.

CI adds Go 1.26.x and 1.27.x sbx runtime legs on Ubuntu 24.04 through the shared Go workflow and its existing module dependency hook. Each leg runs the complete root Go-module ./... suite without retries, the sbx module suite, and separate Compose routing/native integration checks. Three core tests explicitly skip unsupported host/container network modes. Reuse and Ryuk subprocess fixtures share the parent's sandbox Engine, and bootstrap remains lazy for configuration and fake-runtime tests. Each suite has its own report; sandbox cleanup failures fail the tests.

Live sbx legs run for relevant pushes to main and same-repository PRs using DOCKERHUB_USERNAME and DOCKERHUB_TOKEN, with a unique CLI namespace per run, attempt, and Go leg. Fork PRs retain ordinary lint/unit checks and skip the authenticated matrix. The final CI gate uses the same condition. The runtime guide documents local setup, cleanup, supported behavior, and host test commands.

Why is it important?

Provider initialization selects the sandbox Engine before any container or network can bootstrap Ryuk. Allocating the host port first prevents an inner Engine's random port choice from conflicting with a port already occupied on the host. The runtime remains optional through a separate Go module.

Related issues

How to test this PR

Local validation passed: pinned core golangci-lint 2.14.0, compile-only checks for every harness mode on Go 1.26.8 and Go 1.27.2, sbx unit tests with the race detector, Compose routing tests, and existing configuration/fake-runtime/session tests under the actual injected bootstrap. VM-free regressions verify inherited-provider validation, lazy property-file handling, failed-stage propagation, subsequent-stage execution, cleanup, and refusal to overwrite existing bootstrap files. Actionlint, shell checks, and path-selection regressions pass; temporary bootstrap files are removed and module metadata remains unchanged. The module's generated Dependabot/editor metadata and runtime guide are included.

Bind authorization also passes the full sbx race suite with a private allowed root, go vet, golangci-lint 2.14.0, and the compile-only module harness. Regressions cover rejected API requests without backend or port side effects, aliases, nested roots, symlink ordering, root replacement, read-only mounts, and concurrent cleanup.

On a host with sbx v0.48.0 or newer, virtualization support, and a Docker Hub login:

sbx login
unset TESTCONTAINERS_SBX_APP_NAME
GOFLAGS=-v ./scripts/test-sbx.sh module
GOFLAGS=-v ./scripts/test-sbx.sh core
GOFLAGS=-v ./scripts/test-sbx.sh compose

core runs the full root-module suite; all runs core plus the module and Compose stages. No real sandbox was started in this development environment because nested virtualization is unavailable. The current full runtime suite still requires host or eligible CI validation; this fork PR intentionally skips the authenticated sbx matrix. This PR remains a draft.

Register an optional sbx runtime before Docker discovery and run test
containers and Ryuk inside one owned Docker Sandbox. Embed the Docker TCP
kit source, let sbx build it, and verify the exact requested loopback API
publication before connecting.

Reserve host ports before inner container creation, reconcile forwarding
through container lifecycle events, and route native Compose through the
managed endpoint. Add dedicated change-triggered sbx CI, a host test
harness, and runtime documentation.

Co-Authored-By: GPT-5.6 Sol <[email protected]>
Co-Authored-By: GPT-6 Sol <[email protected]>
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Co-Authored-By: GPT-6 Luna <[email protected]>
@mdelapenya mdelapenya added the feature New functionality or new behaviors on the existing one label Oct 15, 2026
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for testcontainers-go ready!

Name Link
🔨 Latest commit 06306e7
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-go/deploys/6ad9e7afff9f203629b87fd4
😎 Deploy Preview https://deploy-preview-3987--testcontainers-go.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

mdelapenya and others added 3 commits October 15, 2026 11:54
Encode container copy destinations as root-relative POSIX tar members
while preserving nested paths and the Engine extraction destination.
Reject invalid or escaping relative destinations and cover both public
copy methods with strict tar readers and a fake Engine.

Use the documented Multiplexed option in sbx integration exec assertions
so they compare decoded payloads instead of Docker stream framing.

Co-Authored-By: GPT-5.6 Sol <[email protected]>
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Co-Authored-By: GPT-6 Luna <[email protected]>
Move the CI-only setup helper into .github/scripts and update workflow
and change-detection references. Preserve the developer-facing host
test runner at scripts/test-sbx.sh.

Co-Authored-By: GPT-6 Luna <[email protected]>
Pass sbx/docker-tcp:latest directly to the CLI and remove embedded source
materialization. Preserve sandbox cleanup ownership and document verbose
host test output.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Co-Authored-By: GPT-6 Luna <[email protected]>
Comment thread modules/sbx/mounts.go Fixed
Comment thread modules/sbx/mounts.go Fixed
Comment thread modules/sbx/mounts.go Fixed
mdelapenya and others added 9 commits October 15, 2026 12:37
Promote a newly created sandbox with run --detach before Docker discovery.
Create releases its CLI session, which otherwise lets sbx stop the sandbox
after the session grace period and interrupt longer test runs.

Keep collision-safe creation and fresh-context cleanup on promotion failure.
Cover the command sequence, timeout/error cleanup, and idle survival beyond
the 30-second grace period in the opt-in integration suite.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Pass DOCKERHUB_USERNAME and DOCKERHUB_TOKEN to the reusable sbx workflow
and read both credentials from secrets. Align the login checks and
documentation with the organization's existing credential names.

Co-Authored-By: GPT-6 Luna <[email protected]>
Refresh Dependabot and the VS Code workspace for the new sbx module.
This keeps modulegen refresh from leaving changes that fail CI's tidy
clean-tree check.

Co-Authored-By: GPT-6 Luna <[email protected]>
Refresh the sbx module metadata for dependencies already updated in the
root module on main. The stale metadata prevented read-only package
loading in the PR merge and made golangci-lint report no Go files.

Co-Authored-By: GPT-6 Luna <[email protected]>
Document host setup, provider registration and selection, explicit
sandbox cleanup, and verbose repository test commands. Link the guide
from System Requirements and the sbx module reference.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Run both supported Go versions through the shared dependency hook and
credentialed sbx runtime path for main pushes and same-repository PRs.
Keep subprocess reuse and Ryuk checks on the parent sandbox daemon,
preserve lazy configuration, and report module and Compose supplements.

Co-Authored-By: GPT-5.6 Sol <[email protected]>
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Co-Authored-By: GPT-6 Luna <[email protected]>
Reuse the existing core module classification and configure sbx-specific
paths in the changed-files action. Classify rename sources as deletions
so changes moving between modules retain the same test coverage.

Remove the redundant sbx detector and its fixture script.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Document provider initialization and cleanup with concise Go comments.
Make authenticated sbx a prerequisite and show local and CI login,
including the configured CLI namespace.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Co-Authored-By: GPT-6 Luna <[email protected]>

Copilot AI 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.

🟡 Changes recommended

Port reservation errors, concurrent lease handling, and persistent cleanup errors remain unresolved.

5 open findings
What changed in this PR

Adds an optional Docker Sandboxes provider using the existing runtime registry, allowing Testcontainers and native Compose to share a managed sandbox Engine.

Changes:

  • Adds sandbox initialization, cleanup, port forwarding, and bind-mount management.
  • Routes Compose and subprocess tests through the selected runtime and fixes file-copy archive paths.
  • Adds regression tests, authenticated CI coverage, and setup documentation.
File Description
scripts/​test-sbx.sh Runs sandbox test stages.
runtime_subprocess_test.go Shares runtime settings with child processes.
reaper_cleanup_test.go Shares the sandbox for Ryuk checks.
provider_test.go Isolates provider autodetection checks.
modules/​sbx/​transport.go Configures Docker HTTP transport.
modules/​sbx/​test_helpers_test.go Provides sandbox test doubles.
modules/​sbx/​sbx.go Initializes and cleans up owned sandboxes.
modules/​sbx/​sbx_test.go Tests provider initialization and cleanup.
modules/​sbx/​sbx_integration_test.go Tests live sandbox behavior.
modules/​sbx/​proxy_test.go Tests API forwarding and streaming.
modules/​sbx/​proxy_regression_test.go Covers proxy lifecycle regressions.
modules/​sbx/​ports.go Plans and manages port publications.
modules/​sbx/​ports_test.go Tests port ownership and recovery.
modules/​sbx/​mounts.go Prepares host bind mounts.
modules/​sbx/​mounts_test.go Tests mount validation and symlinks.
modules/​sbx/​Makefile Adds the sandbox test entry point.
modules/​sbx/​leases.go Tracks and reconciles port leases.
modules/​sbx/​json.go Validates recognized Docker JSON fields.
modules/​sbx/​json_regression_test.go Tests JSON validation and preservation.
modules/​sbx/​go.sum Records dependency checksums.
modules/​sbx/​go.mod Defines the optional module.
modules/​sbx/​events.go Watches container lifecycle events.
modules/​sbx/​engine.go Queries Engine state for reconciliation.
modules/​sbx/​contracts.go Defines runtime and proxy interfaces.
modules/​sbx/​config.go Reads sandbox configuration.
modules/​sbx/​command.go Executes bounded CLI commands.
modules/​compose/​sbx_integration_test.go Tests native Compose on sbx.
modules/​compose/​runtime_provider_test.go Tests managed-runtime routing.
modules/​compose/​compose.go Routes native Compose to managed endpoints.
modules/​compose/​compose_local.go Rejects unsupported shell-based Compose usage.
mkdocs.yml Adds sandbox documentation navigation.
generic_test.go Shares the daemon across reuse subprocesses.
file.go Makes archive member paths root-relative.
file_test.go Tests strict archive path handling.
docs/​system_requirements/​using_sbx.md Documents host setup and CI authentication.
docs/​modules/​sbx.md Documents provider usage and limitations.
docker_test.go Skips unsupported sandbox network modes.
docker_copy_unit_test.go Tests copy APIs with strict tar readers.
.vscode/​.testcontainers-go.code-workspace Adds the sbx module workspace.
.github/​workflows/​ci.yml Selects and gates sandbox CI jobs.
.github/​workflows/​ci-test-go.yml Runs authenticated sandbox test stages.
.github/​scripts/​setup-sbx.sh Installs sandbox prerequisites.
.github/​scripts/​sbx-test-main.go.tmpl Registers providers and handles test cleanup.
.github/​scripts/​modules/​sbx/​install-dependencies.sh Connects sbx setup to the dependency hook.
.github/​dependabot.yml Adds sbx dependency updates.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread modules/sbx/leases.go
Comment thread modules/sbx/leases.go
Comment thread modules/sbx/engine.go Outdated
Comment thread modules/sbx/ports.go Outdated
Comment thread modules/sbx/sbx.go
mdelapenya and others added 5 commits October 20, 2026 11:55
Allow workspace binds by default and require TESTCONTAINERS_SBX_BIND_ROOTS
for external host directories. Resolve requested paths through os.Root,
preserve configured aliases, and reject escaping symlinks and special files.

Check root identity before the sbx mount handoff and close directory handles
with the proxy. Cover authorization, aliases, lifecycle, and read-only binds;
document the configuration and prepare a private root for integration tests.

Co-Authored-By: GPT-5.6 Sol <[email protected]>
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Keep Dependabot's module globs and reconcile the sbx module with the
updated root dependencies.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Reserve Docker public ports and serialize orphan cleanup with allocation.
Read immutable lease tuples without copying concurrent lifecycle state.

Co-Authored-By: GPT-5.6 Sol <[email protected]>
Keep the current shutdown error while allowing later cleanup to succeed.
Retain ownership when sandbox removal must be retried.

Co-Authored-By: GPT-6.1 Sol <[email protected]>

Copilot AI 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.

Comment thread modules/sbx/leases.go
Comment thread modules/sbx/leases.go
mdelapenya and others added 2 commits October 21, 2026 15:36

Copilot AI 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.

🔵 Needs a closer look

The concurrent runtime and filesystem integration still require live-sandbox validation and final human review.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Exclude later fixed ports from ephemeral allocation

modules/​sbx/​ports.go:93

Ephemeral allocation can consume a fixed port requested later in the same create request. For example, with 80/tcp random and 81/tcp fixed at 40000, an ephemeral candidate of 40000 makes planning fail as “already owned” instead of trying another port. Valid mixed bindings can therefore fail intermittently. Exclude every requested fixed protocol/port tuple from ephemeral selection before planning any bindings. Add a deterministic regression where the first candidate matches a later fixed binding.

7 resolved since last review

Review effort: Balanced

mdelapenya and others added 2 commits October 21, 2026 16:35
Merge main and align the sbx properties dependency with the core library
so lint can load its packages in the pull request merge checkout.

Co-Authored-By: GPT-6 Luna <[email protected]>
Collect requested fixed protocol/port pairs before choosing random ports.
Add deterministic coverage for mixed bindings and protocol isolation.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
@mdelapenya

Copy link
Copy Markdown
Member Author

Fixed this allocation-order finding in f51b0ecd. The planner excludes every requested fixed protocol/port pair before choosing ephemeral ports. The deterministic regression tries 40000 first, skips it, and selects 40001 while preserving the later fixed binding. It also covers UDP, a later binding in the same slice, and TCP/UDP independence.

The regression failed before the fix. The sbx non-live race suite, go vet, and golangci-lint 2.14.0 pass.

Copilot AI 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.

🔵 Needs a closer look

The runtime lifecycle and integration changes need final human review and live sandbox validation, which remains pending.

1 open issue

Review effort: Balanced

Comment thread scripts/test-sbx.sh Outdated
Comment on lines +116 to +118
go list -f '{{.Dir}} {{.Name}}' ./... > "$work_dir/core-packages"
make_workspace core
while read -r directory package; do

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 06306e7e: the core package inventory now uses tabs and an explicit tab IFS, preserving checkout paths containing spaces.

bash -n and isolated core-stage checks pass for normal/spaced paths and failure cleanup (real go list/go work; mocked go test). Original comment.

Use tab-delimited package inventory when adding core test bootstraps.

Co-Authored-By: GPT-6.1 Sol <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New functionality or new behaviors on the existing one

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants