Claude Cursor Skill

docker-sandboxes-env

Use this skill when authoring, planning, or running a declarative `sbxenv.yaml` file for Docker Sandboxes (`sbx env create/run/plan/exec/rm`), even if the user just says they want to "check in a sandbox config", "make onboarding reproducible for a sandbox", "run a setup script be

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download docker-skills-skills_docker-sandboxes-env-3e1cbd1.zip · 14 KB
docker/skills 436 23 forks Apache-2.0 Updated 11h ago
Part of docker/skills — 11 skills

Install

skills CLI npx skills add https://github.com/docker/skills/tree/main/skills/docker-sandboxes-env
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install docker-skills@llmmart
Git git clone https://github.com/docker/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole docker/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Docker Sandboxes: Declarative sbxenv.yaml Environments

Overview

sbxenv.yaml (schemaVersion "1", EXPERIMENTAL) declaratively describes one sandbox environment — agent, mixin kits, workspace mounts, environment variables, secrets/registries/bindings to provision, MCP servers, ports, and host-side lifecycle commands — so sbx env create|run|plan|exec|rm can stand it up and tear it down reproducibly instead of a long flag invocation. This skill owns that file format end to end. It delegates the sandbox lifecycle semantics it wraps, the credential/network model it provisions into, and the kit schema its kits: entries reference, to their own skills.

When to use this skill

Activate this skill when:

  • The user wants a checked-in, reproducible definition of a sandbox environment instead of a long sbx create/sbx run command line.
  • The user wants host-side setup/teardown commands (cloning a repo, seeding fixtures, archiving state) tied to a sandbox's create/attach/remove lifecycle.
  • The user wants to parameterize a shared environment file with named arguments (args: + --env-arg).
  • The user is debugging why sbx env create/run is asking for approval, or why a file, kit, or secret it declares was skipped or flagged.

Do not use this skill when

Do not use this skill when:

  • The task is the underlying sbx create/run/rm flag-based workflow with no sbxenv.yaml file involved — use docker-sandboxes-lifecycle.
  • The task is choosing network policy or storing a secret/registry credential independent of any environment file — use docker-sandboxes-network-credentials (this skill's secrets:/ registries:/bindings: blocks provision into that same store, but do not redefine its rules here).
  • The task is authoring the kit spec.yaml a kits: entry points at — use docker-sandboxes-kits.

Core guidance

File resolution and required fields

  • The file sbx env reads from a directory is exactly sbxenv.yaml — no other name, and a directory-named .sbxenv.yaml at the project level is not read as a project's own file (only the home-directory base layer uses that hidden name; see below).
  • Every environment file requires schemaVersion: "1" and agent: (a built-in agent name or the manifest name of an agent kit supplied via kits:). Everything else is optional. agent: shell needs no credentials and is the simplest way to validate a file's mechanics.
  • sbx env create|run|plan|exec|rm accept one or more PATH arguments. Each PATH is either a directory (resolved to <PATH>/sbxenv.yaml) or the file itself. Passing more than one deep-merges them in declaration order — docker compose -f-style semantics: later files override earlier ones, mappings merge key-by-key, sequences concatenate.
    sbx env create sbxenv.yaml override.yaml
    

Naming, workspace, and the .sbxenv.yaml user base layer

  • Unless the file sets name: or --name overrides it, the sandbox is named after the mounted directory (or the project directory when nothing is mounted) — so an environment that mounts nothing is still the same sandbox every time it is applied. Two different environment files in the same directory derive the same sandbox name and collide unless each sets its own name: (or you pass a distinct --name per invocation) — always give each environment its own explicit name: when more than one may exist in the same directory.
  • workspace: names the read/write mount, exactly like sbx create's omitted-path behavior: omitting workspace: mounts nothing at all. A relative workspace: path resolves against the directory of the file that declares it — workspace: . mounts the directory the file sits in. ${{ env.projectDir }} names the project directory (the one holding the first PATH, or cwd when none is named); ${{ env.fileDir }} names the declaring file's own directory. Nothing else is expanded — a bare $ is literal text, so a value written for the container (PATH: $PATH:/opt/bin) reaches it unchanged.
    workspace: .                       # mounts the directory this file sits in
    # workspace: ${{ env.projectDir }} # mounts the project directory explicitly
    
  • Relative kit sources follow the same file-directory anchoring rule as workspace: (see docker-sandboxes-kits for kit reference syntax).
  • Files within a mounted workspace get default read-only masking, and that protection is complete only when the file sits directly at the mount's own root. A read-only bind at the mount point cannot be renamed by the sandbox — there is nothing above it inside the mount to rename. But an environment file in a subdirectory of a read-write mount is protected only at its current path: the sandbox can rename the containing directory (which it can write to) and then recreate the original path itself, landing a sandbox-controlled file back where the read-only bind no longer applies. sbx env plan calls this gap out explicitly for a file that is not at a mount's root. Do not claim renaming the containing directory creates no gap — for anything but the mount root, it does.
  • With no PATH given, an .sbxenv.yaml in the home directory is merged underneath as a base layer for defaults shared across projects; naming any PATH skips this layer entirely. The base layer may not set name: (which identifies one project) and its workspace: must be rooted at ${{ env.projectDir }} — any other value would mount one fixed directory under every project that merges it.

args: — parameterizing a shared file

  • Declare named inputs under args:, each with a default (making it optional, default: "" counts as a real default) or required: true (mutually exclusive), plus optional description, enum, or pattern.
  • Reference one as ${{ env.args.NAME }} anywhere a value appears in the file, and supply it with --env-arg NAME=VALUE (repeatable) or --env-args-file PATH.

lifecycle: — host commands and the approval plan

  • lifecycle: declares shell commands that run on the host, outside the sandbox, with your own privileges — not inside the container. Three phases, run in this order per invocation:
    • initialize — runs on every create and run, including one that only attaches to an existing sandbox. It is the one phase that can produce what the environment needs to exist (a cloned workspace, a generated file), so it must be idempotent — it reruns on every reattach.
    • postCreate — runs once, after the sandbox exists, before an interactive attach takes the terminal.
    • preRemove — runs before sbx env rm deletes the sandbox, while sbx env exec can still reach it. A failing preRemove is only a warning — the failure itself does not block removal. After the hook, removal rechecks the approved destroy plan and sandbox identity. A new credential or changed binding not covered by that approval, or a replacement sandbox under the same name, stops removal before deletion. Review the new destroy plan before retrying.
    • sbx env exec runs no lifecycle commands at all, and requires the sandbox to already exist — it does not create one. Run sbx env create/sbx env run first.
    lifecycle:
      initialize:
        - command: test -d app || git clone https://github.com/acme/app
      postCreate:
        - command: ./scripts/seed-fixtures.sh
      preRemove:
        - command: ./scripts/archive-state.sh
    
  • Every command runs through the shell from the project directory by default (override per-command with workdir:; bound its runtime with timeout:).
  • A file that declares any lifecycle command is asked about on every invocation that reaches it, whether or not this particular invocation changed anything — approving a command also trusts whatever it invokes, including a script whose contents can change after the answer, so the question is repeated rather than remembered by default. The one exception: sbx settings set env.rememberHostCommands true makes it ask again only when the commands actually change. Never treat an untrusted file's or an untrusted kit's lifecycle commands as pre-approved, and never enable rememberHostCommands for a file whose commands you have not reviewed. An environment that declares no host commands at all, and whose config is otherwise unchanged from what was last approved, applies silently with no prompt. Use --skip-host-commands to run none of the declared commands for one invocation.

The environment plan: what it is and is not

  • sbx env plan [PATH...] prints everything applying the file would set up — host commands, credentials/bindings, MCP registrations, directories, published ports, the sandbox itself, and its variables — compared against what was last applied/approved. It changes nothing.
  • sbx env create/sbx env run show the same plan and require approval before doing any work (--auto-approve/-y skips the prompt for non-interactive use — never default to -y for a file or kit you have not reviewed). A secret's literal value: is the one field shown both in the plan and recorded to state as a sha256: digest rather than in the clear; a ref:/command: secret shows where the credential comes from, not its resolved value.

Secrets, registries, and bindings scoped to the environment

  • secrets: and registries: provision into the same credential store sbx secret set uses, at this environment's sandbox scope, so sbx env rm can remove exactly what it created. Each entry uses the same value/ref/command shape as sbx secret set (exactly one of the three) — see docker-sandboxes-network-credentials for what those mean at runtime and why a literal secret value should not otherwise appear in a checked-in file.
  • bindings: are per-service credential bindings merged into the user's global credentials.yaml; unlike secrets:/registries:, they are left in place by default by sbx env rm (they are user-wide and may be shared with other sandboxes/environments) — pass --prune-bindings to also remove them.
  • Never write a literal secret value directly into a checked-in sbxenv.yaml. Use ref: (1Password/AWS Secrets Manager) or command: so the value never lives in the file at all; if a literal value: is used transiently, both the plan and state show only its digest, but the original environment file still contains the plaintext secret. See the labeled secrets: fragment below for the shape — it is intentionally not part of the minimal asset, which needs no credentials at all to validate.
    # OPTIONAL fragment — add only if this environment actually needs a
    # credential; the minimal asset omits this entirely.
    secrets:
      anthropic:
        ref: op://Private/Anthropic/api-key   # never a literal `value:` in a checked-in file
        refresh: 55m
    

kits:, additionalWorkspaces:, mcp:, ports:, and sandboxOptions:

  • kits: composes mixin kits (and, exactly once, an agent kit whose name matches agent:) onto the base agent; a relative source anchors to the declaring file's own directory, the same rule as workspace:.
  • additionalWorkspaces: mounts extra directories beyond the primary workspace: (a file cannot declare one without the other) — the sbxenv.yaml equivalent of sbx run's extra positional workspace arguments with :ro.
  • mcp.servers: registers MCP servers on the host and adds them to the sandbox's fixed (static) MCP set at create time; registrations are host-global and left in place by sbx env rm.
  • ports: pins explicit host-port bindings for container ports the sandbox exposes — the equivalent of sbx ports --publish — and is torn down automatically when sbx env rm deletes the sandbox.
  • sandboxOptions: (beyond writableEnvFiles, below) maps onto the remaining sbx create flags: template, memory, cpus, pullPolicy, profile, skills.

See references/env-schema-fields.md for the exact field shapes, required keys, and a YAML example for each of the five blocks above.

sandboxOptions.writableEnvFiles — a deliberate, explicit downgrade

  • By default, every environment file mounted inside the workspace is read-only at its own path, even though the rest of the mount is writable. This stops an agent editing the very file that decides what host lifecycle commands and secret-resolving commands run on your machine on the next invocation.
  • Set sandboxOptions.writableEnvFiles: true only where an agent is deliberately meant to edit its own environment file. This is a real security downgrade — the plan then reports the file as writable — so treat it the same as any other explicit trust decision, not a default.
  • The protection is complete only at a mount's own root. A file placed directly at the root of a read-write mount cannot be reached even by renaming, because the sandbox cannot rename the mount point itself. A file in a subdirectory of that mount is a different case: it is read-only at its current path, but the sandbox can rename the directory holding it (which it can write to) and recreate a file at the original path, ending up with a sandbox-controlled file there. sbx env plan flags this gap for a file that is not directly at a mount's root — read the plan's output rather than assuming renaming is always harmless.

Related skills

  • For the sbx create/run/rm flag-based workflow this file wraps, use docker-sandboxes-lifecycle.
  • For what secrets:/registries:/bindings: mean at runtime, and for configuring network policy independent of any environment file, use docker-sandboxes-network-credentials.
  • For the schema of the kit spec.yaml a kits: entry (or agent: pointing at an agent kit) references, use docker-sandboxes-kits.

References

  • references/sources.md — provenance for every rule above (help captures, source paths, docs URLs).
  • references/env-schema-fields.md — exact field shapes and YAML examples for kits:, additionalWorkspaces:, mcp:, ports:, and sandboxOptions:.

Assets

  • assets/sbxenv.yaml — a complete, minimal, safe example: a shell agent mounting the declaring file's own directory, one static env var, and no credentials at all — it validates and plans without any onboarding authentication.

Checks

  • checks/verification.md — Verification runbook for sbxenv.yaml commands (unexecuted runbook; run manually with an isolated, uniquely-named --app-name, never with real secret values or untrusted lifecycle commands auto-approved).
Files (skills)
  • agents
    • openai.yaml 428 B
      interface:
        display_name: 'Docker Sandboxes: Declarative sbxenv.yaml Environments'
        short_description: Author, plan, and run declarative sbxenv.yaml environments for Docker Sandboxes. Experimental.
        default_prompt: Use this skill when authoring or running an sbxenv.yaml file for Docker Sandboxes (`sbx env create/run/plan/exec/rm`), including its lifecycle hooks and approval plan.
      policy:
        allow_implicit_invocation: true
      
  • assets
    • sbxenv.yaml 286 B
      # Complete minimal environment; sbx env is experimental.
      # This file sits at the root of its workspace mount, so default read-only
      # masking protects it from agent edits. No host hooks or provider secrets.
      schemaVersion: "1"
      agent: shell
      workspace: .
      env:
        IS_SANDBOX_ENV_EXAMPLE: "1"
      
  • checks
    • verification.md 4.6 KB
      # Verification Runbook for sbxenv.yaml commands
      
      User-run integration checks; not executed during skill generation. `sbx env`
      is experimental. Require a supported local runtime and an existing Docker
      login; login is shared authentication, not scoped by `--app-name`. These
      files use the shell agent without provider secrets. Run from the skill
      directory in one shell. Review each generated host command before approval.
      
      ## 1. Prepare unique scratch files and an isolated app
      
      ```bash
      APP="env-$(date +%s)-$$"  # unique suffix, at most 20 characters
      WORK=$(mktemp -d)
      mkdir "$WORK/minimal" "$WORK/empty" "$WORK/hooks" "$WORK/remove" "$WORK/never"
      cp assets/sbxenv.yaml "$WORK/minimal/sbxenv.yaml"
      cat > "$WORK/empty/sbxenv.yaml" <<'YAML'
      schemaVersion: "1"
      name: env-check-empty
      agent: shell
      YAML
      cat > "$WORK/hooks/sbxenv.yaml" <<'YAML'
      schemaVersion: "1"
      name: env-check-hooks
      agent: shell
      lifecycle:
        initialize:
          - command: printf 'initialize-ran\n' >> initialize.log
        postCreate:
          - command: printf 'post-create-ran\n' >> post-create.log
      YAML
      cat > "$WORK/remove/sbxenv.yaml" <<'YAML'
      schemaVersion: "1"
      name: env-check-remove
      agent: shell
      lifecycle:
        preRemove:
          - command: exit 1
      YAML
      cat > "$WORK/never/sbxenv.yaml" <<'YAML'
      schemaVersion: "1"
      name: env-check-never
      agent: shell
      YAML
      ```
      Pass: each scenario has its own file and distinct explicit or derived name.
      No files outside the scratch directory are edited.
      
      ## 2. Verify plan-only behavior and omitted workspace
      
      ```bash
      sbx --app-name "$APP" env plan "$WORK/minimal"
      sbx --app-name "$APP" env plan "$WORK/empty"
      ```
      Pass: both print apply plans without creating a sandbox, recording approval,
      or running commands. The first declares the directory holding its file as
      a workspace; the second declares no workspace. Plan itself never prompts;
      this does not prove that a later create/run will apply silently.
      
      ## 3. Verify initialize reruns but postCreate runs only once
      
      ```bash
      sbx --app-name "$APP" policy init balanced
      sbx --app-name "$APP" env run -d "$WORK/hooks"
      sbx --app-name "$APP" env run -d "$WORK/hooks"
      sbx --app-name "$APP" env exec "$WORK/hooks" -- pwd
      cat "$WORK/hooks/initialize.log"
      cat "$WORK/hooks/post-create.log"
      ```
      Approve both invocations interactively after reviewing the plan. Pass: two
      `initialize-ran` lines and one `post-create-ran` line; exec adds neither.
      Initialize runs from the project directory on the host on every create/run,
      including reattachment. PostCreate runs on the host once after creation.
      This assumes the default `env.rememberHostCommands` setting, not an override
      that remembers consent.
      
      ## 4. Verify unchanged approved configuration without host commands is silent
      
      ```bash
      sbx --app-name "$APP" env run -d "$WORK/empty"
      sbx --app-name "$APP" env run -d "$WORK/empty"
      ```
      Approve the first invocation interactively; do not use auto-approve. Pass:
      the second run reuses the same sandbox without an approval prompt because
      nothing changed and the file declares no host commands.
      
      ## 5. Verify preRemove failure does not prevent removal
      
      ```bash
      sbx --app-name "$APP" env create "$WORK/remove"
      sbx --app-name "$APP" env rm "$WORK/remove"
      ```
      Review and approve both the create plan and the destroy plan. Pass: removal
      warns that preRemove did not finish but still removes this sandbox. The
      hook is the known `exit 1` command, not an untrusted archival script.
      
      ## 6. Verify env exec does not create a missing sandbox
      
      ```bash
      sbx --app-name "$APP" env exec "$WORK/never" -- echo hi
      ```
      Pass: fails because `env-check-never` has never been created. A successful
      `echo hi` here would contradict the expected missing-sandbox behavior.
      
      ## 7. Verify mount-root environment file protection
      
      ```bash
      sbx --app-name "$APP" env run -d "$WORK/minimal"
      sbx --app-name "$APP" env exec "$WORK/minimal" -- sh -c 'echo x >> "$1"' sh "$WORK/minimal/sbxenv.yaml"
      ```
      Approve creation. Pass: the write fails with a read-only/permission error.
      The file is directly at the workspace mount root (`workspace: .`). This
      does not prove protection for files in a renameable subdirectory; those
      have the rename gap described in SKILL.md.
      
      ## 8. Clean up only this disposable session
      
      ```bash
      sbx --app-name "$APP" env rm --force "$WORK/minimal"
      sbx --app-name "$APP" env rm --force "$WORK/empty"
      sbx --app-name "$APP" env rm --force "$WORK/hooks"
      sbx --app-name "$APP" daemon stop
      rm -rf "$WORK"
      ```
      Pass: the created environments are removed and the isolated daemon stops.
      Forced removal is consented cleanup of these known test files only. If a
      check failed early, inspect this app and remove any remaining test sandbox
      before stopping its daemon; never substitute the default daemon.
      
  • references
    • env-schema-fields.md 3.6 KB
      # sbxenv.yaml: kits, additionalWorkspaces, mcp, ports, sandboxOptions
      
      Schema-verified against `sandboxlib/sbxenv/types.go` at docker/sandboxes
      commit df5c96ba60484fa2c375469dbac912c205da6c37, and against the narrative
      description in the captured `sbx_env.txt` / `docs/yml/sbx_env.yaml` help.
      
      ## `kits:`
      
      Each entry is either a bare reference or a mapping carrying that kit's own
      arguments:
      
      ```yaml
      kits:
        - ./mixins/base
        - source: ./mixins/tool
          args:
            version: ${{ env.args.channel }}
      ```
      
      A source written as an explicit relative path (`./…`, `../…`, `.`, `..`, or
      one ending in `.zip`) resolves against the **directory of the file that
      declares it** — the same anchoring rule as `workspace:` — so a checked-in
      file reaches the same kits from wherever `sbx` is run. A bare `kits/tool` is
      left exactly as written and is a registry reference, not a directory, even
      if a directory of that name sits beside the file.
      
      `--kit-arg name=value` (every kit) or `--kit-arg kitname.name=value` (one
      kit) overrides a kit's `args:` per invocation on the command line; see
      `docker-sandboxes-kits` for what a kit itself may declare under `args:`.
      
      ## `additionalWorkspaces:`
      
      A list of `{path, readOnly}` entries, each **additional to** the primary
      `workspace:` — a file that declares `additionalWorkspaces:` without a
      `workspace:` fails validation (there is nothing for the extra mount to be
      additional to).
      
      ```yaml
      workspace: .
      additionalWorkspaces:
        - path: /path/to/docs
          readOnly: true
      ```
      
      This is the `sbxenv.yaml` equivalent of `sbx run`'s extra positional
      workspace arguments with a `:ro` suffix — see `docker-sandboxes-lifecycle`
      for the flag form and the single-file read-only-carve-out behavior.
      
      ## `mcp:`
      
      `mcp.servers:` lists MCP servers this environment registers on the host
      (the same resolve + policy-check + persist steps `sbx mcp add` performs)
      and adds to the sandbox's fixed (static) MCP set at `sbx env create` time.
      Each entry needs a `name:` and **exactly one** of `url:` (remote HTTP/SSE
      server or registry/OCI reference) or `command:`+`args:` (local stdio
      server).
      
      ```yaml
      mcp:
        servers:
          - name: fetch
            url: https://registry.modelcontextprotocol.io/v0/servers/fetch-mcp/versions/latest
      ```
      
      This requires the hosted MCP control plane to be configured. Registrations
      are host-global and are intentionally **left in place** by `sbx env rm` —
      they are not sandbox-scoped resources this environment tears down.
      
      ## `ports:`
      
      Pins explicit host-port bindings for container ports the sandbox
      (typically a kit) exposes — the `sbxenv.yaml` equivalent of
      `sbx ports --publish`. Each entry needs `sandbox:` (1–65535, required);
      `host:` (omit for an ephemeral port), `protocol:` (`tcp`/`tcp4`/`tcp6`/
      `udp`/`udp4`/`udp6`), and `hostIP:` are optional.
      
      ```yaml
      ports:
        - sandbox: 8080
          host: 3000
      ```
      
      Publishing happens at `sbx env create`/`sbx env run` and is torn down
      automatically when `sbx env rm` deletes the sandbox. A binding that cannot
      be published (e.g. the host port is already taken) fails the create and
      rolls it back, rather than leaving the sandbox up unpublished.
      
      ## `sandboxOptions:`
      
      Beyond `writableEnvFiles` (covered in the main SKILL.md), `sandboxOptions:`
      maps directly onto `sbx create` flags: `template` (image override),
      `memory`, `cpus`, `pullPolicy` (`always`/`missing`/`never`), `profile`
      (governance profile), and `skills` (`off`/`readonly`/`readwrite`, the
      shared skills store mode). See `docker-sandboxes-lifecycle` for what each
      corresponds to on the plain `sbx create`/`sbx run` command line.
      
      ```yaml
      sandboxOptions:
        memory: 8g
        cpus: 4
        skills: readonly
      ```
      
    • sources.md 7.2 KB
      # Sources
      
      ## Local pinned source (repository docker/sandboxes, commit df5c96ba60484fa2c375469dbac912c205da6c37)
      
      Paths below are relative to the repository root.
      
      - `sandboxlib/sbxenv/types.go` — `Config` struct: `SchemaVersion` (only
        `"1"` supported — `SupportedSbxEnvVersions`), `Name`, `Args`, `Agent`,
        `Kits`/`KitEntry`, `Workspace`/`WorkspaceSpec` (bare-string-or-mapping
        shorthand, `clone`, the `declared` flag distinguishing "omitted" from
        "blank"), `AdditionalWorkspaces`, `Env`, `SandboxOptions` (including
        `WritableEnvFiles` doc comment: "Each file a mount would otherwise hand
        over read-write is bound read-only at its own path instead..."),
        `Secrets`/`SecretSource` (exactly one of value/ref/command),
        `Bindings`, `Registries`/`RegistrySource`, `MCP`/`MCPServer`,
        `Ports`/`PortBinding`, `Lifecycle`. `Validate()`/`validateWorkspaces()` —
        confirms omitting `workspace:` mounts nothing, a blank path is rejected,
        and `additionalWorkspaces` requires a primary `workspace:`.
      - `sandboxlib/sbxenv/loader.go` — `DefaultFileName` (`sbxenv.yaml`, the only
        name read from a directory), `UserBaseFileName` (`.sbxenv.yaml`, home-only
        base layer), `LoadMergedWithOptions` doc comment (docker-compose `-f`
        merge semantics: mappings merge key-by-key, sequences concatenate,
        scalars overridden by the last file), `rejectProjectIdentity`/
        `rejectEscapedUserBaseWorkspace` (base layer may not set `name:`, and its
        `workspace:` must resolve at-or-below the project directory),
        `anchorWorkspacePaths`/`anchorKitSources` (relative workspace and kit
        paths resolve against the **declaring file's own directory**), `${{
        env.projectDir }}` / `${{ env.fileDir }}` expansion.
      - `sandboxlib/sbxenv/lifecycle.go` — `LifecyclePhase` constants
        (`initialize`, `postCreate`, `preRemove`) and their doc comments: phase
        ordering, "initialize runs before anything is resolved... on both a
        create and an attach... must be idempotent", `postCreate` "runs once the
        sandbox exists", `preRemove` "runs before sbx env rm deletes the
        sandbox... sbx env exec runs no commands at all"; `LifecycleCommand`
        (`Name`, `Command` run via `sh -c`/`cmd /c`, `Workdir` default =
        project directory, `Timeout`).
      - `cli-plugin/commands/env_lifecycle.go` — `envContext.teardown` warns on
        a failed `preRemove`, then calls `recheckDestroy` and `recheckSandbox`
        before deletion. The failure itself is not fatal; drift from the approved
        destroy plan or replacement of the sandbox is.
      - `cli-plugin/commands/env_plan_test.go` —
        `TestTeardown_ACommandThatFailedIsNotADeadEnd` verifies warning-only hook
        failure; `TestRecheckDestroy_SomethingThatAppearedAfterTheAnswer`,
        `TestTeardown_WhatAPreRemoveCommandLeftBehind`, and
        `TestTeardown_ASandboxReplacedWhileTheCommandsRan` verify the
        post-approval credential/binding/identity guards stop deletion.
      - `sandboxlib/sbxenv/args.go` — `Args` map, `argNamePattern`, the
        distinction between author bugs (`ErrArgSyntax`, `ErrArgUndeclared`) and
        caller errors (`ErrArgUnresolved`, `ErrArgInvalid`, `ErrArgUnused`); "A
        bare `name` with no `=` is rejected rather than resolved from the host
        environment the way `--env-file` does" (confirms args never read the
        ambient host environment implicitly).
      - `cli-plugin/commands/env_plan.go` — `valueFingerprint` and
        `secretSourceFields` replace literal secret values with full `sha256:`
        digests in both the displayed plan and recorded state.
      - `cli-plugin/commands/env_plan.go` — `reachedEnvFile` (`root` field:
        whether the file sits directly at the mount's own directory, "where a
        read-only bind of it cannot be worked around: the mount point is the one
        directory in the tree the sandbox cannot rename"), `envFileMounts`
        (excludes only a file that is both read-only AND at the mount root — a
        file protected read-only in a *subdirectory* is still reported, because
        "Nothing in the sandbox can rename a mount point" is true only at the
        root), `envContext.lifecycleResources` and `planOptions.unspoken` (a
        lifecycle-declaring file's commands are named in the plan every
        invocation that reaches them).
      - `cli-plugin/commands/env_plan_render.go` — `renderWritableFiles`/
        `renderEnvFileReach` (exact two-case wording: "mounted read-write into
        the sandbox... an agent in it can change what a later invocation of this
        environment runs here" vs. "mounted read-only, inside a directory the
        sandbox can rename: ... renaming it puts the file back where an agent
        can change what a later invocation runs here" — the read-only-at-root
        case is not rendered at all, since `envFileMounts` excludes it),
        `renderHostCodeNotice` (`askedAgain` case: "nothing in this env plan has
        changed but commands run on this machine, outside the sandbox, with your
        own privileges which requires explicit approval on each run" — confirms
        declared lifecycle commands are asked about every invocation regardless
        of whether anything changed; the non-`askedAgain`, no-host-code path
        implies a plan with nothing to add and no lifecycle commands has nothing
        new to say).
      
      ## Captured standalone CLI help, cross-checked against an older installed build
      
      Installed `sbx` reports `v0.42.0-503-g951b7f6d7` (commit
      `951b7f6d7f6bb260fac15077b607109ffe8ae012`), older than the pinned source
      HEAD. Verified with `sbx <cmd> --help` under an isolated `--app-name`, no
      daemon started. The generated help text is long-form prose and matches the
      pinned-source doc comments above closely enough that no source-only schema
      difference was found for the fields this skill covers.
      
      - `sbx env --help` (matches `docs/yml/sbx_env.yaml` at the pinned commit) —
        full narrative description: file resolution rules, `kits:` bare-vs-mapping
        shorthand and relative-path anchoring, `workspace:` resolution and naming
        derivation, the complete `lifecycle:` phase description, the full
        environment-plan rendering model (`+`/`~`/`-`/`>`/`!` margin symbols,
        literal `value:` secrets rendered as `sha256:` digests in state),
        `sandboxOptions.writableEnvFiles`, `--auto-approve`/`-y`,
        `env.rememberHostCommands` setting.
      - `sbx env create --help`/`sbx env run --help`/`sbx env plan --help`/
        `sbx env exec --help`/`sbx env rm --help` — per-command flags:
        `--env-arg`, `--env-args-file`, `--kit-arg`, `--kit-args-file`, `--name`,
        `--skip-host-commands`, `--clone` (create/run/plan only, overrides
        `workspace.clone`), `-d/--detached` (`env run` ONLY — `env create` and
        `env plan` do not have this flag), `--prune-bindings` (rm only); `env
        exec`'s `[PATH...] -- COMMAND` argument-splitting rule, and its own text
        stating the sandbox "must already exist" — `env exec` never creates one.
      
      ## Not verified / explicitly excluded
      
      - No claim is made about a stable, non-experimental future schema version;
        at the pinned commit `schemaVersion: "1"` is the only supported value
        (`sandboxlib/sbxenv/types.go`, `SupportedSbxEnvVersions`).
      - No docs.docker.com URL beyond the canonical product page
        (https://docs.docker.com/ai/sandboxes/) is cited here: this review did
        not independently fetch a dedicated `sbxenv.yaml` docs page, so no more
        specific URL is asserted as a source for any rule above. Every rule above
        traces to a repository path and/or a captured `--help` output.
      
  • SKILL.md 15.8 KB
    ---
    name: docker-sandboxes-env
    description: Use this skill when authoring, planning, or running a declarative `sbxenv.yaml` file for Docker Sandboxes (`sbx env create/run/plan/exec/rm`), even if the user just says they want to "check in a sandbox config", "make onboarding reproducible for a sandbox", "run a setup script before the agent starts", or "define arguments for a shared sandbox environment". Covers the sbxenv.yaml schema (schemaVersion, agent, kits, workspace/additionalWorkspaces, args, env, secrets, registries, bindings, mcp, ports, sandboxOptions), host `lifecycle:` commands (initialize/postCreate/preRemove) and their approval-plan model, multi-file merge (`-f`-style deep merge and the user-level `.sbxenv.yaml` base layer), and file-write-protection (`sandboxOptions.writableEnvFiles`).
    license: Apache-2.0
    compatibility: Requires standalone sbx with sbx env support and sbxenv.yaml schemaVersion "1", not the legacy docker sandbox wrapper. Verified against docker/sandboxes df5c96ba60484fa2c375469dbac912c205da6c37; installed-help version and provenance are in references/sources.md. docker_help does not cover standalone sbx.
    ---
    
    # Docker Sandboxes: Declarative sbxenv.yaml Environments
    
    ## Overview
    
    `sbxenv.yaml` (schemaVersion `"1"`, EXPERIMENTAL) declaratively describes one
    sandbox environment — agent, mixin kits, workspace mounts, environment
    variables, secrets/registries/bindings to provision, MCP servers, ports, and
    host-side lifecycle commands — so `sbx env create|run|plan|exec|rm` can stand
    it up and tear it down reproducibly instead of a long flag invocation. This
    skill owns that file format end to end. It delegates the sandbox lifecycle
    semantics it wraps, the credential/network model it provisions into, and the
    kit schema its `kits:` entries reference, to their own skills.
    
    ## When to use this skill
    
    Activate this skill when:
    - The user wants a checked-in, reproducible definition of a sandbox
      environment instead of a long `sbx create`/`sbx run` command line.
    - The user wants host-side setup/teardown commands (cloning a repo, seeding
      fixtures, archiving state) tied to a sandbox's create/attach/remove
      lifecycle.
    - The user wants to parameterize a shared environment file with named
      arguments (`args:` + `--env-arg`).
    - The user is debugging why `sbx env create`/`run` is asking for approval,
      or why a file, kit, or secret it declares was skipped or flagged.
    
    ## Do not use this skill when
    
    Do not use this skill when:
    - The task is the underlying `sbx create`/`run`/`rm` flag-based workflow with
      no `sbxenv.yaml` file involved — use `docker-sandboxes-lifecycle`.
    - The task is choosing network policy or storing a secret/registry
      credential independent of any environment file — use
      `docker-sandboxes-network-credentials` (this skill's `secrets:`/
      `registries:`/`bindings:` blocks provision into that same store, but do not
      redefine its rules here).
    - The task is authoring the kit `spec.yaml` a `kits:` entry points at — use
      `docker-sandboxes-kits`.
    
    ## Core guidance
    
    ### File resolution and required fields
    
    - The file `sbx env` reads from a directory is exactly `sbxenv.yaml` — no
      other name, and a directory-named `.sbxenv.yaml` at the project level is
      **not** read as a project's own file (only the home-directory base layer
      uses that hidden name; see below).
    - Every environment file requires `schemaVersion: "1"` and `agent:` (a
      built-in agent name or the manifest name of an agent kit supplied via
      `kits:`). Everything else is optional. `agent: shell` needs no credentials
      and is the simplest way to validate a file's mechanics.
    - `sbx env create|run|plan|exec|rm` accept one or more `PATH` arguments.
      Each `PATH` is either a directory (resolved to `<PATH>/sbxenv.yaml`) or the
      file itself. Passing more than one deep-merges them in declaration order —
      **`docker compose -f`-style semantics**: later files override earlier ones,
      mappings merge key-by-key, sequences concatenate.
      ```bash
      sbx env create sbxenv.yaml override.yaml
      ```
    
    ### Naming, workspace, and the `.sbxenv.yaml` user base layer
    
    - Unless the file sets `name:` or `--name` overrides it, the sandbox is
      named after the mounted directory (or the project directory when nothing
      is mounted) — so an environment that mounts nothing is still the same
      sandbox every time it is applied. **Two different environment files in the
      same directory derive the same sandbox name and collide** unless each sets
      its own `name:` (or you pass a distinct `--name` per invocation) — always
      give each environment its own explicit `name:` when more than one may
      exist in the same directory.
    - `workspace:` names the read/write mount, exactly like `sbx create`'s
      omitted-path behavior: **omitting `workspace:` mounts nothing** at all.
      A relative `workspace:` path resolves against the **directory of the file
      that declares it** — `workspace: .` mounts the directory the file sits
      in. `${{ env.projectDir }}` names the project directory (the one holding
      the first `PATH`, or cwd when none is named); `${{ env.fileDir }}` names
      the declaring file's own directory. Nothing else is expanded — a bare `$`
      is literal text, so a value written for the container
      (`PATH: $PATH:/opt/bin`) reaches it unchanged.
      ```yaml
      workspace: .                       # mounts the directory this file sits in
      # workspace: ${{ env.projectDir }} # mounts the project directory explicitly
      ```
    - Relative kit sources follow the same file-directory anchoring rule as
      `workspace:` (see `docker-sandboxes-kits` for kit reference syntax).
    - **Files within a mounted workspace get default read-only masking, and that
      protection is complete only when the file sits directly at the mount's own
      root.** A read-only bind at the mount point cannot be renamed by the
      sandbox — there is nothing above it inside the mount to rename. But an
      environment file in a **subdirectory** of a read-write mount is protected
      only at its current path: the sandbox can rename the containing directory
      (which it can write to) and then recreate the original path itself,
      landing a sandbox-controlled file back where the read-only bind no longer
      applies. `sbx env plan` calls this gap out explicitly for a file that is
      not at a mount's root. Do not claim renaming the containing directory
      creates no gap — for anything but the mount root, it does.
    - With **no `PATH`** given, an `.sbxenv.yaml` in the **home directory** is
      merged underneath as a base layer for defaults shared across projects;
      naming any `PATH` skips this layer entirely. The base layer may not set
      `name:` (which identifies one project) and its `workspace:` must be rooted
      at `${{ env.projectDir }}` — any other value would mount one fixed
      directory under every project that merges it.
    
    ### `args:` — parameterizing a shared file
    
    - Declare named inputs under `args:`, each with a `default` (making it
      optional, `default: ""` counts as a real default) or `required: true`
      (mutually exclusive), plus optional `description`, `enum`, or `pattern`.
    - Reference one as `${{ env.args.NAME }}` anywhere a value appears in the
      file, and supply it with `--env-arg NAME=VALUE` (repeatable) or
      `--env-args-file PATH`.
    
    ### `lifecycle:` — host commands and the approval plan
    
    - `lifecycle:` declares shell commands that run **on the host, outside the
      sandbox, with your own privileges** — not inside the container. Three
      phases, run in this order per invocation:
      - **`initialize`** — runs on **every** `create` **and** `run`, including
        one that only attaches to an existing sandbox. It is the one phase that
        can produce what the environment needs to exist (a cloned workspace, a
        generated file), so **it must be idempotent** — it reruns on every
        reattach.
      - **`postCreate`** — runs once, after the sandbox exists, before an
        interactive attach takes the terminal.
      - **`preRemove`** — runs before `sbx env rm` deletes the sandbox, while
        `sbx env exec` can still reach it. **A failing `preRemove` is only a
        warning** — the failure itself does not block removal. After the hook,
        removal rechecks the approved destroy plan and sandbox identity. A new
        credential or changed binding not covered by that approval, or a
        replacement sandbox under the same name, stops removal before deletion.
        Review the new destroy plan before retrying.
      - `sbx env exec` **runs no lifecycle commands at all, and requires the
        sandbox to already exist** — it does not create one. Run
        `sbx env create`/`sbx env run` first.
      ```yaml
      lifecycle:
        initialize:
          - command: test -d app || git clone https://github.com/acme/app
        postCreate:
          - command: ./scripts/seed-fixtures.sh
        preRemove:
          - command: ./scripts/archive-state.sh
      ```
    - Every command runs through the shell from the **project directory** by
      default (override per-command with `workdir:`; bound its runtime with
      `timeout:`).
    - **A file that declares any lifecycle command is asked about on every
      invocation that reaches it, whether or not this particular invocation
      changed anything** — approving a command also trusts whatever it invokes,
      including a script whose contents can change after the answer, so the
      question is repeated rather than remembered by default. The one exception:
      `sbx settings set env.rememberHostCommands true` makes it ask again only
      when the commands actually change. **Never treat an untrusted file's or an
      untrusted kit's lifecycle commands as pre-approved**, and never enable
      `rememberHostCommands` for a file whose commands you have not reviewed. An
      environment that declares **no** host commands at all, and whose config is
      otherwise unchanged from what was last approved, applies silently with no
      prompt. Use `--skip-host-commands` to run none of the declared commands
      for one invocation.
    
    ### The environment plan: what it is and is not
    
    - `sbx env plan [PATH...]` prints everything applying the file would set up
      — host commands, credentials/bindings, MCP registrations, directories,
      published ports, the sandbox itself, and its variables — compared against
      what was last applied/approved. **It changes nothing.**
    - `sbx env create`/`sbx env run` show the same plan and require approval
      before doing any work (`--auto-approve`/`-y` skips the prompt for
      non-interactive use — **never default to `-y` for a file or kit you have
      not reviewed**). A secret's literal `value:` is the one field shown both
      in the plan and recorded to state as a `sha256:` digest rather than in the
      clear; a `ref:`/`command:` secret shows where the credential comes from,
      not its resolved value.
    
    ### Secrets, registries, and bindings scoped to the environment
    
    - `secrets:` and `registries:` provision into the **same credential store**
      `sbx secret set` uses, at this environment's **sandbox scope**, so
      `sbx env rm` can remove exactly what it created. Each entry uses the same
      `value`/`ref`/`command` shape as `sbx secret set` (exactly one of the
      three) — see `docker-sandboxes-network-credentials` for what those mean at
      runtime and why a literal secret value should not otherwise appear in a
      checked-in file.
    - `bindings:` are per-service credential bindings merged into the user's
      **global** `credentials.yaml`; unlike `secrets:`/`registries:`, they are
      **left in place by default** by `sbx env rm` (they are user-wide and may
      be shared with other sandboxes/environments) — pass `--prune-bindings` to
      also remove them.
    - **Never write a literal secret value directly into a checked-in
      `sbxenv.yaml`.** Use `ref:` (1Password/AWS Secrets Manager) or `command:`
      so the value never lives in the file at all; if a literal `value:` is used
      transiently, both the plan and state show only its digest, but the
      original environment file still contains the plaintext secret. See the labeled `secrets:` fragment below for the
      shape — it is intentionally not part of the minimal asset, which needs no
      credentials at all to validate.
      ```yaml
      # OPTIONAL fragment — add only if this environment actually needs a
      # credential; the minimal asset omits this entirely.
      secrets:
        anthropic:
          ref: op://Private/Anthropic/api-key   # never a literal `value:` in a checked-in file
          refresh: 55m
      ```
    
    ### `kits:`, `additionalWorkspaces:`, `mcp:`, `ports:`, and `sandboxOptions:`
    
    - `kits:` composes mixin kits (and, exactly once, an agent kit whose name
      matches `agent:`) onto the base agent; a relative source anchors to the
      **declaring file's own directory**, the same rule as `workspace:`.
    - `additionalWorkspaces:` mounts extra directories beyond the primary
      `workspace:` (a file cannot declare one without the other) — the
      `sbxenv.yaml` equivalent of `sbx run`'s extra positional workspace
      arguments with `:ro`.
    - `mcp.servers:` registers MCP servers on the host and adds them to the
      sandbox's fixed (static) MCP set at create time; registrations are
      host-global and **left in place** by `sbx env rm`.
    - `ports:` pins explicit host-port bindings for container ports the
      sandbox exposes — the equivalent of `sbx ports --publish` — and is torn
      down automatically when `sbx env rm` deletes the sandbox.
    - `sandboxOptions:` (beyond `writableEnvFiles`, below) maps onto the
      remaining `sbx create` flags: `template`, `memory`, `cpus`,
      `pullPolicy`, `profile`, `skills`.
    
    See `references/env-schema-fields.md` for the exact field shapes, required
    keys, and a YAML example for each of the five blocks above.
    
    ### `sandboxOptions.writableEnvFiles` — a deliberate, explicit downgrade
    
    - By default, **every environment file mounted inside the workspace is
      read-only at its own path**, even though the rest of the mount is
      writable. This stops an agent editing the very file that decides what
      host lifecycle commands and secret-resolving commands run on your machine
      on the next invocation.
    - Set `sandboxOptions.writableEnvFiles: true` only where an agent is
      deliberately meant to edit its own environment file. This is a real
      security downgrade — the plan then reports the file as writable — so
      treat it the same as any other explicit trust decision, not a default.
    - **The protection is complete only at a mount's own root.** A file placed
      directly at the root of a read-write mount cannot be reached even by
      renaming, because the sandbox cannot rename the mount point itself. A file
      in a subdirectory of that mount is a different case: it is read-only at
      its current path, but the sandbox can rename the directory holding it
      (which it can write to) and recreate a file at the original path, ending
      up with a sandbox-controlled file there. `sbx env plan` flags this gap for
      a file that is not directly at a mount's root — read the plan's output
      rather than assuming renaming is always harmless.
    
    ## Related skills
    
    - For the `sbx create`/`run`/`rm` flag-based workflow this file wraps, use
      `docker-sandboxes-lifecycle`.
    - For what `secrets:`/`registries:`/`bindings:` mean at runtime, and for
      configuring network policy independent of any environment file, use
      `docker-sandboxes-network-credentials`.
    - For the schema of the kit `spec.yaml` a `kits:` entry (or `agent:`
      pointing at an agent kit) references, use `docker-sandboxes-kits`.
    
    ## References
    
    - `references/sources.md` — provenance for every rule above (help captures, source paths, docs URLs).
    - `references/env-schema-fields.md` — exact field shapes and YAML examples for `kits:`, `additionalWorkspaces:`, `mcp:`, `ports:`, and `sandboxOptions:`.
    
    ## Assets
    
    - `assets/sbxenv.yaml` — a complete, minimal, safe example: a `shell` agent
      mounting the declaring file's own directory, one static env var, and no
      credentials at all — it validates and plans without any onboarding
      authentication.
    
    ## Checks
    
    - `checks/verification.md` — Verification runbook for sbxenv.yaml commands (unexecuted runbook; run manually with an isolated, uniquely-named `--app-name`, never with real secret values or untrusted lifecycle commands auto-approved).
    
  • skill.yaml 1.3 KB
    schema: v1
    id: docker-sandboxes-env
    version: 0.1.0
    title: 'Docker Sandboxes: Declarative sbxenv.yaml Environments'
    description: Author, plan, and run declarative sbxenv.yaml environments (workspace, kits, args, host lifecycle hooks, secrets/registries/bindings, ports) for Docker Sandboxes.
    owns:
      - sbxenv.yaml
      - sbx-env
      - sandbox-environment-lifecycle-hooks
      - sandbox-environment-plan-approval
    use_when:
      - The user wants a checked-in, reproducible definition of a sandbox environment instead of a long sbx create/run command line.
      - The user wants host-side setup or teardown commands tied to a sandbox's create, attach, or remove lifecycle.
      - The user wants to parameterize a shared environment file with named arguments.
      - The user is debugging why sbx env create/run is asking for approval, or why a declared resource was skipped or flagged.
    do_not_use_when:
      - The task is the underlying sbx create/run/rm flag-based workflow with no sbxenv.yaml file involved.
      - The task is choosing network policy or storing a secret/registry credential independent of any environment file.
      - The task is authoring the kit spec.yaml a kits entry points at.
    delegates_to:
      - docker-sandboxes-lifecycle
      - docker-sandboxes-network-credentials
      - docker-sandboxes-kits
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related