Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/minor-docker-sbx-runtime-support.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3463,9 +3463,9 @@
},
"runtime": {
"type": "string",
"description": "Container runtime for the agent container. Use 'gvisor' to run the agent under gVisor's runsc runtime for additional kernel-level isolation. Requires sandbox.agent.sudo: true (root access is needed to install and register runsc). Incompatible with runner.topology: arc-dind.",
"enum": ["gvisor"],
"examples": ["gvisor"]
"description": "Container runtime for the agent container. Use 'gvisor' to run the agent under gVisor's runsc runtime for additional kernel-level isolation. Use 'docker-sbx' to run the agent inside a Docker sbx microVM with KVM hypervisor-level isolation — requires sandbox.agent.sudo: true, DOCKER_PAT and DOCKER_USERNAME secrets, and a KVM-capable runner. Incompatible with runner.topology: arc-dind.",
"enum": ["gvisor", "docker-sbx"],
"examples": ["gvisor", "docker-sbx"]
},
"config": {
"type": "object",
Expand Down
16 changes: 16 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import (
_ "embed"
"encoding/json"
"fmt"
"slices"
"strings"
"sync"

Expand Down Expand Up @@ -425,6 +426,21 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) {
awfConfigLog.Printf("Network section: isolation enabled with %d topology attachments", len(awfConfig.Network.TopologyAttach))
}

// docker-sbx: the sbx microVM resolves host services via host.docker.internal
// (the Docker bridge gateway, 172.17.0.1). Allow this domain so AWF's network
// policy permits connections from the microVM to the api-proxy, MCP gateway, and
// Squid proxy that are all published on the host bridge.
if isDockerSbxRuntime(config.WorkflowData) {
if awfConfig.Network == nil {
awfConfig.Network = &AWFNetworkConfig{}
}
const hostDockerInternal = "host.docker.internal"
if !slices.Contains(awfConfig.Network.AllowDomains, hostDockerInternal) {
awfConfig.Network.AllowDomains = append(awfConfig.Network.AllowDomains, hostDockerInternal)
awfConfigLog.Printf("Network section: added %s for docker-sbx microVM routing", hostDockerInternal)
}
}

if platformType := extractPlatformType(config.WorkflowData); platformType != "" {
awfConfig.Platform = &AWFPlatformConfig{Type: platformType}
awfConfigLog.Printf("Platform section: type=%s", platformType)
Expand Down
10 changes: 10 additions & 0 deletions pkg/workflow/awf_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,16 @@ func BuildAWFArgs(config AWFCommandConfig) []string {
awfArgs = append(awfArgs, "--tty")
}

// docker-sbx: tell AWF to launch the agent inside a Docker sbx microVM instead
// of as a standard Docker Compose service. Guard on the effective AWF version so
// older binaries do not receive an unknown flag.
if isDockerSbxRuntime(config.WorkflowData) && awfSupportsContainerRuntime(firewallConfig) {
awfArgs = append(awfArgs, "--container-runtime", "sbx")
awfHelpersLog.Print("Added --container-runtime sbx for docker-sbx microVM runtime")
} else if isDockerSbxRuntime(config.WorkflowData) {
awfHelpersLog.Printf("Skipping --container-runtime sbx: AWF version %q is older than required minimum %s", getAWFImageTag(firewallConfig), constants.AWFContainerRuntimeMinVersion)
}

// Pass all environment variables to the container, but exclude every variable whose
// step-env value comes from a GitHub Actions secret. AWF's API proxy (--enable-api-proxy)
// handles authentication for these tokens transparently, so the container does not need
Expand Down
9 changes: 9 additions & 0 deletions pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ func (e *CodexEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubA
steps = append(steps, generateGVisorInstallStep())
}

// docker-sbx must be installed, authenticated, and smoke-tested BEFORE AWF.
if isDockerSbxRuntime(workflowData) {
steps = append(steps, generateDockerSbxKVMCheckStep())
steps = append(steps, generateDockerSbxSecretsCheckStep())
steps = append(steps, generateDockerSbxInstallStep())
steps = append(steps, generateDockerSbxAuthAndDaemonStep())
steps = append(steps, generateDockerSbxPreFlightStep())
}

// Install AWF binary (or skip if custom command is specified)
awfInstall := generateAWFInstallationStep(awfVersion, agentConfig)
if len(awfInstall) > 0 {
Expand Down
154 changes: 154 additions & 0 deletions pkg/workflow/docker_sbx_install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// This file generates the GitHub Actions steps required to install, authenticate,
// and pre-flight-test the Docker sbx microVM runtime for sandbox.agent.runtime: docker-sbx.
//
// The steps emitted are (in order):
// 1. KVM availability check – fails fast when nested virtualisation is absent.
// 2. Docker Hub secrets check – fails fast when DOCKER_PAT / DOCKER_USERNAME are missing.
// 3. sbx installation – adds the Docker apt repo and installs the docker-sbx package.
// 4. sbx auth & daemon – authenticates with Docker Hub, starts the daemon, resets and
// re-initialises the allow-all policy, then pre-pulls the template image.
// 5. sbx pre-flight smoke – creates a throwaway sandbox, execs a command, then removes it.
//
// All five steps must be injected BEFORE the AWF installation step so the sbx runtime
// is available when AWF starts the agent inside a microVM.

package workflow

// generateDockerSbxKVMCheckStep creates a fail-fast step that verifies the runner
// has KVM support before spending time on sbx installation.
func generateDockerSbxKVMCheckStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: Check KVM availability for docker-sbx",
" run: |",
" set -euo pipefail",
` echo "::group::KVM availability check"`,
` if ! lsmod | grep -q kvm; then`,
` echo "::error::KVM kernel module is not loaded. docker-sbx requires a KVM-capable runner with nested virtualisation enabled."`,
` exit 1`,
` fi`,
` if ! test -e /dev/kvm; then`,
` echo "::error::/dev/kvm is missing. docker-sbx requires the KVM device to be present on the runner."`,
` exit 1`,
` fi`,
` echo "KVM is available and /dev/kvm is present ✅"`,
` echo "::endgroup::"`,
})
}

// generateDockerSbxSecretsCheckStep creates a fail-fast step that verifies the
// DOCKER_PAT and DOCKER_USERNAME secrets are present before attempting sbx install.
func generateDockerSbxSecretsCheckStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: Check Docker Hub secrets for docker-sbx",
" env:",
" DOCKER_PAT_VAL: ${{ secrets.DOCKER_PAT }}",
" DOCKER_USERNAME_VAL: ${{ secrets.DOCKER_USERNAME }}",
" run: |",
" set -euo pipefail",
` echo "::group::Docker Hub secrets check"`,
` if [[ -z "${DOCKER_PAT_VAL}" ]]; then`,
` echo "::error::secrets.DOCKER_PAT is empty. docker-sbx requires a Docker Hub personal access token to pull the sandbox template image. Add a DOCKER_PAT secret to your repository."`,
` exit 1`,
` fi`,
` if [[ -z "${DOCKER_USERNAME_VAL}" ]]; then`,
` echo "::error::secrets.DOCKER_USERNAME is empty. docker-sbx requires a Docker Hub username. Add a DOCKER_USERNAME secret to your repository."`,
` exit 1`,
` fi`,
` echo "Docker Hub secrets are present ✅"`,
` echo "::endgroup::"`,
})
}

// generateDockerSbxInstallStep creates a GitHub Actions step that installs the
// docker-sbx package via the official Docker apt repository.
func generateDockerSbxInstallStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: Install docker-sbx",
" run: |",
" set -euo pipefail",
` echo "::group::Install docker-sbx"`,
` # Add Docker apt repo without installing Docker Engine (already present).`,
` curl -fsSL https://get.docker.com | sudo REPO_ONLY=1 sh`,
` sudo apt-get install -y docker-sbx`,
` sbx version`,
` # Fix KVM permissions so the runner user can create microVMs.`,
` sudo chmod 666 /dev/kvm`,
` echo "docker-sbx installed successfully ✅"`,
` echo "::endgroup::"`,
})
}

// generateDockerSbxAuthAndDaemonStep creates a step that:
// 1. Starts the sbx daemon.
// 2. Authenticates with Docker Hub (both docker and sbx CLIs).
// 3. Resets and re-initialises the sbx policy (required for mount policy).
// 4. Restarts the daemon and re-authenticates.
// 5. Pre-pulls the sandbox template image.
func generateDockerSbxAuthAndDaemonStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: Start docker-sbx daemon and authenticate",
" env:",
" DOCKER_PAT_VAL: ${{ secrets.DOCKER_PAT }}",
" DOCKER_USERNAME_VAL: ${{ secrets.DOCKER_USERNAME }}",
" run: |",
" set -euo pipefail",
` export DOCKER_CONFIG="$(mktemp -d)"`,
` trap 'rm -rf "${DOCKER_CONFIG}"' EXIT`,
` echo "::group::Start sbx daemon"`,
` nohup sbx daemon start > /tmp/sbx-daemon.log 2>&1 &`,
` # Poll until daemon is running (up to 10 s).`,
` for i in $(seq 1 10); do`,
` if sbx daemon status 2>/dev/null | grep -q -i running; then`,
` echo "sbx daemon is running"`,
` break`,
` fi`,
` sleep 1`,
` done`,
` echo "::endgroup::"`,
` echo "::group::Authenticate with Docker Hub"`,
` printf '%s' "${DOCKER_PAT_VAL}" | docker login --username "${DOCKER_USERNAME_VAL}" --password-stdin`,
` printf '%s' "${DOCKER_PAT_VAL}" | sbx login --username "${DOCKER_USERNAME_VAL}" --password-stdin`,
` echo "::endgroup::"`,
` echo "::group::Reset and initialise sbx policy"`,
` sbx daemon stop || true`,
` sbx policy reset --force || true`,
` sbx policy init allow-all`,
` nohup sbx daemon start > /tmp/sbx-daemon.log 2>&1 &`,
` for i in $(seq 1 10); do`,
` if sbx daemon status 2>/dev/null | grep -q -i running; then`,
` echo "sbx daemon restarted"`,
` break`,
` fi`,
` sleep 1`,
` done`,
` printf '%s' "${DOCKER_PAT_VAL}" | docker login --username "${DOCKER_USERNAME_VAL}" --password-stdin`,
` printf '%s' "${DOCKER_PAT_VAL}" | sbx login --username "${DOCKER_USERNAME_VAL}" --password-stdin`,
` echo "::endgroup::"`,
` echo "::group::Pre-pull sandbox template image"`,
` docker pull docker/sandbox-templates:shell-docker`,
` echo "Template image ready ✅"`,
` echo "::endgroup::"`,
})
}

// generateDockerSbxPreFlightStep creates a step that verifies the sbx stack works
// end-to-end before the MCP gateway and AWF container setup begins.
func generateDockerSbxPreFlightStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: docker-sbx pre-flight smoke test",
" run: |",
" set -euo pipefail",
` echo "::group::docker-sbx pre-flight smoke test"`,
` sandbox_name="test-sandbox-direct"`,
` cleanup() {`,
` sbx stop "${sandbox_name}" >/dev/null 2>&1 || true`,
` sbx rm --force "${sandbox_name}" >/dev/null 2>&1 || true`,
` }`,
` trap cleanup EXIT`,
` echo "y" | sbx create shell --name "${sandbox_name}" "${GITHUB_WORKSPACE}"`,
` sbx exec "${sandbox_name}" uname -a`,
` sbx stop "${sandbox_name}"`,
` echo "✅ sbx ready"`,
` echo "::endgroup::"`,
})
}
Loading
Loading