Table of Contents
- Quick Start
- What --sandbox actually does
- Lifecycle: use the sbx CLI
- Credentials & Secrets
- How injection works
- Attached RAG collections
- The generated coyote-mcp mixin
- Placeholder-based custom secrets
- First-run wizard inside the sandbox
- Updating a registered secret
- Vault operations inside the sandbox
- Extending the Sandbox: Auto-Discovered Mixins
- Mixin file format
- What a mixin can declare
- Bundling static files alongside a mixin
- Built-in mixins Coyote ships
- Sharing mixins with others
- Custom Kit Override
- Nesting
- Troubleshooting
- When not to use sandbox mode
- See Also
Coyote can launch itself inside an isolated Docker Sandbox (sbx) with one command. This gives you a disposable,
hypervisor-isolated environment where Coyote runs against your project workspace without having access to the rest of
your host machine.
Sandbox mode is powered by Docker Sandboxes. Coyote does not implement its own
container layer. It delegates everything to the sbx CLI: lifecycle, isolation, networking, image management, and
process supervision. Coyote's only job is to orchestrate the bootstrap (build a sandbox of the right shape, copy your
config in, inject your LLM and MCP credentials into the sbx secret store, attach you to a running session) and then get
out of the way.
Quick Start
Install the sbx CLI first (Docker Sandboxes install guide). Then from any directory:
# Sandbox named after the current directory's basename
coyote --sandbox
# Or with an explicit name
coyote --sandbox my-project
# Start fresh with no copied config, MCP secrets, or generated MCP mixin; LLM credentials still injected via proxy
coyote --sandbox throwaway --fresh
The first run pulls the pre-built Coyote sandbox image from Docker Hub (darkalex17/coyote) and creates the sandbox.
Pull time depends on your connection speed; once the image is cached locally, subsequent sandbox creates are fast.
Attaches to an existing sandbox are instant: your config, vault state, sessions, OAuth tokens, and installed tools
persist inside the sandbox until you sbx rm it.
Re-running coyote --sandbox [NAME] with the same name re-attaches to the existing sandbox silently rather than
creating a fresh one. --fresh is ignored on re-attach (it only affects sandbox creation).
Multiple sandboxes per workspace
Pass a distinct name for each sandbox you want to keep alive against the same directory. Coyote will treat them as fully independent sbx sandboxes. This is handy for branch-per-sandbox workflows, spike vs. main, or running two Coyote sessions with different vault providers side by side:
# From ~/code/my-project
coyote --sandbox feature-branch # create/attach to sandbox 'feature-branch'
coyote --sandbox spike # create/attach to sandbox 'spike'
coyote --sandbox review # create/attach to sandbox 'review'
sbx ls shows each one. Manage them individually with sbx stop <NAME>, sbx rm <NAME>, etc. The bare
coyote --sandbox (no name) still resolves to the current directory basename, so treat that as your default sandbox
and use explicit names for the rest.
Tip: Inside the sandbox REPL, prefix any line with
!to run a shell command without going throughsbx exec <name> -- <cmd>to modify the sandbox state; .e.g,!apt-get update,!git pull,!cargo build, etc. Output streams to your terminal, Ctrl-C interrupts long-running commands, and you don't spend any tokens because no output is sent to the LLM. See REPL -!<command>for details.
What --sandbox actually does
Coyote bundles a pre-built sbx kit (its sandbox manifest) directly inside the binary. When you run --sandbox,
it executes this sequence:
# 1. Extract the embedded base kit to your local cache (skipped on hash-match)
coyote --info | grep -i "sbx_kit_dir" | awk '{print $2}'
# 2. Discover mixins:
# - Walk known discovery paths for user-authored sbx-mixin.yaml files
# 3. Log what's about to be applied (info! and println!). See "Verbose
# mixin log" below.
# 4. Inject your LLM API credentials into the sbx global secret store (host-side).
# Coyote reads your host config, finds any {{SECRET_NAME}} placeholder in your
# client's api_key, decrypts it from your host vault, and registers it:
sbx secret set <llm-provider> # e.g. anthropic, openai, gemini
# (skipped silently if already registered; see "Updating a registered secret")
# 5. Inject MCP secrets and generate the coyote-mcp mixin (host-side, skipped if --fresh).
# Coyote scans your mcp.json for {{SECRET_NAME}} placeholders anywhere in each server
# config (env, headers, args, URLs, every occurrence of every secret; any number of
# secrets per server), decrypts each distinct secret on the host, and registers it
# one of two ways:
sbx secret set <secret-name> # proxy-injectable header secrets, e.g. {{GITHUB_PAT}} → github-pat
sbx secret set-custom --env COYOTE_SECRET_<NAME> <host>...
# ...for everything else, plus every attached RAG's driver secrets: the env var
# holds a placeholder the proxy swaps for the real value at the network edge
# (each secret skipped silently if already registered)
# Coyote then renders a "coyote-mcp" mixin kit that declares each proxy-managed
# credential and allows egress to every remote MCP server. See "The generated coyote-mcp mixin".
# 6. Check if a sandbox with this name already exists
sbx ls
# 7. If not, create it with the base kit + every discovered mixin + the generated
# coyote-mcp mixin layered on
sbx create \
--name <NAME> \
--kit <cache>/sbx-kit/ \
--kit <user-mixin-1> \
--kit <user-mixin-N> \
--kit <cache>/sbx-mixin-kits/<hash>/ \
coyote .
# 8. Copy your host config into the sandbox (skipped if --fresh). vault.yml is
# excluded. The vault is not used inside the sandbox. Each top-level entry is
# copied individually to sidestep a macOS `docker cp` quirk that silently drops
# files carrying `com.apple.provenance` xattrs when tarred as a recursive copy.
sbx exec <NAME> sh -c "sudo mkdir -p /home/agent/.config/coyote && sudo chown agent:agent /home/agent/.config/coyote"
for entry in ~/.config/coyote/*; do
sbx cp "$entry" <NAME>:/home/agent/.config/coyote/
done
sbx exec <NAME> sh -c "sudo chown -R agent:agent /home/agent/.config/coyote"
# 9. Hand control to sbx (Coyote's process is replaced). `--kit` is re-passed
# on reattach because sbx expects it even when the sandbox already exists.
exec sbx run --name <NAME> --kit <cache>/sbx-kit/
Once sbx run takes over, Coyote on the host exits and your terminal is connected to Coyote inside the sandbox. All
signals (Ctrl-C, etc.) flow straight through.
Coyote handles steps 1–5, 8, and the
--nameargument of step 7. Everything else issbxdoing its job.
Verbose mixin log
Before sbx create runs, Coyote emits a single block to both the log and stdout naming every mixin about to be applied:
Applying 2 sbx mixin(s):
~/.config/coyote/functions/sbx-mixin.yaml (adds: 0 installs, 20 domains)
~/.config/coyote/agents/my-python-dev/sbx-mixin.yaml (adds: 1 install, 1 domain)
If zero mixins were discovered, you'll see No sbx mixins discovered. in the log (no terminal noise).
Skim this log on first launch and after installing any shared bundle. It's your audit point for what each mixin grants in terms of installs and network domain allowances.
Lifecycle: use the sbx CLI
Coyote intentionally does not wrap sandbox lifecycle commands. Use sbx directly as it's the single source of truth:
| Task | Command |
|---|---|
| List sandboxes | sbx ls |
| Open a shell in a running sandbox | sbx exec <NAME> |
| Run a one-off command in a sandbox | sbx exec <NAME> <command> |
| Copy files in/out | sbx cp <src> <dest> (use <NAME>:/path to reference a sandbox) |
| Stop a sandbox without removing it | sbx stop <NAME> |
| Remove a sandbox (destroys all state) | sbx rm <NAME> |
| Forward a port to the host | sbx ports <NAME> |
| Diagnose problems | sbx diagnose |
Run sbx --help for the full surface. None of these are reimplemented in Coyote.
Credentials & Secrets
The vault is not used inside a sandbox. Instead, Coyote registers your LLM API credentials and MCP secrets with the sbx secret store on your host before the sandbox starts. Inside the sandbox, each credential is supplied one of two ways, and in both, the real value never enters the VM:
- Proxy-injected service secrets. The sbx network proxy intercepts outbound HTTPS requests and writes the real
credential into the request header at the network edge. The environment inside the VM only ever holds the literal
string
proxy-managed. Used for LLM providers, and for any MCP secret whose every occurrence is a conflict-free request-header value (see the demotion rules). - Placeholder-based custom secrets. Everything else (e.g. MCP secrets that can't be expressed as a proxy header
rule, and every attached RAG's driver secrets) is registered with
sbx secret set-custom. Inside the sandbox,COYOTE_SECRET_<NAME>holds a unique placeholder (sbx-cs-…); whenever that placeholder appears in an outbound HTTP(S) request header to one of the secret's target hosts, the proxy swaps in the real value at the network edge. Coyote resolves{{SECRET_NAME}}placeholders from those env vars at startup. Either way, yourmcp.jsonworks unmodified inside the sandbox. See Placeholder-based custom secrets.
How injection works
Before sbx create, Coyote:
- Reads your host
config.yamland locates any{{SECRET_NAME}}placeholder in your configured client'sapi_keyfield. - Decrypts that secret from your host vault.
- Registers it with
sbx secret set <service>(e.g.anthropic,openai,gemini). - Unless
--freshis passed, repeats the same process for every{{SECRET_NAME}}placeholder found anywhere in yourmcp.jsonserver configurations; i.e.env,headers,args, URLs, any nested field. There is no limit on the number of secrets per server, and one secret may be shared by several servers. Each distinct secret is registered once: proxy-injectable ones as a service secret under their kebab-cased name ({{GITHUB_PAT}}registers asgithub-pat), the rest as placeholder-based custom secrets. - Registers every attached RAG's driver secrets as placeholder-based custom secrets too. A vault secret missing for an MCP server fails the launch; one missing for a RAG only warns. That RAG's queries would fail, nothing else.
Secrets already registered with sbx are silently skipped to keep re-attaches fast. See
Updating a registered secret if you've rotated a credential.
If your LLM client uses OAuth instead of an API key (auth: oauth in your config), no secret is injected. The
sbx proxy handles OAuth natively without a stored key.
Attached RAG collections
A RAG attached to a remote Qdrant collection needs two things to work inside a sandbox, and Coyote arranges both without being asked: the collection's host has to be reachable through the network policy, and the API key has to reach the server without ever entering the VM.
When you attach a RAG, Coyote writes a small mixin next to it declaring the host as an allowed domain. On launch that
mixin is discovered alongside the generated coyote-mcp one, and every secret referenced by the RAG's driver
configuration is registered as a placeholder-based custom secret targeting the
host(s) found in that configuration (any http(s) URL in the values contributes its host; the host/url keys also
accept a bare host[:port]). Inside the sandbox the driver sees the placeholder; the proxy swaps in the real key at
the network edge. Queries then behave identically inside and outside the sandbox.
This is also why a RAG's api_key must be a {{SECRET_NAME}} placeholder rather than the key itself: Coyote reads
that placeholder back out to learn which vault secret to hand to sbx. A literal key can't be provisioned, so Coyote
refuses to load a RAG configured that way and tells you which secret to create.
The generated coyote-mcp mixin
Alongside secret registration, every non---fresh launch renders a mixin kit named coyote-mcp and
passes it to sbx create as an extra --kit. It carries two things:
- Network egress for your MCP servers. Every remote server
urlinmcp.jsoncontributes an entry to the sandbox's network allow list (HTTPS on the default port as a bare host, anything else ashost:port). You don't need to hand-maintain a mixin just to reach the MCP servers you've already configured. - One credential declaration per proxy-managed secret. Each entry names the sbx service (
github-pat), the in-sandbox env var (COYOTE_SECRET_GITHUB_PAT), and the proxy inject rules that let sbx write the real value into request headers at the network edge (proxyManaged: true). Custom secrets are not declared in the mixin (sbx binds them itself), but their target hosts still contribute allow-list entries (ports preserved).
When sbx asks you to approve credential bindings on the first interactive run, each entry carries a description naming the vault secret and the MCP server(s) that use it, so you can tell exactly what you're approving.
A secret is proxy-managed only when every occurrence of it across all servers is a single-placeholder HTTPS header value. It is provisioned as a placeholder-based custom secret instead when any of these apply:
- It appears anywhere other than a request header:
env,args, a URL, or any nested field. - Its header value holds more than one placeholder (e.g.
Basic {{USER}}:{{PASS}}) or a literal%. - The server URL isn't HTTPS. (Non-default ports are fine: the inject rule targets
host:port, mirroring the allow-list format.) - Two different secrets target the same header on the same domain; e.g. two
mcp.jsonentries pointing at the same host with differentAuthorizationbearer tokens. The proxy injects per domain + header and cannot tell which credential a given request needs, so Coyote provisions all the conflicting secrets as custom secrets and explains why on stderr at launch. Because custom secrets are swapped by placeholder value, not by domain + header, the conflict resolves itself: each secret gets its own placeholder and every server authenticates correctly. Different secrets in different headers on one domain are fine and stay proxy-managed.
Either way, every server's domain stays in the generated allow list, so connectivity is never affected; only the mechanism carrying the secret changes.
Not covered: servers whose URL is buried in
argsrather than theurlfield (e.g.npx mcp-remote https://…) contribute no allow entry, because Coyote only derives egress fromurl. Add those domains to a user mixin.
Placeholder-based custom secrets
sbx secret set-custom binds an env var to a secret value and a set of target hosts. Inside the sandbox the env
var holds a unique placeholder (sbx-cs-…); whenever that placeholder appears verbatim in an outbound HTTP(S)
request header to a target host, the proxy substitutes the real value at the network edge. Semantics worth knowing:
- The secret must reach the wire verbatim, in a header. A secret that gets transformed before it's sent (e.g. AWS SigV4 request signing, HMAC computation, a client that encodes or locally inspects the key), can never work under this scheme: the placeholder would be signed or encoded instead of the key. Such secrets have no sandbox support today.
- Host-scoped targets. Targets are host-only (sbx rejects
host:porttargets; the network allow list keeps the port). When Coyote can't derive any host for a secret (e.g. a stdio server whose config contains no URL)
it registers the wildcard target'**'and says so at launch; the swap then applies to any destination the sandbox is allowed to reach. - Write-once values, drift-safe targets. Re-launches skip secrets already registered with the right targets.
If the derived host set changes, Coyote removes the old registration (by placeholder) and re-registers with the
union of old and new targets. It never narrows a registration. An existing registration targeting
'**'is left alone with a note that it's wider than needed. - Existing sandboxes. Value updates reach running sandboxes, but a newly registered env var only exists in sandboxes created after it; Coyote prints a restart notice when that happens. Separately, Coyote hashes the generated mixin per sandbox and warns on re-attach when the sandbox's baked-in network and credential rules have drifted from your current config. Remove and re-create the sandbox to pick up the new rules.
First-run wizard inside the sandbox
If the sandbox has no Coyote config (e.g. after --fresh or on a brand-new sandbox), Coyote runs a lightweight
first-run wizard when it starts inside the sandbox:
- Pick your API provider.
- For providers that support OAuth (Claude, OpenAI, Gemini, and any openai-compatible provider with bundled OAuth), optionally choose OAuth instead of an API key.
- Pick a model.
The resulting config.yaml contains no secrets. Credentials are still managed entirely by the sbx proxy.
Updating a registered secret
Coyote skips secrets that are already registered to avoid unnecessary prompts on every sandbox launch. If you've rotated a credential, update it manually on your host:
# See what's currently registered
sbx secret ls
# Overwrite an LLM provider credential (supply the new value via stdin)
echo "sk-new-value" | sbx secret set --force anthropic
# Overwrite an MCP secret, keyed by its kebab-cased secret name
echo "ghp-new-value" | sbx secret set --force github-pat
The updated credential takes effect immediately; you do not need to restart the sandbox.
Placeholder-based custom secrets are keyed by placeholder instead. Update the value in your vault first
(coyote --update-secret), then:
sbx secret ls # the CUSTOM SECRETS section lists env, targets, placeholder
sbx secret rm --placeholder sbx-cs-XXXX -f # drop the old registration
coyote --sandbox <NAME> # next launch re-registers it from the vault
Re-registration mints a new placeholder, so existing sandboxes hold a stale env value: re-create the sandbox, or update the env var inside it to the new placeholder (Coyote and sbx both print the exact value to use).
Vault operations inside the sandbox
Vault management commands (coyote --add-secret, --update-secret, --delete-secret, --list-secrets, and .vault
REPL commands) are disabled inside the sandbox and will return an error directing you to your host. Manage your
vault from your host machine.
Extending the Sandbox: Auto-Discovered Mixins
To add applications, network allowances, environment variables, or files to your sandbox beyond what the base kit
provides, drop an sbx-mixin.yaml file at any of these locations and Coyote will discover and apply it automatically
on every coyote --sandbox:
| Discovery path | Purpose |
|---|---|
<config-dir>/sbx-mixin.yaml |
Top-level user mixin. Applies to every sandbox you launch |
<config-dir>>/functions/sbx-mixin.yaml |
Mixin for global custom tools |
<config-dir>/functions/<tool>/sbx-mixin.yaml |
Per-custom-tool mixin (alphabetical) |
<config-dir>/agents/<agent>/sbx-mixin.yaml |
Per-agent mixin (alphabetical), applied for every sandbox |
<workspace-root>/.coyote/sbx-mixin.yaml |
Workspace mixin (walk-up search from cwd) |
Coyote does not recursively scan for
sbx-mixin.yamlanywhere else. These five paths are the whole surface, meaning anything outside is ignored.
The <workspace-root> walk follows the same convention as Memory: Coyote walks up from your current directory
looking for the first ancestor containing .coyote/sbx-mixin.yaml. Use this to ship per-project sandbox extensions in
your repo.
Mixin file format
Mixins are standard sbx kit YAML with kind: mixin. Here's a complete working example that adds ruff to every
sandbox:
# <config-dir>/sbx-mixin.yaml
schemaVersion: "2"
kind: mixin
name: my-python-tooling
description: Install the ruff Python linter for use in any sandbox
permissions:
network:
allow:
- "files.pythonhosted.org"
- "pypi.org"
setup:
install:
- command: "uv tool install ruff"
user: "1000"
description: Install ruff via uv
After saving this file, your next coyote --sandbox automatically applies it. See the official sbx kit reference
for the full mixin schema.
What a mixin can declare
A mixin can carry everything a full kit can except sandbox (base image/entrypoint), extends, and
mixins, as those belong to the base kit. The full authoring surface:
| Section | Purpose |
|---|---|
permissions.network.allow |
Extra egress domains. Bare host = HTTPS on 443; use host:port for anything else. *.host matches exactly one subdomain label. It covers api.host but not a.b.host, and not the bare host itself. There is no allow-all '*'. |
setup.install |
One-time install commands, run once when the sandbox is created. command is a shell string (run via sh -c); set user: "0" for root or "1000" for the agent user. |
setup.startup |
Commands run on every sandbox start. command is an argv list (e.g. ['sh', '-c', '...']); add background: true for daemons. |
setup.files |
Files written into the sandbox: path, content, mode. Only ${WORKDIR} is expanded inside content. Other ${...} sequences are rejected (unbraced $VAR in scripts is fine). |
environment.variables |
Static environment variables. |
credentials |
Secret declarations: each entry names an sbx service and an env var, and may add apiKey.inject rules so the sandbox proxy writes the real value into request headers at the network edge (proxyManaged: true). Bind values with sbx secret set <service>. Every inject domain must also appear in your permissions.network.allow. |
agentInstructions.content |
Instructions for the agent running inside the sandbox, written to the sandbox's kits-memory/<mixin-name>.md and progressively disclosed. (A mixin's filename field is ignored, and only the base kit controls the primary instructions file.) |
Composition across the base kit and all mixins is additive: allow lists concatenate, setup entries append in kit
order, and environment variables merge with later kits overriding earlier ones on key collisions. Kit names must be
unique across everything passed to one sandbox.
The credentials section deserves a call-out: it means a custom tool's mixin can ship real proxy-managed auth, not
just open domains. For example, a tool hitting an internal API could declare:
credentials:
- service: internal-api
description: Internal API token for the my-report tool
apiKey:
name: INTERNAL_API_TOKEN
proxyManaged: true
inject:
- domain: internal-api.example.com
scheme: bearer
Inside the sandbox, INTERNAL_API_TOKEN holds the literal string proxy-managed; the real value (bound once via
sbx secret set internal-api) never enters the VM. Requests to internal-api.example.com get the
Authorization: Bearer <token> header added at the proxy. Use header: + format: "%s" instead of
scheme: bearer for non-standard headers (exactly one %s required, and format/scheme are mutually exclusive).
Bundling static files alongside a mixin
For content that doesn't need runtime substitution (dotfiles, tool configs, helper scripts, cheatsheets, reference
material, etc.), drop it into a files/ directory alongside the sbx-mixin.yaml. Coyote stages the whole tree into the
cache-backed kit it hands to sbx, and sbx applies its normal
static-files convention: files/home/* lands
under /home/agent/, files/workspace/* lands under the mounted workspace, and so on.
<config-dir>/agents/researcher/
├── sbx-mixin.yaml
└── files/
├── home/
│ └── .config/my-tool/settings.json # → /home/agent/.config/my-tool/settings.json
└── workspace/
└── .editorconfig # → <workspace>/.editorconfig
The convention applies to every discovery path in the table above: the workspace mixin, per-tool mixins, RAG
sidecars, and the top-level user mixin can each ship a sibling files/. There's no per-source special-casing.
Use files/ for static content; use the mixin's setup.files entries for content that
needs ${WORKDIR} substitution or has to be generated at startup.
Caveats:
- The
files/directory must be a sibling of thesbx-mixin.yaml, not somewhere else in the config tree. Coyote doesn't recurse into arbitrary paths. A tree at<config-dir>/files/...with no adjacent mixin is ignored, and a file (rather than a directory) namedfilesnext to the mixin is also ignored. - Symlinks inside the tree are rejected with an error at sandbox-launch time. This is deliberate: a symlink
pointing at
/etc/passwdin a shared bundle would silently exfiltrate host content into the sandbox. Copy the file in for real, or reference it via asetup.filescontent:entry. - The tree's contents are hashed into the kit identity. Editing any file under
files/invalidates the cached kit dir on the nextcoyote --sandbox, so iteration is safe. No stale content is served from cache. - Executable bits are preserved on Unix. Scripts placed at e.g.
files/home/.local/bin/my-helperland runnable inside the sandbox. - UTF-8 paths only. A file whose path Coyote can't render as UTF-8 (rare, but possible on some filesystems) is rejected at wrap time so the kit hash stays deterministic across platforms.
Built-in mixins Coyote ships
Coyote already ships mixins for the most common needs. They get auto-applied whenever they're relevant — you don't need to do anything to enable them:
- Built-in tools mixin (auto-applied when
coyote --install functionshas run). Allowlists the domains used by every built-in global tool and the default MCP server set: Wikipedia, arxiv, jina, wttr, WolframAlpha, Perplexity, Tavily, Twilio, github MCP, atlassian MCP, ddg-search MCP, npm registry, Docker registries. coyote-mcpmixin (auto-generated on every non---freshlaunch; not a file on disk). Declares your proxy-managed MCP credentials and allowlists every remote MCP server in yourmcp.json. See The generated coyote-mcp mixin. It does not appear in the verbose mixin log, which only lists discovered mixin files.
You can list everything that's about to be applied via the verbose mixin log on every launch.
Sharing mixins with others
A mixin in <config-dir>/agents/<agent>/sbx-mixin.yaml travels with the agent if you publish it via the Bundles
mechanism. See that page for the security implications. Installing a bundle that ships an sbx-mixin.yaml grants the
included install commands and network domains the next time you coyote --sandbox.
Custom Kit Override
If you want to point Coyote at a completely different kit instead of the embedded one (e.g. for development, hardening,
or a fork), set COYOTE_SANDBOX_KIT:
COYOTE_SANDBOX_KIT=./my-fork-of-coyote-kit/ coyote --sandbox
When this environment variable is set, Coyote skips the embedded-kit extraction entirely and passes the override path
straight to sbx. Use this sparingly. The embedded kit is what every documented coyote --sandbox workflow assumes.
Nesting
Sandbox mode refuses to start a sandbox if $IS_SANDBOX is already set. The bundled kit exports IS_SANDBOX=1 inside
every Coyote sandbox, so running coyote --sandbox from within a sandbox is a no-op error because you're already in one.
Troubleshooting
If something looks wrong, these are your three debugging flags:
| Flag | Effect | Use when… |
|---|---|---|
--fresh |
Skips host config copy, MCP secret injection, and the generated coyote-mcp mixin (so MCP servers get no auto egress); LLM credentials still injected via proxy |
You want a clean-slate sandbox with no copied state |
COYOTE_SANDBOX_KIT |
Points at an alternate base kit instead of the embedded one | Developing a fork of the kit or hardening for a specific environment |
Common failures
- A mixin causes
sbx createto abort. Check the verbose mixin log to identify which mixin was being applied when it failed. Open that mixin file and inspect itssetup.install(v1:commands.install) block. Temporarily move the file aside to confirm. - A malformed
sbx-mixin.yamlaborts launch beforesbx createruns. Coyote fails fast with a parse error naming the file. Fix the YAML or temporarily move it aside. - An API request from inside the sandbox fails with an auth error. The secret may not have been registered or may
be stale. Run
sbx secret lson your host to check, then see Updating a registered secret. - A custom tool fails with
command not found. The tool's required binary isn't installed in the sandbox. Add it to the matching mixin (per-tool, per-agent, or top-level user mixin). See Custom Tools. - A network request from inside the sandbox hangs or fails. The domain isn't in any allow list
(
permissions.network.allow; v1:network.allowedDomains). Remote MCP servers frommcp.jsonare auto-allowed via the generatedcoyote-mcpmixin; anything else needs a user mixin. The sandbox's proxy denies all unlisted traffic silently.
When not to use sandbox mode
- You only need filesystem isolation, not network or process isolation. sandbox mode trades a few minutes of first-run setup and ongoing VM overhead (one Docker daemon per sandbox) for full hypervisor isolation. If you only want to restrict which files Coyote can touch, the existing per-tool permission model is lighter weight.
- Fast one-off shell commands. Sandbox attach is fast on warm sandboxes, but cold-start (first-time image pull) takes time.
- You need to interact with host-only resources (the host's clipboard, host system services, certain GUI integrations). Sandboxes can't see the host beyond the mounted workspace and the proxy-mediated network.
See Also
- Docker Sandboxes: Official Docs
- Vault: How Coyote's vault works; vault management is disabled inside sandboxes
- Environment Variables: Including
COYOTE_SANDBOX_KIT - Clients: Auth flows you may need to redo inside the sandbox for OAuth providers