{"owner":"docker","repo":"docs","hasSkills":true,"totalSkillsCount":131,"totalTokensCount":131730,"categories":["anthropic-skill","root-instruction","claude-rule","subagent-persona","command-prompt"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/agent-readiness-audit/SKILL.md",".agents/skills/agent-readiness-audit/references/report-template.md",".agents/skills/agent-readiness-audit/references/rubric.md",".agents/skills/create-lab-guide/SKILL.md",".agents/skills/create-pr/SKILL.md",".agents/skills/curate-whats-new/SKILL.md",".agents/skills/curate-whats-new/agents/openai.yaml",".agents/skills/fix-issue/SKILL.md",".agents/skills/maintain-pr/SKILL.md",".agents/skills/maintain-pr/agents/openai.yaml",".agents/skills/migrate-content-ia/SKILL.md",".agents/skills/research/SKILL.md",".agents/skills/review-changes/SKILL.md",".agents/skills/review-pr/SKILL.md",".agents/skills/review-pr/agents/openai.yaml",".agents/skills/testcontainers-guides-migrator/SKILL.md",".agents/skills/triage-issue/SKILL.md",".agents/skills/write/SKILL.md","AGENTS.md","CLAUDE.md","_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md","_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md","_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md","_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md","_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md","_vendor/github.com/docker/docker-agent/docs/features/skills/index.md","_vendor/github.com/docker/docker-agent/docs/tools/_index.md","_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md","_vendor/github.com/docker/docker-agent/docs/tools/api/index.md","_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md","_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md","_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md","_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md","_vendor/github.com/docker/docker-agent/docs/tools/git/index.md","_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md","_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md","_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md","_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md","_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md","_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md","_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md","_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md","_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md","_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md","_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md","_vendor/github.com/docker/docker-agent/docs/tools/script/index.md","_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md","_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md","_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md","_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md","_vendor/github.com/docker/docker-agent/docs/tools/think/index.md","_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md","_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md","_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md","_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md","content/manuals/ai/sandboxes/agents/_index.md","content/manuals/ai/sandboxes/agents/claude-code.md","content/manuals/ai/sandboxes/agents/codex.md","content/manuals/ai/sandboxes/agents/copilot.md","content/manuals/ai/sandboxes/agents/cursor.md","content/manuals/ai/sandboxes/agents/docker-agent.md","content/manuals/ai/sandboxes/agents/droid.md","content/manuals/ai/sandboxes/agents/gemini.md","content/manuals/ai/sandboxes/agents/kiro.md","content/manuals/ai/sandboxes/agents/opencode.md","content/manuals/ai/sandboxes/agents/shell.md","content/manuals/dhi/tools/_index.md","content/manuals/dhi/tools/api.md","content/manuals/dhi/tools/cli.md","content/manuals/dhi/tools/hub.md","content/manuals/dhi/tools/mcp.md","content/manuals/dhi/tools/terraform.md","content/manuals/extensions/_index.md","content/manuals/extensions/extensions-sdk/_index.md","content/manuals/extensions/extensions-sdk/architecture/_index.md","content/manuals/extensions/extensions-sdk/architecture/metadata.md","content/manuals/extensions/extensions-sdk/architecture/security.md","content/manuals/extensions/extensions-sdk/design/_index.md","content/manuals/extensions/extensions-sdk/design/design-guidelines.md","content/manuals/extensions/extensions-sdk/design/design-principles.md","content/manuals/extensions/extensions-sdk/design/mui-best-practices.md","content/manuals/extensions/extensions-sdk/dev/_index.md","content/manuals/extensions/extensions-sdk/dev/api/_index.md","content/manuals/extensions/extensions-sdk/dev/api/backend.md","content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md","content/manuals/extensions/extensions-sdk/dev/api/dashboard.md","content/manuals/extensions/extensions-sdk/dev/api/docker.md","content/manuals/extensions/extensions-sdk/dev/api/overview.md","content/manuals/extensions/extensions-sdk/dev/continuous-integration.md","content/manuals/extensions/extensions-sdk/dev/test-debug.md","content/manuals/extensions/extensions-sdk/dev/usage.md","content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md","content/manuals/extensions/extensions-sdk/extensions/_index.md","content/manuals/extensions/extensions-sdk/extensions/labels.md","content/manuals/extensions/extensions-sdk/extensions/multi-arch.md","content/manuals/extensions/extensions-sdk/extensions/publish.md","content/manuals/extensions/extensions-sdk/extensions/share.md","content/manuals/extensions/extensions-sdk/extensions/validate.md","content/manuals/extensions/extensions-sdk/guides/_index.md","content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md","content/manuals/extensions/extensions-sdk/guides/kubernetes.md","content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md","content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md","content/manuals/extensions/extensions-sdk/process.md","content/manuals/extensions/extensions-sdk/quickstart.md","content/manuals/extensions/marketplace.md","content/manuals/extensions/non-marketplace.md","content/manuals/extensions/private-marketplace.md","content/manuals/extensions/settings-feedback.md"],"skills":{".agents/skills/agent-readiness-audit/SKILL.md":"---\nname: agent-readiness-audit\ndescription: >\n  Audit a documentation site for agent-friendliness: discovery, markdown\n  delivery, crawlability, semantic structure, machine-readable surfaces,\n  and content legibility. Use when asked to assess docs.docker.com or any\n  docs site for AI/agent readiness, produce a scored report, compare with\n  external scanners, or generate a remediation list. Triggers on:\n  \"audit docs for agent readiness\", \"how agent-friendly is docs.docker.com\",\n  \"score our docs for AI agents\", \"review llms.txt / markdown / crawlability\",\n  \"create an agent-readiness remediation plan\".\nargument-hint: \"<base-url>\"\n---\n\n# Agent Readiness Audit\n\nAudit the live site, not the source tree alone. Prefer the same fetch path\nan external agent would use in the wild: direct HTTP requests, sitemap\nsampling, and page-level inspection.\n\nDo not reduce the result to a homepage-only scan or a binary checklist.\n\n## 1. Set scope\n\nUse `$ARGUMENTS` as the base URL when provided. Otherwise infer the base\nURL from context and state the assumption.\n\nDecide whether the host being audited is:\n\n- a docs-only host\n- an app/tool host\n- a mixed host\n\nThis matters for optional checks such as MCP, plugin manifests, or other\ntool discovery files. Do not penalize a docs-only host for missing\ntooling manifests that belong on a separate service.\n\nFor `docs.docker.com`, treat the public docs host as docs-only. Docker's\nMCP server is published separately, so missing MCP files on the docs host\nshould be reported as `N/A`, not as a failure.\n\n## 2. Gather sitewide signals\n\nAlways check these resources first:\n\n- `/llms.txt`\n- `/llms-full.txt`\n- `/robots.txt`\n- `/sitemap.xml`\n\nOnly check host-level tool manifests when the host is an app/tool host,\nmixed host, or explicitly advertises them:\n\n- `/.well-known/ai-plugin.json`\n- `/.well-known/agent.json`\n- `/.well-known/agents.json`\n\nUse the bundled script for a baseline:\n\n```bash\nbash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\nThe script produces baseline evidence only. You still need to interpret\nwhat matters for a docs property and score it with the rubric.\n\nFor docs-only hosts, you may skip tool-manifest probes to reduce noise:\n\n```bash\nCHECK_TOOL_MANIFESTS=0 \\\n  bash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\n## 3. Sample representative pages\n\nUse the sitemap when available. Do not rely on the homepage alone.\n\nIf `llms.txt` exists, sample some URLs from it as well. This helps catch\nstale or misleading discovery surfaces that a sitemap-only sample would miss.\n\nSample at least 12 pages when the site is large enough, and cover multiple\npage types:\n\n- homepage or docs landing page\n- section landing pages\n- task guides\n- product manuals\n- reference or API pages\n- tutorial or learning pages\n\nIf the sitemap is missing or unusable, discover pages through internal\nlinks and note the lower confidence.\n\nIf the site has distinct delivery patterns, sample each one. For example:\n\n- normal content pages\n- generated reference pages\n- versioned docs\n- localized docs\n\n## 4. Run fetch-path checks on each sample\n\nFor each sampled page, verify:\n\n- HTML fetch status, content type, and final URL\n- `Accept: text/markdown` behavior\n- direct markdown route behavior such as `<page>.md` or another stable path\n- page-level markdown alternate links and whether they actually resolve\n- whether page actions such as \"Open Markdown\" agree with the working route\n- whether the HTML title or H1 matches the markdown H1 closely enough for\n  retrieval parity\n- whether main content is present in the initial HTML\n- redirect chain length and canonical URL consistency\n- obvious chrome/noise in the markdown response\n\nDo not assume a `.md` mirror exists just because another site uses one.\nVerify the actual markdown path the site exposes.\n\nTreat these as separate signals:\n\n- negotiated markdown works\n- a stable direct markdown URL works\n- the page advertises the correct markdown URL\n\nIf the page advertises dead markdown alternates but a working markdown route\nexists, do not fail markdown delivery outright. Score it as a discoverability\nand consistency problem instead.\n\nFor API or generated reference pages, also verify whether a machine-readable\nasset such as OpenAPI YAML is directly linked and fetchable.\n\n## 5. Judge structure and legibility\n\nMeasure structural signals:\n\n- exactly one `h1`\n- sane heading hierarchy\n- `main` and `article` presence where appropriate\n- canonical tags\n- JSON-LD or breadcrumb structured data\n- stable anchors and deep-linkable headings\n\nAlso make a qualitative judgment about agent legibility:\n\n- markdown strips site chrome cleanly\n- headings are specific and task-oriented\n- code blocks stay intelligible without client-side JS\n- the page is not dominated by banners, injected chat, or nav noise\n\nMeasure code block labeling explicitly when code samples are common. A page\ntype with many untagged fenced blocks should lose points even if the prose is\notherwise clean.\n\nFor page types that intentionally render interactive UIs with JavaScript,\njudge them separately from normal docs pages. If the HTML shell is thin,\ncheck whether the page still provides:\n\n- a fetchable markdown summary\n- a directly linked machine-readable asset\n- a usable non-JS fallback\n\n## 6. Score with the rubric\n\nUse [references/rubric.md](references/rubric.md).\n\nRules:\n\n- score only what you verified\n- mark non-applicable checks as `N/A`\n- normalize the final score against applicable points only\n- do not let optional manifest checks dominate the grade\n\nApply the foundational caps from the rubric. A site with broken discovery\nor broken markdown delivery should not earn a high grade because it has\nclean metadata.\n\nDo not average away a weak page type. If one major page type, such as API\nreference, is materially worse than the rest of the corpus, call it out as\nthe weakest segment and reflect it in the category notes.\n\n## 7. Compare with external scanners when useful\n\nIf external scanner results are available, compare them to your live\nfindings. Treat them as secondary evidence.\n\nIf a scanner and the live fetch disagree:\n\n- trust the live fetch\n- report the mismatch explicitly\n- explain whether the scanner is testing a different assumption\n\n## 8. Produce a remediation list\n\nTurn findings into a short backlog:\n\n- `P0`: fetchability or discovery blockers\n- `P1`: recurring structural or parity issues\n- `P2`: polish, optional manifests, or low-impact enhancements\n\nFor each remediation, include:\n\n- the failing signal\n- why it matters to agents\n- a concrete fix\n- whether it is sitewide or page-type-specific\n\n## 9. Report in a stable format\n\nUse [references/report-template.md](references/report-template.md).\n\nAlways include:\n\n- overall score and grade\n- confidence level\n- sampled URLs or sample strategy\n- category scores\n- highest-priority findings\n- remediation backlog\n\n## Notes\n\n- Favor docs-delivery checks over marketing-site heuristics.\n- Do not fail a docs host for lacking MCP or plugin manifests unless the\n  host itself is meant to expose tools.\n- Treat raw byte size as supporting evidence, not as a primary scoring input.\n- Prefer short evidence excerpts and commands over long copied page text.\n",".agents/skills/agent-readiness-audit/references/report-template.md":"# Agent Readiness Report Template\n\nUse this structure for final audit output.\n\n```markdown\n## Agent Readiness Audit\n\n**Site:** <base-url>\n**Date:** <YYYY-MM-DD>\n**Overall score:** <score>/100\n**Grade:** <A-F>\n**Confidence:** <High|Medium|Low>\n\n### Summary\n\n<2-4 sentence verdict focused on what an external agent can actually\ndiscover, fetch, and interpret on this site.>\n\n### Category Scores\n\n| Category | Score | Notes |\n| --- | ---: | --- |\n| Discovery and policy | <x>/<y> | <short note> |\n| Retrieval and markdown delivery | <x>/<y> | <short note> |\n| Structure and semantics | <x>/<y> | <short note> |\n| Crawlability and delivery behavior | <x>/<y> | <short note> |\n| Machine-readable surfaces | <x>/<y> | <short note or N/A> |\n| Content legibility | <x>/<y> | <short note> |\n\n### Sample\n\n- Sample strategy: <sitemap / internal links / explicit URLs>\n- Sampled pages: <count>\n- Page types covered: <landing, guide, manual, reference, ...>\n- Weakest page type: <if any>\n\n### Findings\n\n- `P0`: <highest-priority blocker with evidence>\n- `P1`: <important recurring issue with evidence>\n- `P2`: <lower-priority or optional improvement>\n\n### Remediation\n\n- `P0`: <fix>, because <why it matters to agents>\n- `P1`: <fix>, because <why it matters to agents>\n- `P2`: <fix>, because <why it matters to agents>\n\n### Evidence\n\n- Sitewide checks: <llms.txt, robots.txt, sitemap.xml, manifests>\n- Fetch-path checks: <markdown negotiation, direct markdown routes,\n  advertised alternates, parity>\n- Structural checks: <h1/main/article/canonical/json-ld/title-h1 parity>\n- Code block checks: <fence count, language-tag coverage>\n- Scanner comparison: <optional>\n```\n\n## Notes\n\n- Keep the summary short and outcome-oriented.\n- Findings should refer to concrete URLs or page types.\n- If a criterion is `N/A`, say why instead of leaving it blank.\n",".agents/skills/agent-readiness-audit/references/rubric.md":"# Agent Readiness Rubric\n\nScore the site on a 100-point scale before normalization. If a criterion is\nnot applicable, remove its points from the denominator instead of treating\nit as failed.\n\n## Grade bands\n\n- `A`: 90-100\n- `B`: 80-89\n- `C`: 65-79\n- `D`: 50-64\n- `F`: below 50\n\n## Confidence levels\n\n- `High`: sitemap available and at least 12 sampled pages across at least\n  four page types\n- `Medium`: six to 11 sampled pages, or weaker coverage of page types\n- `Low`: fewer than six sampled pages, or homepage-biased sampling\n\n## Foundational caps\n\nApply these after computing the raw score:\n\n- No `sitemap.xml` and no `llms.txt`: maximum grade `C`\n- Markdown delivery fails on most sampled pages and no usable alternate\n  markdown path exists: maximum grade `D`\n- Main content is missing from initial HTML on more than 25% of sampled\n  pages: maximum grade `D`\n- `robots.txt` blocks broad crawl access to the docs site and the block is\n  not clearly intentional: maximum grade `F`\n\nOptional manifest gaps alone must not drop a docs-only host below `B`.\n\n## Categories\n\n### 1. Discovery and policy - 15 points\n\n- `5` `llms.txt` exists, is fetchable, and is useful for agent discovery\n- `4` `sitemap.xml` exists and includes the main docs corpus\n- `4` `robots.txt` is accessible and does not unintentionally block major\n  crawl agents or search agents\n- `2` curated bulk-discovery aid exists, such as `llms-full.txt` or an\n  equivalent machine-readable catalog\n\nWhen `llms.txt` exists, sample some URLs from it. Stale or misleading\ndiscovery links should reduce this category even if the file itself exists.\n\n### 2. Retrieval and markdown delivery - 25 points\n\n- `8` `Accept: text/markdown` works on sampled pages or an equivalent\n  negotiated markdown response exists\n- `5` a stable direct markdown route works on sampled pages\n- `5` page-level markdown hints, alternates, or UI actions point to a\n  working markdown URL\n- `4` markdown responses strip navigation chrome and preserve headings,\n  links, and code blocks cleanly\n- `3` HTML and markdown stay in parity across the sampled set\n\n### 3. Structure and semantics - 20 points\n\n- `6` sampled pages have one `h1` and a mostly consistent heading hierarchy\n- `5` `main` or `article` marks the primary content and the content is\n  present in the initial HTML\n- `4` canonical tags and stable final URLs are correct\n- `3` structured data such as breadcrumbs or article metadata exists where\n  appropriate\n- `2` headings expose stable anchors or deep-link targets, and the HTML title\n  or H1 stays reasonably aligned with the markdown H1\n\n### 4. Crawlability and delivery behavior - 15 points\n\n- `5` crawl directives are sane for a public docs property\n- `4` the site does not depend on client-side rendering to expose core\n  content\n- `3` cache and freshness signals are reasonable for bots, such as\n  `ETag`, `Last-Modified`, or useful cache headers\n- `3` redirect chains are short and predictable\n\n### 5. Machine-readable surfaces - 10 points\n\n- `4` API or reference sections expose OpenAPI, schema, or downloadable\n  machine-readable assets where relevant\n- `3` pages with interactive JavaScript reference UIs still provide a usable\n  non-JS fallback such as markdown, YAML, or another directly linked asset\n- `3` tool manifests such as MCP, plugin, or agent descriptors exist only\n  when the audited host is actually meant to expose tools\n\n### 6. Content legibility - 15 points\n\n- `5` markdown is clean and low-noise rather than a dump of site chrome\n- `4` headings and section intros are specific enough for retrieval and\n  chunking\n- `3` fenced code blocks are mostly language-tagged and remain copyable and\n  interpretable\n- `3` repeated banners, chat chrome, consent overlays, or other boilerplate\n  do not overwhelm the main content\n\n## Scoring guidance\n\nUse the full category only when the signal is consistently good across the\nsample. Partial credit is expected.\n\nExamples:\n\n- A sitewide `llms.txt` that exists but is stale or too shallow may earn\n  partial credit rather than full credit.\n- If markdown works only on some page types, score that criterion based on\n  observed coverage instead of failing or passing it outright.\n- If a working markdown route exists but the page advertises a dead\n  alternate URL, deduct in markdown discoverability rather than in raw\n  markdown availability.\n- If `llms.txt` exists but points to stale, broken, or inconsistent paths,\n  deduct in discovery rather than in core fetchability.\n- If tool manifests are irrelevant to the host, mark them `N/A`.\n- If a major page type is weaker than the rest of the site, note that\n  explicitly instead of letting stronger page types hide it in the average.\n\n## Reporting guidance\n\nFor every category, include one line that explains the score:\n\n- what was tested\n- what passed\n- what limited the score\n\nUse evidence from live fetches. Do not score from assumptions about the\nframework or source repository.\n",".agents/skills/create-lab-guide/SKILL.md":"---\nname: create-lab-guide\ndescription: \"Clone a dockersamples Labspace repo, extract learning objectives and module structure from labspace.yaml, and produce a Hugo guide page under content/guides/ with correct frontmatter, labspace-launch shortcode, and Docker docs style compliance. Use when asked to create a lab guide, write a Labspace page, add a Docker lab tutorial, migrate a lab to docs, or document a hands-on lab.\"\n---\n\n# Create Lab Guide\n\nCreate a guide page for a Docker Labspace: clone the source repo, extract\nstructure from `labspace.yaml`, write the Hugo markdown page, and validate.\n\n## Inputs\n\n- **REPO_NAME**: GitHub repo in the `dockersamples` org (e.g. `labspace-ai-fundamentals`)\n\n## Step 1: Clone the labspace repo\n\n```bash\nTMPDIR=$(mktemp -d)\ngit clone --depth 1 https://github.com/dockersamples/{REPO_NAME}.git \"$TMPDIR/{REPO_NAME}\"\n```\n\n## Step 2: Extract key information\n\nRead these files from the cloned repo:\n\n| File | Purpose |\n|------|---------|\n| `README.md` | Lab purpose and overview |\n| `labspace/labspace.yaml` | Module structure and content paths |\n| `labspace/*.md` | Module content (only files listed in `labspace.yaml`) |\n| `.github/workflows/*.yml` | Published Compose file URL for the launch command |\n| `compose.override.yaml` | Check for top-level `model` specs (triggers `model-download` param) |\n\nExtract:\n1. A short description for the `description` and `summary` frontmatter fields.\n2. Learning objectives from the module content.\n3. Whether a model download is required (`compose.override.yaml` → top-level `model` key).\n\n## Step 3: Write the guide markdown\n\nPlace the file at `content/guides/lab-{GUIDE_ID}.md`.\n\n```markdown\n---\ntitle: \"Lab: { Short title }\"\nlinkTitle: \"Lab: { Short title }\"\ndescription: |\n  A short description of the lab for SEO and social sharing.\nsummary: |\n  A short summary of the lab for the guides listing page. 2-3 lines.\nkeywords: AI, Docker, Model Runner, agentic apps, lab, labspace\naliases: # Include only for AI-related labs\n  - /labs/docker-for-ai/{REPO_NAME_WITHOUT_LABSPACE_PREFIX}/\nparams:\n  tags: [ai, labs]\n  time: 20 minutes\n  resource_links:\n    - title: A resource link pointing to relevant documentation or code\n      url: /ai/model-runner/\n    - title: Labspace repository\n      url: https://github.com/dockersamples/{REPO_NAME}\n---\n\nShort explanation of the lab and what it covers.\n\n## Launch the lab\n\n{{< labspace-launch image=\"dockersamples/{REPO_NAME}\" >}}\n\n## What you'll learn\n\nBy the end of this Labspace, you will have completed the following:\n\n- Objective #1\n- Objective #2\n- Objective #3\n\n## Modules\n\n| # | Module | Description |\n|---|--------|-------------|\n| 1 | Module #1 | Description of module #1 |\n| 2 | Module #2 | Description of module #2 |\n| 3 | Module #3 | Description of module #3 |\n```\n\nConditional rules:\n- All lab guides **must** include `labs` in `params.tags`.\n- AI-related labs: also add `ai` tag and an alias under `/labs/docker-for-ai/`.\n- If a model download is required: add `model-download: true` to the `labspace-launch` shortcode.\n\n## Step 4: Apply Docker docs style rules\n\nFollow STYLE.md and COMPONENTS.md. Key rules:\n\n| Avoid | Use instead |\n|-------|-------------|\n| \"we\", \"let's\" | Imperative voice or \"you\" |\n| \"simply\", \"easily\", \"just\" | Remove the hedge word |\n| \"allows you to\" / \"enables you to\" | \"lets you\" or rephrase |\n| \"click\" | \"select\" |\n| Bold for emphasis / product names | Bold only for UI elements |\n| \"currently\", \"new\", \"recently\" | Remove time-relative language |\n\nUse `console` as the language hint for shell blocks with `$` prompts.\nUse contractions (\"it's\", \"you're\", \"don't\").\n\n## Step 5: Validate\n\n1. Confirm frontmatter has `title`, `description`, `keywords`, and `params.tags` including `labs`.\n2. Run `npx --no-install rumdl fmt <file>` to format.\n3. Run `docker buildx bake lint vale` and fix any errors.\n4. Re-read the file and verify: correct shortcode syntax, objectives match source content, modules match `labspace.yaml`, no vendored paths edited.\n\nDo not proceed to commit until validation passes.\n",".agents/skills/create-pr/SKILL.md":"---\nname: create-pr\ndescription: >\n  Push the current branch and create a pull request against docker/docs.\n  Use after changes are committed and reviewed. \"create a PR\", \"submit the\n  fix\", \"open a pull request for this\".\n---\n\n# Create PR\n\nPush the branch and create a properly structured pull request.\n\n## 1. Verify the branch\n\nConfirm you're on a dedicated branch, not the default branch:\n\n```bash\ngit branch --show-current   # must not be main or master\n```\n\nIf this returns `main` or `master`, stop. Create a branch and move your\ncommits onto it before continuing.\n\nConfirm commits exist and the working tree is clean:\n\n```bash\ngit log --oneline main..HEAD   # confirm commits exist\ngit status --porcelain         # must print nothing\n```\n\nIf `git status --porcelain` prints anything, there are uncommitted or\nunstaged changes. Stop and commit them — or unstage stray files like\n`package-lock.json` — before opening a PR. Don't open a PR mid-edit.\n\n## 2. Push the branch\n\nIdentify the remote that points at your fork. Inspect the remotes:\n\n```bash\ngit remote -v\n```\n\nIf `origin` is your fork, use it. If `origin` points at canonical\n`docker/docs` (the upstream), push to your separate fork remote instead —\nnever push the branch to `docker/docs` directly:\n\n```bash\nFORK_REMOTE=origin   # or the name of your fork remote if origin is upstream\ngit push -u \"$FORK_REMOTE\" <branch-name>\n```\n\n## 3. Create the PR\n\nBefore creating a PR for an issue, check whether that issue already has an open\nlinked PR:\n\n```bash\ngh api repos/docker/docs/issues/<issue-number>/timeline --paginate \\\n  --jq '.[] | select((.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") and .source.issue.pull_request and .source.issue.state==\"open\") | {url: .source.issue.html_url, title: .source.issue.title}'\n```\n\nIf this returns an open PR that addresses the same issue, stop. Don't open a\nduplicate PR; report the existing PR instead. Only proceed if there is no open\nlinked PR, or if the existing PR clearly does not address the issue and you\nexplain why in the new PR body.\n\nDerive the fork owner dynamically from the same fork remote you pushed to:\n\n```bash\nFORK_OWNER=$(git remote get-url \"$FORK_REMOTE\" | sed -E 's|.*[:/]([^/]+)/[^/]+(\\.git)?$|\\1|')\n```\n\n```bash\ngh pr create --repo docker/docs \\\n  --head \"${FORK_OWNER}:<branch-name>\" \\\n  --title \"<concise summary under 70 chars>\" \\\n  --body \"$(cat <<'EOF'\n## Summary\n\n<1-2 sentences: what was wrong and what was changed>\n\nCloses #NNNN\n\nGenerated by <active coding agent name>\nEOF\n)\"\n```\n\nPrefix the title with the change type to match repo convention — `docs:` for\ndocumentation changes (or another scope like `hub:` when appropriate), for\nexample `docs: fix broken link on install page`.\n\nKeep the body short. Reviewers need to know what changed and why — nothing\nelse. Do **not** add a \"Test plan\" section — documentation PRs don't need one.\n\nUse an accurate disclosure footer that names the active coding agent, for\nexample `Generated by Codex` or `Generated by Claude Code`.\n\n### Optional: Netlify preview entry path\n\nIf the PR primarily edits a single page or a focused section of pages, add a\n`@netlify` stanza to the PR body (for example, just below the Summary). This\nsets the entry path for the Netlify deploy preview so reviewers land on the\nedited page instead of the site root:\n\n```markdown\n@netlify /desktop/setup/install/\n```\n\nThe stanza takes a single published URL path. Derive it from the source file\npath: drop the `content/` prefix and `.md` suffix, strip the `/manuals`\nsegment, and add a trailing slash. For example,\n`content/manuals/desktop/setup/install/mac-install.md` becomes\n`/desktop/setup/install/mac-install/`.\n\nOnly add this when the change is focused on one page or section. Skip it for\nPRs that touch many unrelated pages — there is no useful single entry path.\n\n### Optional: Preview links\n\nWhen the change is focused, also add direct links to the deploy preview in the\nPR body so reviewers can jump straight to the affected pages. The preview URL\nembeds the PR number:\n\n```\nhttps://deploy-preview-<pr-number>--docsdocker.netlify.app/path/to/page/\n```\n\nThe PR number isn't known until `gh pr create` returns, so add these links\nafter creating the PR by updating the body:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --body \"...\"\n```\n\nUse the same source-path-to-URL mapping as the `@netlify` stanza above.\n\n## 4. Apply labels and request review\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<pr-number>/labels \\\n  --method POST \\\n  --field 'labels[]=status/review'\n```\n\nRequest review:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --add-reviewer docker/docs-team\n```\n\nVerify the reviewer was assigned:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json reviewRequests \\\n  --jq '.reviewRequests[].slug'\n```\n\nIf the team doesn't appear, use the API directly:\n\n```bash\ngh api repos/docker/docs/pulls/<pr-number>/requested_reviewers \\\n  --method POST --field 'team_reviewers[]=docs-team'\n```\n\n## 5. Report\n\nPrint the PR URL and current CI state:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json url,state\ngh pr checks <pr-number> --repo docker/docs --json name,state\n```\n\n## Notes\n\n- Always use `Closes #NNNN` (not \"Fixes\") for GitHub auto-close linkage\n- One issue, one branch, one PR — never combine\n",".agents/skills/curate-whats-new/SKILL.md":"---\nname: curate-whats-new\ndescription: Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights.\n---\n\n# Curate What's New\n\nTreat merged documentation as evidence that a capability shipped, but do not\ntreat a documentation change as news by itself.\n\nInclude an item only when the merged documentation directly shows all of the\nfollowing:\n\n- A released user-facing feature, material enhancement, or broader availability\n  milestone that was not available before the period\n- A substantial capability or workflow, not new syntax or a small control\n  within an existing workflow\n- Enough Docker-wide editorial significance to merit proactively telling users\n  about it outside product release notes\n- A useful published page and a factual title and description\n\nApply a high bar. The result is a curated launch archive, not a complete\nchangelog. A specialized feature can qualify when its user impact is\nsubstantial. A quiet period can produce few or no items.\n\n## Exclusions\n\nExclude documentation maintenance; fixes; rewrites; guidance for old behavior;\nroutine release or generated-content syncs; limitations, prerequisites, and\nworkarounds; narrow flags, settings, command variants, protocols, and\ncompatibility changes; incremental UI, safety, permissions, or observability\nimprovements; and lower-level Engine, Build, networking, or storage changes.\nThese qualify only when they are part of an independently newsworthy\nproduct-level launch.\n\nJudge the user outcome, not PR size, product popularity, labels, changed lines,\na dedicated page, or the existence of a new API or command.\n\n## Select highlights\n\nInclude every qualifying launch; do not impose a quota. Mark the five most\nimportant as `featured: true`, or all items when fewer than five qualify. Rank\nby the magnitude and distinctness of the user outcome and the value of helping\nits audience discover it. Breadth can matter, but a major capability for a\nspecialized audience can outrank a smaller change for a broad audience. Recency\nand product variety are not ranking goals.\n\nCreate one item per launch and combine PRs that document the same launch.\nPreserve existing copy while it remains accurate and qualifies. Change featured\nstatus only when the relative importance of the candidate set changes.\n\n## Procedure\n\n1. Read `data/whats-new.json`.\n2. Determine the review mode from the request:\n   - For an incremental review, list PRs merged from the day after the supplied\n     checkpoint through the end of the publication window. Retain existing\n     items inside the publication window without re-reviewing their source PRs.\n   - For a full review, inspect every PR merged in the supplied publication\n     window.\n3. List PRs in the range that applies to the review mode:\n\n   ```console\n   $ gh pr list --repo docker/docs --state merged --search 'merged:START..END' --limit 200\n   ```\n\n   If an incremental search returns no PRs, skip candidate inspection and only\n   remove expired items.\n4. Inspect the diff and resulting pages for every plausible new candidate.\n5. Decide what qualifies using only evidence in the merged documentation.\n6. Remove existing items published before the requested publication window.\n   Add newly qualifying launches, combine related PRs, and reconsider featured\n   status across the resulting list. Do not replace or rewrite retained items\n   merely because they were not part of the incremental candidate range.\n7. Replace `period_start`, `period_end`, and `items` in\n   `data/whats-new.json`. Sort items by `published` date, newest first.\n8. Write `.pr-body.md` with the publication period, selected highlights and\n   source PRs, plus concise reasons for plausible exclusions.\n\nEach item must contain `product`, `title`, `description`, `url`, `published`,\n`source_prs`, and `featured`. Use the canonical product name, a published\ninternal URL, the merge date in `YYYY-MM-DD` format, and source PR numbers.\n\nWrite factual, restrained copy. Avoid superlatives, promotional language, and\nclaims about ease or importance. Do not modify tracked files other than\n`data/whats-new.json`.\n",".agents/skills/curate-whats-new/agents/openai.yaml":"interface:\n  display_name: \"Curate What's New\"\n  short_description: \"Curate noteworthy Docker launches from merged docs\"\n  default_prompt: \"Use $curate-whats-new to curate Docker launches published during the requested date range.\"\n",".agents/skills/fix-issue/SKILL.md":"---\nname: fix-issue\ndescription: >\n  Fix a single GitHub issue end-to-end: triage, research, write the fix,\n  review, and create a PR. Use when asked to fix an issue: \"fix issue 1234\",\n  \"resolve #500\", \"create a PR for issue 200\".\nargument-hint: \"<issue-number>\"\n---\n\n# Fix Issue\n\nGiven GitHub issue **$ARGUMENTS**, decide what to do with it and either\nclose it or fix it. This skill orchestrates the composable skills — it owns\nthe decision tree, not the individual steps.\n\n## 1. Triage\n\nInvoke `/triage-issue $ARGUMENTS` to understand the issue and decide what\nto do. This runs in a forked subagent and returns a verdict.\n\n## 2. Act on the triage result\n\nIf triage says **close it** — comment with the reason and close:\n```bash\ngh issue close $ARGUMENTS --repo docker/docs \\\n  --comment \"<one sentence explaining why>\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **escalate upstream** — comment noting the repo and stop:\n```bash\ngh issue comment $ARGUMENTS --repo docker/docs \\\n  --body \"This needs to be fixed in <upstream-repo>.\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **leave it open** — comment explaining what was checked and\nwhat's unclear. Do not close.\nDone.\n\nEnd every issue comment with an accurate agent-disclosure footer that names\nthe active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n\nIf triage says **fix it** — proceed to step 3.\n\n## 3. Research\n\nInvoke `/research` to locate affected files, verify facts, and identify\nthe fix. The issue context carries over from triage. This runs inline —\nfindings stay in conversation context for the write step.\n\nIf research reveals the issue is upstream or cannot be fixed (e.g.\nunverifiable URLs), comment on the issue and stop.\n\n## 4. Write\n\nInvoke `/write` to create a branch, make the change, format, self-review,\nand commit.\n\n## 5. Review\n\nInvoke `/review-changes` to check the diff for correctness, coherence, and\nmechanical compliance. This runs in a forked subagent with fresh context.\n\nIf issues are found, fix them and re-review until clean.\n\n## 6. Create PR\n\nInvoke `/create-pr` to push the branch and open a pull request.\n\n## 7. Return to main\n\n```bash\ngit checkout main\n```\n\n## 8. Report\n\nSummarize what happened: the issue number, what was done (closed, escalated,\nfixed with a PR link), and why — in a sentence or two.\n",".agents/skills/maintain-pr/SKILL.md":"---\nname: maintain-pr\ndescription: >\n  Maintain and follow up on a single Docker documentation pull request that\n  you own or are responsible for updating. Check CI and review feedback, fix\n  actionable failures, push changes, reply to comments, and report status.\n  Use for requests such as \"babysit this PR\", \"check the status of my PR\",\n  \"fix CI on my PR\", or \"address review comments on #500\". Do not use for\n  maintainer review of an incoming contribution; use review-pr for that.\n---\n\n# Maintain PR\n\nDo one maintenance pass over the specified author-owned PR: inspect its\nstate, fix actionable failures or feedback, reply to reviewers, and report\nthe result. This workflow may modify the branch and GitHub because the user\nis asking to maintain the PR. Do not apply it to an incoming PR merely\nbecause the user asks to review or assess it.\n\n## 1. Gather PR state\n\n```bash\ngh pr view <PR> --repo docker/docs --json state,title,url,headRefName,headRepositoryOwner,comments,reviews,reviewDecision\ngh pr checks <PR> --repo docker/docs --json name,state,detailsUrl\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, in_reply_to_id}]'\n```\n\nAlways check both top-level reviews and inline comments. A review with an\nempty body may still contain line-level feedback. Confirm that the PR is one\nthe user owns or is authorized to update before checking out or pushing its\nbranch. If not, stop and use `review-pr`.\n\n## 2. Handle terminal states\n\nIf merged, report the final state and identify unanswered review comments.\nReply only when the user remains responsible for follow-up.\n\nIf closed without merge, read the closing context and report the reason.\nCommon causes include maintainer rejection, supersession, or automation.\n\n## 3. Diagnose CI failures\n\n- Read the failure details.\n- Determine whether the failure comes from the PR or predates it.\n- Fix actionable failures in the PR's changed files.\n- Report pre-existing or upstream failures without changing unrelated files.\n\nFollow repository instructions for formatting, targeted linting, explicit\nstaging, commits, and pushes. Preserve unrelated working-tree changes.\n\n## 4. Address review feedback\n\nTreat every review comment as a claim to verify. Implement it only when the\nevidence supports it; explain any evidence-based disagreement.\n\nAfter each fix:\n\n1. Format and validate the changed files.\n2. Commit and push the focused change.\n3. Reply to every addressed thread with what changed or why no change was\n   made.\n4. End replies with an accurate agent-disclosure footer, such as\n   `Generated by Codex`.\n5. Resolve threads only after replying.\n6. Re-request review when appropriate.\n\nUse the inline comment endpoint to reply:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --method POST \\\n  --field in_reply_to=<COMMENT_ID> \\\n  --field body='<RESPONSE>'\n```\n\nUse GraphQL to retrieve unresolved review-thread IDs and resolve only the\nthreads that were addressed. Do not silently fix feedback without replying.\n\n## 5. Report\n\n```markdown\n## PR #<number>: <title>\n\n**State:** <open, merged, or closed>\n**CI:** <passing, failing, or pending>\n**Review:** <approved, changes requested, or pending>\n**Action taken:** <changes, replies, and thread resolution, or none needed>\n```\n",".agents/skills/maintain-pr/agents/openai.yaml":"interface:\n  display_name: \"Maintain PR\"\n  short_description: \"Maintain an authored PR through review and CI\"\n  default_prompt: \"Use $maintain-pr to maintain this pull request through CI and review follow-up.\"\n",".agents/skills/migrate-content-ia/SKILL.md":"---\nname: migrate-content-ia\ndescription: >\n  Handle Hugo docs information-architecture moves: discover old vs new URLs,\n  add front matter aliases (Phase 1), update in-repo links (Phase 2), interactive\n  List 2 resolution and fragment validation (Phase 3; no guessing). Supports\n  PR-scoped mapping plus whole-content sweeps for inbound links to that mapping,\n  or a full-site follow-up. Triggers on: \"IA migration\", \"redirects for moved\n  pages\", \"fix links after content move\", \"PR-scoped link/anchor pass\",\n  \"aliases for old URLs\". After branch work, chain the review-changes skill\n  (main...HEAD) before a PR. Agents must run the in-file required procedure\n  and definition of done, not the phases alone in isolation.\n---\n\n# Migrate content IA (redirects + links + anchors)\n\nUse this skill when pages **move or rename** under `content/` and you must\npreserve old public URLs and/or fix cross-references. Work in **phases**;\nchoose **PR-scoped** vs **full-site** mode per run.\n\n**Read first:** **CLAUDE.md** / **AGENTS.md** (URL rules, vendored areas, external\nlinks, special cases) and **hugo.yaml** (`permalinks`, `refLinksErrorLevel`,\n`disablePathToLower`). For **prose and link text**, follow **STYLE.md**; for\n**components, front matter, and link examples**, follow **COMPONENTS.md**.\n\n**Related skills:** **research** helps map moves and find inbound links; **write**\ncommits minimal edits. Run this skill’s phases after the move is identified (or\nin parallel with research for large IA work).\n\n## Agent: required procedure (do not skip)\n\n**Common mistake (wrong):** use **`git diff main...HEAD` (or the PR’s file\nlist) as the full set of places to fix links** for a migration. That set shows\n**what *moved***; it is **not** the list of every page that **points *to*** a\nmoved page. Inbound stragglers are often in files the PR **never** touched. You\nmust still **sweep the repo** for every string in the **old path and published-URL set**\nfor this run, not only for “files in the diff.”\n\n**Definition of done (when the migration is *finished*):** **Both** of the\nfollowing (unless the user or **AGENTS.md** **explicitly defers** a **List 2**\nitem in **Phase 3**; document the deferral):\n\n1. **`docker buildx bake validate`** passes for the branch, with no new\n   build/link errors from this work.\n2. A **sweep of the old path and published-URL set for this run** (see\n   [Sweep commands](#sweep-commands) below) finds **no** remaining\n   migration-relevant **inbound** reference—**including**:\n   - links to an old **source** path (plain `.md` and equivalent `ref` forms),\n   - links that use the old path **and** a `#fragment`,\n   - and, where your mapping includes them, old **published-style** `link:` /\n   `url:` / full-site URL strings,  \n   **except** intentional entries to keep: for example `aliases` on the **new**\n   canonical page, or **redirects.yml** *sources* you must not edit per policy.\n   (A hit on a **source** that is only an `alias` line on the new page is\n   **expected**—do not “fix” that away; distinguish alias rows from straggler\n   links in body or nav config.)\n\n**Chaining (policy):** when this branch’s content work is ready for handoff,\n**run the [review-changes](../review-changes/SKILL.md) skill** on\n**`main...HEAD`** (or **`merge-base`…`HEAD`** for a different target branch) so\nthe **whole branch** is re-read for cross-page issues before opening a PR. Do\nnot treat phases 0–3 alone as the final check.\n\n**Run in order (mandatory for agents):**\n\n1. **Scope the moves (mapping input):** set the Git range like **review-changes**\n   (for a PR to `main`: `git diff --name-only main...HEAD`; for another target:\n   `BASE=$(git merge-base <target-branch> HEAD)` then\n   `git diff --name-only $BASE...HEAD`, as in **Phase 0.5**). Include\n   renames; build the **old → new** table (source and published) per **Phase\n   0**.\n2. **Sweep and list:** for every **old** path/URL in that table, run\n   [Sweep commands](#sweep-commands) on the **allowed** trees. Record\n   every hit as **List 1** (no `#`) or **List 2** (old path with `#...`) per\n   **Phase 0.5**.\n3. **Phased edits:** **Phase 1** (`aliases`), then **Phase 2** (List 1), then\n   **Phase 3** (List 2) with **no guessing**—as in the sections below.\n4. **Re-sweep** the same old-path set, then run **`docker buildx bake\n   validate`**. The **Definition of done** above is met or you have **explicit\n   defers** for the remainder.\n5. **review-changes:** run **[review-changes](../review-changes/SKILL.md)**\n   on the branch vs **`main`…`HEAD`** (or the correct base) before a PR.\n\n### Sweep commands\n\nUse a **repository** search (e.g. `rg` / your IDE) so **nothing** in the\nallowed scope is only eyeballed.\n\n**Trees to include** (at minimum): all of `content/`, plus **`data/`** and\n**`layouts/`** when a migration can appear in config, `link:`-like fields,\nshortcodes, or hardcoded path strings. Follow **Vendored / generated** rules in\n**AGENTS.md**; do not edit disallowed files.\n\n**What to search for (repeat per row in the old side of the mapping):**\n\n- **Hugo / source form:** path segments that identify the *old* file, e.g.\n  `manuals/.../old-segment/...` or `../old-segment/.../page.md` as your tree\n  uses; include variants that still appear in the repo.\n- **Published / site form:** e.g. `/admin/.../old-slug/` in front matter, nav\n  `url:`, or `https://docs.docker.com/...` in allowed files—**match the\n  file’s** established pattern, per **Conventions** below.\n- **Anchors:** search for the **old path string**; matches that also include\n  `#...` belong on **List 2** for **Phase 3** unless the whole link is\n  a pure path-only case.\n\n[scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) (if present) prints\n**`PR_SCOPE_FILES` only**—it does **not** replace this sweep. Use it to build\nthe **old → new** table, **not** to list where inbound links were fixed.\n\n## Progressive disclosure (optional)\n\nThe procedure below stays in this file. If a run produces a very large\n**old → new** URL table, store that table in **`reference.md`** in this skill\ndirectory and link it from the task summary, so the agent reads the long\nmapping only when needed.\n\n## Modes\n\n- **PR-scoped (typical for a single PR)**  \n  - **What the PR “owns” (focus):** use `git diff` / `base...HEAD` to know which\n    pages and renames the branch actually moves (`PR_SCOPE_FILES`). The **old →\n    new** mapping and **List 1 / List 2** for this migration are defined from\n    **that** work, not from unrelated areas.\n  - **Where to look for stale references (sweep):** search broadly—typically all\n    of `content/` (and config, shortcodes, layouts, per Conventions)—for **inbound**\n    links and fields whose **target** is an **old** path or URL in **this** PR’s\n    mapping. Inbound stragglers are often in files the PR never touched; finding\n    them is **in scope** for this migration.  \n  - **What to edit:** update **any** file in the allowed trees that contains a\n    **migration-relevant** reference (target ∈ this PR’s old path set) according\n    to the phases below. **Do not** treat `PR_SCOPE_FILES` as a hard limit on\n    *which files you may save* for **inbound** link repairs (unless\n    project policy for a given PR says otherwise; then follow policy and\n    **defer** out-of-PR file fixes).\n  - **Out of scope (defer / ignore in this run):** link and anchor problems that\n    are **not** about this PR’s old→new map—e.g. a different area’s own slug\n    issues, rot unrelated to the remapped path set. *Example:* a PR that only\n    remaps `content/strawberry/...` should not “fix the whole site”; it **should**\n    still fix a link under `mango/…` that **points at** an old `strawberry/…` path\n    in the mapping, and **should not** chase **mango/**-only issues that do\n    not involve those old targets.\n\n- **Full-site (complete migration after the PR)**  \n  - Update stragglers **across the repo** (or all inbound links to moved\n    sections), including config-driven `link:` fields if policy allows.  \n  - Still make **minimal** edits; no drive-by rewrites to **unrelated** targets\n    outside the run’s **declared** mapping and lists.\n\n### No guessing\n\n- The agent must **not** guess **replacement paths, published URLs, or fragment\n  IDs** (including for consolidated pages, renamed headings, or\n  “semantic” remaps of `#anchor` → new `#…`). If the user has not given an\n  explicit new target, **ask**, **defer**, or **stop** per **AGENTS.md**; never\n  infer, autocomplete, or substitute a plausible fragment from the target page’s\n  heading list. That rule applies in **every** phase, including after validation\n  in Phase 3.\n\n---\n\n## Conventions (links, anchors, redirects)\n\n### Front matter `aliases` (redirects)\n\n- Per **COMPONENTS.md**, `aliases` are **URLs that redirect to this page**.\n- Add or **merge** on the **new canonical** page; do not drop unrelated\n  entries. Match local examples: **published-style paths** (leading `/`), and\n  **trailing `/`** when that matches existing pages in the same area.\n- **No** speculative redirects for URLs that were never published.\n- **Collision check** before adding: no other page or redirect may already\n  own the same old path.\n- If the site also uses **`data/redirects.yml`**, only add entries when\n  project policy requires it; avoid duplicating the same old URL in\n  `aliases` **and** `redirects.yml` unless maintainers do.\n\n### Internal links in Markdown (STYLE.md + COMPONENTS.md)\n\n- Use **relative paths to source files** (e.g. `../section/page.md`) with\n  **`.md`**, following **COMPONENTS.md** examples, unless the file already\n  uses an established pattern (e.g. some `link:` or nav fields use **published**\n  paths without `manuals` or `.md` — **match the surrounding file**).\n- Keep **CLAUDE.md** / **AGENTS.md** rules: internal ref targets under\n  `content/manuals/...` often use the full **`/manuals/...`** path; published\n  URLs omit the `manuals` segment—do not confuse the two when fixing links.\n- **Link text (STYLE.md):** descriptive, ~**5 words**; no “click here” or\n  “learn more”; **no** end punctuation **inside** the link text; **no** bold/italic\n  on link text unless normal in the sentence.\n- **Headings (STYLE):** **sentence case**; do not rename headings in passing\n  unless the migration requires it (heading changes break fragments).\n\n### Shortcodes and layouts (links not only in Markdown)\n\n- **Phase 2–3 scope includes** any **shortcode or layout partial** (under\n  **Modes**, search broadly for inbound links to the migration; **edits** follow\n  the same file-level rules as for Markdown) that emits links: e.g. `ref` /\n  `relref`, `link` fields in shortcode args, or hardcoded\n  `docs.docker.com` / path strings. Grep for old paths, slugs, and fragments\n  under `layouts/shortcodes/` (and `layouts/_default/` if partials build nav).\n- Match each file’s existing pattern; do not rewrite working shortcode style\n  just to “clean up.”\n\n### Fragments / anchors (Phase 3)\n\n- List 1 / List 2: fragment-bearing **cross-references to old paths** are tracked\n  on **List 2** in Phase 0.5; do not bulk-rewrite them in the **List 1** pass\n  (Phase 2). See Phase 0.5 and Phase 2.\n- **Valid `#fragment` values:** after the user supplies a new fragment, it should\n  match the **target** page’s **generated** heading ID (Hugo slugification; see\n  **CLAUDE.md** / **AGENTS.md**). The agent still **validates** (see Phase 3) and\n  must **not** “pick” a different id from the page to replace a bad answer—**No\n  guessing**.\n- Same-page: `[Text](#section-id)`.\n- Cross-page: when user-provided, `#fragment` must still be checked against the\n  **target** file. Validate fragments in shortcodes the same way as in body\n  Markdown.\n\n### External URLs (**AGENTS.md**)\n\n- Do not commit **guessed** replacement URLs. If a URL cannot be verified,\n  treat as blocked or drop the fragment per AGENTS guidance. See also **No\n  guessing** above; internal and external link targets are treated the same for\n  inference: **none** without user input or a verified source.\n\n### Special cases (**AGENTS.md**)\n\n- **Engine API version** pages: respect coordinated **`/latest/` `aliases`**\n  rules—never leave two version files both owning `/latest/`.\n- **Vendored / generated** trees: read-only; see CLAUDE.md. Do not “fix” links\n  there if policy forbids.\n\n---\n\n## Phase 0 — Discovery (read-only; may use whole repo)\n\n1. Read **hugo.yaml** (permalinks, `refLinksErrorLevel`, `disablePathToLower`).\n2. From the branch (diff, renames), build a **mapping table**:\n   - old source path → new source path  \n   - old published URL → new published URL (from permalink rules)\n3. **Case:** with `disablePathToLower: true`, filesystem path **case** appears in\n   URLs—**directory and link casing must match** (e.g. `setup` vs `Setup`).\n4. When planning **inbound link** fixes, treat old-path references as two\n   categories: **no fragment** vs **with `#fragment`**. That split feeds\n   **List 1** and **List 2** in Phase 0.5 and drives Phase 2 ordering (see\n   there).\n\n---\n\n## Phase 0.5 — PR-scoped evaluation (required before edits in PR mode)\n\n1. **Set `PR_SCOPE_FILES` (Git scope for PR mode)**  \n   - When the PR **targets `main`**, use the same triple-dot form as\n     **review-changes**:  \n     `git diff --name-only main...HEAD`  \n   - For a **different target branch** or a custom base, use the merge base:  \n     `BASE=$(git merge-base <target-branch> HEAD)`  \n     then:  \n     `git diff --name-only \"$BASE\"...HEAD`  \n   - Those paths define **what moved** in the branch; they are the primary input\n     to the **old → new** path/URL table. They are **not** a hard cap on *where\n     to search* for **inbound** links (see **Modes**): sweeps for links **to** old\n     paths usually cover all of `content/` (and other trees per Conventions).  \n   - If project policy **limits edits** to the diff for a given PR, follow that\n     and **defer** link fixes in files outside the diff; note the exception in\n     the task if the user relaxes that policy.\n\n2. Build checklists (see **Modes** for sweep vs area-of-work):\n   - path/URL mapping this run must honor (old source path → new; old published\n     → new, from the **PR’s** moves in PR-scoped mode, or the **declared** full\n     migration in full-site mode)\n   - **List 1 — old path, no fragment:** every **inbound** reference, found on\n     the **sweep** surface, to a moved **old** path that does **not** include a\n     `#...` fragment (e.g. `…/banana.md` in the repo’s link style for that\n     file).\n   - **List 2 — old path with fragment:** every **inbound** reference, found on\n     the same sweep, to a moved **old** path that **includes** a `#...` fragment\n     (e.g. `…/banana.md#anchor` or the published-style equivalent in context). The\n     **same** old path string may appear on **both** List 1 and List 2 for\n     different links; duplication across the two lists is OK.\n   - **Matching rules:** when recording List 1 / List 2, use **one** consistent\n     path representation for comparison (e.g. relative `../path/banana.md` vs\n     root-anchored) **per the conventions in this doc** and the **surrounding\n     file’s** established pattern. Agents compare and skip List 2 links in the\n     List 1 pass using the **same** representation rules.\n3. **Out of scope** for the lists: only include references whose **old** target\n   is in this run’s **mapping**. Do not build List 1/2 for unrelated **mango/**\n   (or other) problems unless those links also target an **old** path that this\n   migration renames. Defer those issues separately (see **Modes**).\n\n---\n\n## Phase 1 — `aliases` (old published URLs)\n\n1. On each **new** canonical page, add or merge **`aliases`** for every **real**\n   former public URL.\n2. Do not strip existing unrelated aliases.\n3. **PR-scoped:** add aliases only where the canonical file is in scope or the\n   project requires it; otherwise list missing alias targets for follow-up.\n\n---\n\n## Phase 2 — In-repo link reference updates\n\n1. **List 1 first (path only):** update references that belong to **List 1**\n   (old path, **no** fragment). Replace old source paths or old published URLs\n   with the **new** targets; preserve each file’s link pattern (relative vs\n   root-anchored `.md` paths). **Do not** apply the same bulk path replacement to\n   links that appear in **List 2** (old path **with** `#...`) during this\n   sub-step—**leave** every **List 2** link **unchanged** for now.\n2. **After List 1 is complete:** **re-scan** the **same** **sweep** surface as\n   in Phase 0.5 (e.g. all of `content/` plus config) or **print** a clear list of\n   all **remaining** **List 2** entries. Those links should still point at the\n   **old** path and **old** fragment until Phase 3.  \n3. **Full-site (extra sweep):** after steps 1–2, still use **AGENTS “Page\n   deletion checklist”**-style thoroughness for **config / front matter**\n   `link:` and similar so nav and grids are not left on old slugs. Apply the\n   **List 1 / List 2** rules there too: path-only old references first; defer\n   fragment-bearing rewrites in line with **List 2** until Phase 3.\n4. **PR-scoped (which files to change):** apply List 1 and later Phase 3 updates\n   to **every** file the **sweep** finds with a **migration-relevant** reference\n   (inbound to an **old** path in the mapping), including files **not** in\n   `PR_SCOPE_FILES`, per **Modes**. **Log** and **defer** (do not “fix”)\n   unrelated stragglers. If policy forbids out-of-PR file edits, defer per step 1\n   of Phase 0.5.  \n5. Include **shortcodes and layout partials** (see Conventions and **Modes** for\n   sweep vs focus).\n\n---\n\n## Phase 3 — List 2: interactive path and fragment resolution\n\n**Prerequisites:** Phase 2 has updated **List 1**; **List 2** still lists **old\npath + `#...`** (unchanged) for this migration. See **Modes** for which files\nmay be edited; **No guessing** applies.\n\n1. **Print List 2** to the user: every remaining **old path** + `#anchor` (in the\n   agreed representation), so nothing is hidden before the loop.\n2. **For each distinct** `old-path#oldAnchor` (or process in the order the user\n   prefers, one at a time):  \n   - Ask: **What is the new path (and fragment, if any) for this content?** The\n     user may give a new source path, published URL, and/or `#newAnchor` per\n     project conventions.  \n   - **Validate** the user’s answer: open the **target** page (or resolve the\n     target) and check that `#newAnchor` (if any) **exists** as a real heading\n     / generated id on that page, per **CLAUDE.md** / **AGENTS.md** (same rules\n     as the rest of the site). **Do not** replace the user’s fragment with a\n     “better” one from the file.  \n   - If validation **fails** (unknown target file, or `#newAnchor` not found on\n     the page): **warn** clearly (what failed: path vs missing fragment), then\n     **ask again** for a corrected path and/or fragment. **Repeat** until\n     validation passes or the user **defers** / **drops** the fragment (per\n     **AGENTS.md**). **Never** guess a new fragment to fix the problem.  \n   - When validation **passes:** update **all** in-repo references that match\n     that **same** `old-path#oldAnchor` to the user-approved `new-path#newAnchor`\n     (respect each file’s link style; include shortcodes/layouts on the same\n     **sweep** surface as Phase 2).  \n3. **Repeat** from step 1: **re-print** or **re-scan** for **List 2** until it is\n   **empty** or the user defers the remainder.  \n4. **PR-scoped / full-site:** the **loop** is the same. **Edits** follow **Modes**:\n   migration-relevant **inbound** links may live in any file on the sweep; do\n   not expand into **unrelated** link debt from other areas. Defer as in **Modes**\n   and Phase 0.5.\n\n---\n\n## Optional: scripts helper\n\nThis skill includes a small **scope helper** so agents do not re-derive Git\nrecipes. See [scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) — it prints\npaths in PR scope for a given target branch (default `main`).\n\n---\n\n## Verification\n\n```bash\ndocker buildx bake validate\n```\n\nUse the **Definition of done** in **Agent: required procedure (do not skip)**\nas the final bar: **validate** must pass, and the **sweep** must be clean for\n**plain** and **`#fragment`** old-path references, **or** the remainder must be\n**explicitly deferred** in **Phase 3** per **AGENTS.md** / the user. Mid-run,\n**Phase 2** may still leave **List 2** links unchanged **until** Phase 3; that\nintermediate state is **not** the finished migration.\n",".agents/skills/research/SKILL.md":"---\nname: research\ndescription: >\n  Research a documentation topic — locate affected files, understand the\n  problem, identify what to change. Use when investigating an issue, a\n  question, or a topic before writing a fix. Triggers on: \"research issue\n  1234\", \"investigate what needs changing for #500\", \"what files are\n  affected by #200\", \"where is X documented\", \"is our docs page about Y\n  accurate\", \"look into how we document Z\".\n---\n\n# Research\n\nThoroughly investigate the topic at hand and produce a clear plan for\nthe fix. The goal is to identify exact files, named targets within those\nfiles, and the verified content needed for the fix.\n\n## 1. Gather context\n\nIf the input is a GitHub issue number, fetch it:\n\n```bash\ngh issue view <number> --repo docker/docs \\\n  --json number,title,body,labels,comments\n```\n\nOtherwise, work from what was provided — a description, a URL, a question,\nor prior conversation context. Identify the topic, affected feature, or\npage to investigate.\n\n## 2. Locate affected files\n\nSearch `content/` using the URL or topic from the issue. Remember the\n`/manuals` prefix mapping when converting URLs to file paths.\n\nFor each candidate file, read the relevant section to confirm it contains\nthe reported problem.\n\n## 3. Check vendored ownership\n\nBefore planning any edit, verify the file is editable locally:\n\n- `_vendor/` — read-only, vendored via Hugo modules\n- `data/cli/` — read-only, generated from upstream YAML\n- `content/reference/cli/` — read-only, generated from `data/cli/`\n- Everything else in `content/` — editable\n\nIf the fix requires upstream changes, identify the upstream repo and note\nit as out of scope. See the vendored content table in CLAUDE.md.\n\n## 4. Find related content\n\nLook for pages that may need updating alongside the primary fix:\n\n- Pages that link to the affected content\n- Include files (`content/includes/`) referenced by the page\n- Related pages in the same section describing the same feature\n\n## 5. Verify facts\n\nIf the issue makes a factual claim about how a feature behaves, verify it.\nFollow external links, read upstream source, check release notes. Do not\nplan a fix based on an unverified claim.\n\nIf the fix requires a replacement URL and that URL cannot be verified (e.g.\nnetwork restrictions), report it as a blocker rather than guessing.\n\n## 6. Check the live site (if needed)\n\nFor URL or rendering issues, fetch the live page:\n\n```\nhttps://docs.docker.com/<path>/\n```\n\n## 7. Report findings\n\nSummarize what you found — files to change, the specific problem in each,\nwhat the fix should be, and any constraints. This context feeds directly\ninto the write step.\n\nBe specific: name the file, the section or element within it, and the\nverified content needed. \"Fix the broken link in networking.md\" is not\nspecific enough. \"In `compose/networking.md`, the 'Custom networks' section,\nremove the note about `driver_opts` being ignored — this was fixed in\nCompose 2.24\" is.\n\n## Notes\n\n- Research quality bounds write quality. Vague research produces broad\n  changes; precise research produces minimal ones.\n- Do not create standalone research files — findings stay in conversation\n  context for the write step.\n",".agents/skills/review-changes/SKILL.md":"---\nname: review-changes\ndescription: >\n  Review uncommitted or recently committed documentation changes for\n  correctness, coherence, and style compliance. Use before creating a PR\n  to catch issues. \"review my changes\", \"review the diff\", \"check the fix\n  before submitting\", \"does this look right\".\ncontext: fork\nmodel: opus\n---\n\n# Review Changes\n\nEvaluate whether the changes correctly and completely solve the stated\nproblem, without introducing new issues. Start with no assumptions — the\nchange may contain mistakes. Your job is to catch what the writer missed,\nnot to rubber-stamp the diff.\n\n## 1. Identify what changed\n\nDetermine the scope of changes to review:\n\n```bash\n# Uncommitted changes\ngit diff --name-only\n\n# Last commit\ngit diff --name-only HEAD~1\n\n# Entire branch vs main\ngit diff --name-only main...HEAD\n```\n\nPick the right comparison for what's being reviewed. If reviewing a branch,\nuse `main...HEAD` to see all changes since the branch diverged.\n\n## 2. Read each changed file in full\n\nDo not just read the diff. For every changed file, read the entire file to\nunderstand the full context the change lives in. A diff can look correct in\nisolation but contradict something earlier on the same page.\n\nThen read the diff for the detailed changes:\n\n```bash\n# Adjust the comparison to match step 1\ngit diff --unified=10              # uncommitted\ngit diff --unified=10 HEAD~1       # last commit\ngit diff --unified=10 main...HEAD  # branch\n```\n\n## 3. Follow cross-references\n\nFor each changed file, check what links to it and what it links to:\n\n- Search for other pages that reference the changed content (grep for the\n  filename, heading anchors, or key phrases)\n- Read linked pages to verify the change doesn't create contradictions\n  across pages\n- Check that anchor links in cross-references still match heading IDs\n\nA change that's correct on its own page can break the story told by a\nrelated page.\n\n## 4. Verify factual accuracy\n\nDon't assume the change is factually correct just because it reads well.\n\n- If the change describes how a feature behaves, verify against upstream\n  docs or source code\n- If the change includes a URL, check that it resolves\n- If the change references a CLI flag, option, or API field, confirm it\n  exists\n\n## 5. Evaluate as a reader\n\nConsider someone landing on this page from a search result, with no prior\ncontext:\n\n- Does the page make sense on its own?\n- Is the changed section clear without having read the issue or diff?\n- Would a reader be confused by anything the change introduces or leaves\n  out?\n\n## 6. Review code and template changes\n\nFor non-Markdown changes (JS, HTML, CSS, Hugo templates):\n\n- Trace through the common execution path\n- Trace through at least one edge case (no stored preference, Alpine fails\n  to load, first visit vs returning visitor)\n- Ask whether the change could produce unexpected browser or runtime\n  behavior that no automated tool would catch\n\n## 7. Decision\n\n**Approve** if the change is correct, coherent, complete, and factually\naccurate.\n\n**Request changes** if:\n- The change does not correctly solve the stated problem\n- There is a factual error or contradiction (on-page or cross-page)\n- A cross-reference is broken or misleading\n- A reader would be confused\n\nWhen requesting changes, be specific: quote the exact text that is wrong,\nexplain why, and suggest the correct fix.\n",".agents/skills/review-pr/SKILL.md":"---\nname: review-pr\ndescription: >\n  Review one or more incoming Docker documentation pull requests as a\n  maintainer. Independently validate technical claims, assess editorial fit\n  and information architecture, choose a verdict, and draft exact inline or\n  PR-wide feedback behind a confirmation gate. Use for requests such as\n  \"review PR 123\", \"is this PR correct?\", \"does this information belong\n  here?\", \"validate this PR\", or \"help review backlog PRs\". Do not use to\n  maintain or fix a PR you own; use maintain-pr for that.\n---\n\n# Review PR\n\nReview incoming contributions for factual correctness and whether they make\nthe documentation better as a whole. Treat a technically true addition as\ninsufficient when it is misplaced, overemphasized, redundant, or unhelpful\nto the page's intended reader.\n\n## Preserve the write boundary\n\nPerform the review in two phases:\n\n1. Research the PR, decide a verdict, and present the exact proposed\n   comment or review text.\n2. Wait for explicit user confirmation, then post only the confirmed text.\n\nBefore confirmation, do not post comments, submit a GitHub review, approve or\nrequest changes, resolve threads, push commits, edit labels, or otherwise\nmutate GitHub. A request to review or draft feedback is not confirmation to\npost it. Ask `Post these comments?` and stop. Treat revisions to a draft as\nunconfirmed until the user explicitly asks to post them.\n\n## 1. Gather the full context\n\nFor each PR, inspect its metadata, body, commits, changed files, checks,\nconversation, reviews, and linked issues. Always fetch inline comments\nseparately because `gh pr view --json reviews` omits them.\n\n```bash\ngh pr view <PR> --repo docker/docs \\\n  --json number,title,url,state,author,body,baseRefName,headRefName,headRefOid,commits,files,comments,reviews,reviewDecision,statusCheckRollup\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, side, commit_id}]'\ngh pr diff <PR> --repo docker/docs\n```\n\nRead linked issues and relevant discussion. An issue is evidence that a\nreader was confused, but it does not establish the reporter's diagnosis or\njustify a new highlighted note by itself. Green CI establishes only that automated\nchecks passed, not that the content is correct.\n\nFetch the PR head when local inspection is useful. Compare it with the\ncanonical upstream base rather than assuming the local branch is fresh.\nRead each changed file in full, not only its diff.\n\n## 2. Research independently\n\nVerify every material claim against authoritative sources such as product\nsource code, upstream documentation, specifications, release notes, or safe\nlocal reproduction. Do not accept the PR description, issue diagnosis, or\nexisting review feedback as fact.\n\nSearch the documentation for related explanations and canonical pages. Read\n`STYLE.md`, `COMPONENTS.md`, and applicable repository instructions. Check\nwhether a changed file is generated or maintained upstream and identify the correct\nupstream repository instead of proposing a local edit.\n\nDistinguish among:\n\n- a wrong fact\n- a correct fact expressed inaccurately\n- a correct fact placed on the wrong page\n- content already explained elsewhere\n- a real discovery problem better addressed with a short signpost and link\n- a request that needs no documentation change.\n\nIf an external claim or replacement URL cannot be verified, report that\nlimitation instead of guessing.\n\n## 3. Assess editorial fit\n\nApply these questions to each addition:\n\n- Does it change a reader's decision or next action on this page?\n- Is this the canonical page for the concept?\n- Is the fact general, or specific to this page, feature, or component?\n- Is the information already documented elsewhere?\n- Would a concise local signpost to canonical coverage solve the discovery\n  problem better than duplicating the explanation?\n- Is the visual and textual weight proportional to the information's value?\n- Does it preserve the page's scope, flow, and character?\n\nPrefer one coherent explanation in the canonical location. Add local context\nonly when it helps the reader complete the task at hand. Avoid stray notes,\ncallouts, and exhaustive edge cases whose prominence exceeds their value.\n\n## 4. Choose a decisive verdict\n\nLead with one of these outcomes:\n\n- **Approve**: correct, useful, well placed, and ready to merge.\n- **Approve with optional polish**: ready to merge; suggestions are genuinely\n  non-blocking.\n- **Focused rewrite**: the underlying need is valid, but wording, scope,\n  placement, or structure should change before merge.\n- **Close / no docs change**: incorrect, redundant, out of scope, or not a\n  documentation problem.\n\nExplain the verdict with evidence. When wording is the issue, provide exact\nreplacement text rather than a vague request to improve it.\n\n## 5. Place feedback deliberately\n\nUse an inline comment when the finding is anchored to a narrow changed line\nor range and acting on it is local. Examples include an inaccurate sentence,\nan ambiguous option description, a broken link, or a precise wording\nreplacement.\n\nUse a PR-wide comment for scope, information architecture, overall approach,\nmultiple intertwined edits, or a proposed replacement section. Do not attach\nholistic feedback to an arbitrary line.\n\nUse both when appropriate: put the overall direction in the PR-wide comment\nand line-specific corrections inline. Do not repeat the same point in both.\nConsolidate related feedback so the author receives the fewest comments that\nremain clear and actionable.\n\nFor every proposed inline comment, resolve and display the current changed\nfile path and right-side diff line. If the target line is not part of the\ncurrent diff or cannot be identified reliably, use a PR-wide comment that\nquotes the target text instead. Never guess a line number.\n\nEnd comments posted on the user's behalf with an accurate agent-disclosure\nfooter, such as `Generated by Codex`.\n\n## 6. Present drafts and stop\n\nBefore any GitHub write, show the review in this form, omitting empty\nsections:\n\n```markdown\n## Verdict\n\nFocused rewrite\n\n## Findings\n\n- <finding and evidence>\n\n## Proposed inline comments\n\n1. `path/to/file.md:42`\n   > Exact comment text\n\n## Proposed PR-wide comment\n\n> Exact comment text\n\nPost these comments?\n```\n\nFor multiple PRs, give each PR its own verdict and comment set. Make the\nconfirmation scope unambiguous. Do not interpret approval of one PR's drafts\nas approval to post comments on the others.\n\n## 7. Post only confirmed feedback\n\nImmediately before posting, re-fetch the PR head SHA and diff. If either the\nhead or an inline target changed, stop and show the updated draft or\nplacement for confirmation.\n\nPost confirmed inline comments as a single comment-only review when\npractical. Use the current head SHA and right-side diff lines:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/reviews --method POST --input <payload>\n```\n\nThe JSON payload contains `commit_id`, `event: \"COMMENT\"`, and a `comments`\narray whose entries contain `path`, `line`, `side: \"RIGHT\"`, and `body`.\nSubmitting a review with `APPROVE` or `REQUEST_CHANGES` requires separate,\nexplicit user authorization; a verdict alone does not grant it.\n\nPost confirmed holistic feedback separately:\n\n```bash\ngh pr comment <PR> --repo docker/docs --body-file <file>\n```\n\nUse a safely created temporary file or API input so Markdown, backticks, and\nshell substitutions are preserved literally. Post exactly the confirmed\ntext. Verify the resulting review/comments and report their URLs and\nplacements. If GitHub rejects an inline location, do not silently fall back\nto a PR-wide comment; report the failure and prepare a revised placement for\nconfirmation.\n\n## Definition of done\n\n- Verify technical claims with authoritative evidence.\n- Evaluate usefulness, placement, duplication, and proportionality.\n- Give a decisive verdict and exact actionable wording.\n- Choose inline and PR-wide placement based on the feedback's scope.\n- Show every exact draft and target before any GitHub mutation.\n- Post only after explicit confirmation and verify what was posted.\n",".agents/skills/review-pr/agents/openai.yaml":"interface:\n  display_name: \"Review PR\"\n  short_description: \"Validate incoming documentation pull requests\"\n  default_prompt: \"Use $review-pr to validate this incoming documentation PR and draft maintainer feedback.\"\n",".agents/skills/testcontainers-guides-migrator/SKILL.md":"---\nname: testcontainers-guide-migrator\ndescription: >\n  Migrate a Testcontainers guide from testcontainers.com into the Docker docs site (docs.docker.com).\n  Converts AsciiDoc to Hugo Markdown, updates code to the latest Testcontainers API, splits into\n  chapters with stepper navigation, verifies code compiles and tests pass, and validates against\n  Docker docs style rules. Use when asked to migrate a testcontainers guide, add a TC guide, or\n  port content from testcontainers.com to Docker docs.\n---\n\n# Migrate a Testcontainers Guide\n\nYou are migrating guides from https://testcontainers.com/guides/ into the Docker docs Hugo site.\nEach guide lives in its own GitHub repo under `testcontainers/tc-guide-*`, written in AsciiDoc.\nThe source repos are listed in the testcontainers-site build.sh:\nhttps://github.com/testcontainers/testcontainers-site/blob/main/build.sh#L23-L45\n\n## Inputs\n\nThe user provides one or more guides to migrate. Resolve these from the inventory below:\n\n- **REPO_NAME**: GitHub repo (e.g. `tc-guide-getting-started-with-testcontainers-for-java`)\n- **SLUG**: guide slug inside `guide/` dir (e.g. `getting-started-with-testcontainers-for-java`)\n- **LANG**: language identifier (go, java, dotnet, nodejs, python)\n- **GUIDE_ID**: short kebab-case name (e.g. `getting-started`)\n\n## Guide inventory\n\nThese are the 21 guides from testcontainers.com/guides/ and their source repos:\n\n| # | Title | Repo | Lang | GUIDE_ID |\n|---|-------|------|------|----------|\n| 1 | Introduction to Testcontainers | tc-guide-introducing-testcontainers | (none) | introducing |\n| 2 | Getting started for Java | tc-guide-getting-started-with-testcontainers-for-java | java | getting-started |\n| 3 | Testing Spring Boot REST API | tc-guide-testing-spring-boot-rest-api | java | spring-boot-rest-api |\n| 4 | Testcontainers lifecycle (JUnit 5) | tc-guide-testcontainers-lifecycle | java | lifecycle |\n| 5 | Configuration of services in container | tc-guide-configuration-of-services-running-in-container | java | service-configuration |\n| 6 | Replace H2 with real database | tc-guide-replace-h2-with-real-database-for-testing | java | replace-h2 |\n| 7 | Testing ASP.NET Core web app | tc-guide-testing-aspnet-core | dotnet | aspnet-core |\n| 8 | Testing Spring Boot Kafka Listener | tc-guide-testing-spring-boot-kafka-listener | java | spring-boot-kafka |\n| 9 | REST API integrations with MockServer | tc-guide-testing-rest-api-integrations-using-mockserver | java | mockserver |\n| 10 | Getting started for .NET | tc-guide-getting-started-with-testcontainers-for-dotnet | dotnet | getting-started |\n| 11 | AWS integrations with LocalStack | tc-guide-testing-aws-service-integrations-using-localstack | java | aws-localstack |\n| 12 | Testcontainers in Quarkus apps | tc-guide-testcontainers-in-quarkus-applications | java | quarkus |\n| 13 | Getting started for Go | tc-guide-getting-started-with-testcontainers-for-go | go | getting-started |\n| 14 | jOOQ and Flyway with Testcontainers | tc-guide-working-with-jooq-flyway-using-testcontainers | java | jooq-flyway |\n| 15 | Getting started for Node.js | tc-guide-getting-started-with-testcontainers-for-nodejs | nodejs | getting-started |\n| 16 | REST API integrations with WireMock | tc-guide-testing-rest-api-integrations-using-wiremock | java | wiremock |\n| 17 | Local dev with Testcontainers Desktop | tc-guide-simple-local-development-with-testcontainers-desktop | java | local-dev-desktop |\n| 18 | Micronaut REST API with WireMock | tc-guide-testing-rest-api-integrations-in-micronaut-apps-using-wiremock | java | micronaut-wiremock |\n| 19 | Micronaut Kafka Listener | tc-guide-testing-micronaut-kafka-listener | java | micronaut-kafka |\n| 20 | Getting started for Python | tc-guide-getting-started-with-testcontainers-for-python | python | getting-started |\n| 21 | Keycloak with Spring Boot | tc-guide-securing-spring-boot-microservice-using-keycloak-and-testcontainers | java | keycloak-spring-boot |\n\nAlready migrated: **#2 (Java getting-started)**, **#13 (Go getting-started)**, **#20 (Python getting-started)**\n\n## Step 0: Pre-flight\n\n1. Confirm `testing-with-docker` tag exists in `data/tags.yaml`. If not, add:\n   ```yaml\n   testing-with-docker:\n     title: Testing with Docker\n   ```\n2. Check if new terms need adding to `_vale/config/vocabularies/Docker/accept.txt`.\n3. Read `STYLE.md` and `COMPONENTS.md` to refresh on Docker docs conventions.\n\n## Step 1: Clone the guide repo\n\nClone the guide repo to a temporary directory. This gives you all source files locally — no HTTP calls needed.\n\n```bash\ngit clone --depth 1 https://github.com/testcontainers/{REPO_NAME}.git <tmpdir>/{REPO_NAME}\n```\n\nWhere `<tmpdir>` is a temporary directory on your system (e.g. the output of `mktemp -d`).\n\nThe repo structure is:\n- `<tmpdir>/{REPO_NAME}/guide/{SLUG}/index.adoc` — the AsciiDoc guide source\n- `<tmpdir>/{REPO_NAME}/src/` — application source code (referenced by `include::` directives)\n- `<tmpdir>/{REPO_NAME}/testdata/` — test data files (SQL scripts, configs, etc.)\n- `<tmpdir>/{REPO_NAME}/pom.xml` or `go.mod` — build config\n\n1. Read `guide/{SLUG}/index.adoc` to get the guide content.\n2. Find all `include::{codebase}/path/to/file[]` directives. The `{codebase}` attribute points to a remote URL, but since you have the repo cloned, read the files directly from disk instead (e.g. `include::{codebase}/src/main/java/Foo.java[]` → read `<tmpdir>/{REPO_NAME}/src/main/java/Foo.java`).\n3. If includes have `[lines=\"X..Y\"]`, extract only those lines from the local file.\n4. Note the `[source,lang]` block preceding each include — that determines the code fence language.\n\nThis cloned repo also serves as the base for Step 6 (code verification) — you can run the tests directly in it to confirm they pass before updating the code to the latest API.\n\n## Step 2: Convert AsciiDoc to Markdown\n\n| AsciiDoc | Markdown |\n|---|---|\n| `== Heading` | `## Heading` |\n| `=== Heading` | `### Heading` |\n| `*bold*` (AsciiDoc bold) | `**bold**` |\n| `https://url[Link text]` | `[Link text](url)` |\n| `[source,lang]\\n----\\ncode\\n----` | `` ```lang\\ncode\\n``` `` |\n| `[source,shell]` with `$` prompts | `` ```console `` |\n| `[NOTE]\\ntext` or `====\\n[NOTE]\\n...\\n====` | `> [!NOTE]\\n> text` |\n| `[TIP]\\ntext` | `> [!TIP]\\n> text` |\n| `:toc:`, `:toclevels:`, `:codebase:` | Remove entirely |\n| `include::{codebase}/path[]` | Replace with fetched code in a code fence |\n| YAML front matter (date, draft, repo) | Remove; transform to Docker docs format |\n\n## Step 3: Apply Docker docs style rules\n\nThese are mandatory (from STYLE.md and AGENTS.md):\n\n- **No \"we\"**: \"We are going to create\" → \"Create\" or \"Start by creating\"\n- **No \"let us\" / \"let's\"**: → imperative voice or \"You can...\"\n- **No hedge words**: remove \"simply\", \"easily\", \"just\", \"seamlessly\"\n- **No meta-commentary**: remove \"it's worth noting\", \"it's important to understand\"\n- **No \"allows you to\" / \"enables you to\"**: → \"lets you\" or rephrase\n- **No \"click\"**: → \"select\"\n- **No bold for emphasis or product names**: only bold UI elements\n- **No time-relative language**: remove \"currently\", \"new\", \"recently\", \"now\"\n- **No exclamations**: remove \"Voila!!!\" etc.\n- Use `console` language hint for interactive shell blocks with `$` prompts\n- Use contractions: \"it's\", \"you're\", \"don't\"\n\n## Step 4: Update code to latest Testcontainers API\n\nResearch the latest API version for the target language before writing code.\n\n**Best practices reference**: The Testcontainers team maintains Claude skills with up-to-date API patterns and best practices for each language at https://github.com/testcontainers/claude-skills/ — check the relevant language skill (testcontainers-go, testcontainers-node, testcontainers-dotnet) for current API signatures, cleanup patterns, wait strategies, and anti-patterns to avoid.\n\nFor each language, check the cloned repo's existing code, then update to the latest API. Key patterns per language:\n\n**Go** (testcontainers-go v0.41.0):\n- `postgres.RunContainer(ctx, opts...)` → `postgres.Run(ctx, \"image\", opts...)`\n- `testcontainers.WithImage(...)` → image is now the 2nd positional param to `Run()`\n- Manual `WithWaitStrategy(wait.ForLog(...))` → `postgres.BasicWaitStrategies()`\n- `t.Cleanup(func() { ctr.Terminate(ctx) })` → `testcontainers.CleanupContainer(t, ctr)`\n- `if err != nil { log.Fatal(err) }` → `require.NoError(t, err)` (use testify require/assert)\n- Helper functions should accept `t *testing.T` as first param, call `t.Helper()`\n- No `TearDownSuite()` needed if `CleanupContainer` is registered in the helper\n- Go version prerequisite: 1.25+\n\n**Java** (testcontainers-java 2.0.4):\n- Artifacts renamed in 2.x: `org.testcontainers:postgresql` → `org.testcontainers:testcontainers-postgresql`\n- Check the latest version at https://java.testcontainers.org/\n- Use `@Testcontainers` and `@Container` annotations for JUnit 5 lifecycle\n- Prefer module-specific containers (e.g. `PostgreSQLContainer`) over `GenericContainer`\n- Use `@DynamicPropertySource` for Spring Boot integration\n\n**.NET** (testcontainers-dotnet):\n- Check the latest NuGet package version\n- Use `IAsyncLifetime` for container lifecycle in xUnit\n- Use builder pattern: `new PostgreSqlBuilder().Build()`\n\n**Node.js** (testcontainers-node):\n- Check the latest npm version\n- Use module-specific packages (e.g. `@testcontainers/postgresql`)\n- Use `GenericContainer` for services without a dedicated module\n\n**Python** (testcontainers-python):\n- Check the latest PyPI version\n- Use context managers (`with PostgresContainer() as postgres:`)\n- Use module-specific containers when available\n\nFor all languages: consult the corresponding Testcontainers skill at https://github.com/testcontainers/claude-skills/ for current best practices and anti-patterns.\n\n## Step 5: Create guide directory structure\n\nDirectory: `content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n\nEach guide is its own top-level entry under `/guides/`. Do NOT nest guides inside a shared parent section — otherwise they won't appear individually in the tag/language filters on the guides listing page.\n\n### _index.md (landing page)\n\n```yaml\n---\ntitle: {Full guide title}\nlinkTitle: {Short title for guides listing}\ndescription: {One-line description}\nkeywords: testcontainers, {lang}, testing, {technologies used}\nsummary: |\n  {2-3 line summary for the guides listing card}\ntoc_min: 1\ntoc_max: 2\ntags: [testing-with-docker]\nlanguages: [{lang}]\nparams:\n  time: {estimated} minutes\n---\n\n<!-- Source: https://github.com/testcontainers/{REPO_NAME} -->\n```\n\nContent: what you'll learn (bulleted list), prerequisites, and a NOTE linking to `https://testcontainers.com/getting-started/` for newcomers.\n\n### Sub-pages (chapters)\n\nSplit the guide into logical chapters. Each sub-page:\n\n```yaml\n---\ntitle: {Chapter title}\nlinkTitle: {Short title for stepper}\ndescription: {One-line description}\nweight: {10, 20, 30, ...}\n---\n```\n\n**No `tags`, `languages`, or `params` on sub-pages** — only on `_index.md`.\n\nTypical chapter breakdown:\n| Weight | File | Content |\n|--------|------|---------|\n| 10 | `create-project.md` | Project setup, dependencies, business logic |\n| 20 | `write-tests.md` | First test using testcontainers |\n| 30 | `test-suites.md` | Reusing containers, test helpers, suites |\n| 40 | `run-tests.md` | Running tests, summary, further reading |\n\nAdapt the split to the guide's content — some guides may need fewer or more chapters.\n\n## Step 6: Verify code compiles and tests pass\n\nThis is CRITICAL. The code in the guide MUST compile and all tests MUST pass. Do not skip this step.\n\n### 6a: Use the cloned repo as the verification project\n\nThe repo you cloned in Step 1 (`<tmpdir>/{REPO_NAME}`) already contains a working project with all source files, build config, and tests. Use it as the starting point:\n\n```bash\ncd <tmpdir>/{REPO_NAME}\n```\n\nFirst, verify the **original** code compiles and tests pass before you change anything. This confirms a good baseline.\n\n### 6b: Update the code in the cloned repo\n\nAfter confirming the original works, apply the API updates (from Step 4) directly in the cloned repo's source files. This is the same code you're putting in the guide — keep them in sync.\n\n### 6c: Update dependencies and compile\n\nRun compilation inside a container for reproducibility — no need to install the language toolchain on the host. Use the appropriate language Docker image, mounting the cloned repo:\n\n```bash\ndocker run --rm -v \"<tmpdir>/{REPO_NAME}\":/app -w /app <language-image> sh -c \"<compile command>\"\n```\n\nPick the right image for the language (e.g. `golang:1.25-alpine`, `maven:3-eclipse-temurin-21`, `gradle:jdk21`, `mcr.microsoft.com/dotnet/sdk:9.0`, `node:22-alpine`, `python:3.13-alpine`). Update dependencies to the latest Testcontainers version and compile.\n\nIf compilation fails, fix the code and update the guide markdown to match.\n\n### 6d: Run tests in a container with Docker socket mounted\n\nRun tests in the same kind of container, but **mount the Docker socket** so Testcontainers can create sibling containers.\n\n#### macOS Docker Desktop workarounds\n\nWhen running on macOS with Docker Desktop, these environment variables and flags are **required**:\n\n- **`TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`** — On macOS, containers can't reach sibling containers via the Docker bridge IP (`172.17.0.x`). This tells Testcontainers (including Ryuk) to connect via `host.docker.internal` instead. **Do NOT disable Ryuk** — it is a core Testcontainers feature and the guides must demonstrate proper usage.\n- **`docker-java.properties`** with `api.version=1.47` — Docker Desktop's minimum API version is 1.44, but docker-java defaults to 1.24. Create this file in the project root and mount it to `/root/.docker-java.properties` inside Java containers.\n- **`-Dspotless.check.skip=true`** — The Spotless Maven plugin in the source repos is incompatible with JDK 21. Skip it since it's a code formatter, not part of the test.\n- **`-Dmicronaut.test.resources.enabled=false`** — Micronaut's Test Resources service starts a separate process that can't connect to Docker from inside a container. The guide tests use Testcontainers directly, not Test Resources. Only needed for Micronaut guides.\n#### Java guide test command\n\n```bash\n# Create docker-java.properties in the project root\necho \"api.version=1.47\" > <tmpdir>/{REPO_NAME}/docker-java.properties\n\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v \"<tmpdir>/{REPO_NAME}/docker-java.properties\":/root/.docker-java.properties \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  maven:3.9-eclipse-temurin-21 \\\n  mvn -B test -Dspotless.check.skip=true -Dspotless.apply.skip=true\n```\n\nFor Quarkus guides, use `maven:3.9-eclipse-temurin-17` instead (Quarkus 3.22.3 compiles for Java 17).\n\n#### Go guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  golang:1.25-alpine \\\n  sh -c \"apk add --no-cache gcc musl-dev && go test -v -count=1 ./...\"\n```\n\n#### Python guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  python:3.13-slim \\\n  sh -c \"pip install -r requirements.txt && python -m pytest\"\n```\n\n#### .NET guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  mcr.microsoft.com/dotnet/sdk:9.0 \\\n  dotnet test\n```\n\n#### Node.js guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  node:22-alpine \\\n  sh -c \"npm install && npm test\"\n```\n\n#### Important: run tests sequentially\n\nRun guide tests **one at a time**. Running multiple concurrent DinD or sibling-container tests can overwhelm Docker Desktop's containerd store and cause `meta.db: input/output error` corruption, requiring a Docker Desktop restart.\n\n### 6e: Fix until green\n\nIf any test fails, debug and fix the code in both the temporary project AND the guide markdown. Re-run until all tests pass. Do not proceed until verified.\n\n## Step 7: Update cross-references\n\n1. **`content/manuals/testcontainers.md`**: Add a bullet under the `## Guides` section:\n   ```markdown\n   - [Guide title](/guides/testcontainers-{LANG}-{GUIDE_ID}/)\n   ```\n2. **Do NOT update** `content/guides/testcontainers-cloud/_index.md` — keep its external links.\n3. Link to `https://testcontainers.com/getting-started/` for the Testcontainers overview.\n4. Use internal paths for already-migrated guides; keep `testcontainers.com` links for unmigrated ones.\n\n## Step 8: Validate\n\n**IMPORTANT**: Run ALL validation locally before committing. Vale checks run on CI and will block the PR if they fail — fixing after push wastes CI cycles and review time.\n\n1. `npx --no-install rumdl fmt content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n2. `npx --no-install rumdl fmt content/manuals/testcontainers.md`\n3. `docker buildx bake lint` — must pass with no errors\n4. `docker buildx bake vale` — then check for errors in the new files:\n   ```bash\n   grep -A2 \"testcontainers-{LANG}-{GUIDE_ID}\" tmp/vale.out\n   ```\n   Fix ALL errors before proceeding. Common issues:\n   - **Vale.Spelling**: tech terms (library names, tools) not in the dictionary → add to `_vale/config/vocabularies/Docker/accept.txt` (alphabetical order)\n   - **Vale.Terms**: wrong casing (e.g. \"python\" → \"Python\") → fix in the markdown. Watch for package names like `testcontainers-python` triggering false positives — rephrase to \"Testcontainers for Python\" in prose.\n   - **Docker.Avoid**: hedge words like \"very\", \"simply\" → reword\n   - **Docker.We**: first-person plural → rewrite to \"you\" or imperative\n   - Info-level suggestions (e.g. \"VS Code\" → \"versus\") are not blocking but review them\n\n   Re-run `docker buildx bake vale` after fixes until no errors remain in the new files.\n5. Verify in local dev server (`HUGO_PORT=1314 docker compose watch`):\n   - Guide appears when filtering by its language\n   - Guide appears when filtering by `Testing with Docker` tag\n   - Stepper navigation works across chapters\n   - All links resolve (no 404s)\n6. Verify all external URLs return 200:\n   ```bash\n   curl -s -o /dev/null -w \"%{http_code}\" -L \"{url}\"\n   ```\n\n## Step 9: Commit\n\nOne commit per guide. Message format:\n```\nfeat(guides): add testcontainers {lang} {guide-id} guide\n\nMigrated from https://github.com/testcontainers/{REPO_NAME}\nUpdated to testcontainers-{lang} v{version} API.\n```\n\n## Special cases\n\n- **introducing-testcontainers**: Language-agnostic, conceptual. May overlap with `content/manuals/testcontainers.md`. Review for deduplication before migrating.\n- **local-dev-testcontainers-desktop**: About Testcontainers Desktop (now part of Docker Desktop). May need significant rewriting rather than mechanical migration.\n- **Java guides**: Many share the same language. Each still gets its own `testcontainers-java-{GUIDE_ID}` directory.\n\n## Reference: completed migration (Go getting-started)\n\nUse `content/guides/testcontainers-go-getting-started/` as the reference implementation:\n- `_index.md` — landing page with frontmatter, prerequisites, learning objectives\n- `create-project.md` (weight: 10) — project setup and business logic\n- `write-tests.md` (weight: 20) — first test with testcontainers-go\n- `test-suites.md` (weight: 30) — container reuse with testify suites\n- `run-tests.md` (weight: 40) — running tests, summary, further reading\n",".agents/skills/triage-issue/SKILL.md":"---\nname: triage-issue\ndescription: >\n  Analyze a single GitHub issue for docker/docs — check whether the problem\n  still exists, determine a verdict, and report findings. Use when asked to\n  triage, assess, or review an issue, even if the user doesn't say \"triage\"\n  explicitly: \"triage issue 1234\", \"is issue 500 still valid\", \"should we\n  close #200\", \"look at this issue\", \"what's going on with #200\".\nargument-hint: \"<issue-number>\"\ncontext: fork\n---\n\n# Triage Issue\n\nGiven GitHub issue **$ARGUMENTS** from docker/docs, figure out whether\nit's still a real problem and say what should happen next.\n\n## 1. Fetch the issue\n\n```bash\ngh issue view $ARGUMENTS --repo docker/docs \\\n  --json number,title,body,state,labels,createdAt,updatedAt,closedAt,assignees,author,comments\n```\n\n## 2. Understand the problem\n\nRead the issue body and all comments. Identify:\n\n- What is the reported problem?\n- What content, URL, or file does it reference?\n- Has anyone already proposed a fix or workaround in the comments?\n\nCheck for linked PRs in the issue timeline, not only in the issue body or\ncomments:\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/timeline --paginate \\\n  --jq '.[] | select(.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") | {event, created_at, source: .source.issue.html_url, title: .source.issue.title, state: .source.issue.state}'\n```\n\nIf an open PR already addresses the issue, don't open another PR. Review the\nexisting PR instead, and report that the issue already has an associated PR. A\nmerged PR is strong evidence the issue is fixed. A closed-without-merge PR means\nthe issue is likely still open.\n\n## 3. Follow URLs\n\nFind all `docs.docker.com` URLs in the issue body and comments. For each:\n\n- Fetch the URL to check if it still exists (404 = content removed or moved)\n- Check whether the content still contains the problem described\n- Note when the page was last updated relative to when the issue was filed\n\nFor non-docs URLs (GitHub links, external references), fetch them too if\nthey are central to understanding the issue.\n\n## 4. Check the repository\n\nIf the issue references specific files, content sections, or code:\n\n- Find and read the current version of that content\n- Check whether the problem has been fixed, content moved, or file removed\n- Remember the `/manuals` prefix mapping when looking up files\n\n## 5. Check for upstream ownership\n\nIf the issue is about content in `_vendor/` or `data/cli/`, it cannot be\nfixed here. Identify which upstream repo owns it (see the vendored content\ntable in CLAUDE.md).\n\n## 6. Decide and act\n\nAfter investigating, pick one of these verdicts and take the corresponding\naction on the issue:\n\n- **Close it** — the problem is already fixed, the content no longer exists,\n  or the issue is too outdated to be useful. Close the issue with a comment\n  explaining why:\n\n  ```bash\n  gh issue close $ARGUMENTS --repo docker/docs \\\n    --comment \"Closing: <one-sentence reason>\"\n  ```\n\n- **Fix it** — the problem is real and fixable in this repo. Name the\n  file(s) and what needs to change. Label the issue `status/confirmed` and\n  remove `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/confirmed'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Escalate upstream** — the problem is real but lives in vendored content.\n  Name the upstream repo. Label the issue `status/upstream` and remove\n  `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/upstream'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Leave it open** — you can't determine the current state, or the issue\n  needs human judgment. Label the issue `status/needs-analysis`:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/needs-analysis'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\nDon't overthink the classification. An old issue isn't stale if the problem\nstill exists. An upstream issue is still valid — it's just not fixable here.\n\nAlso apply the most relevant `area/` label based on the content affected.\nAvailable area labels: `area/accounts`, `area/admin`, `area/ai`,\n`area/api`, `area/billing`, `area/build`, `area/build-cloud`, `area/cli`,\n`area/compose`, `area/compose-spec`, `area/config`, `area/contrib`,\n`area/copilot`, `area/desktop`, `area/dhi`, `area/engine`,\n`area/enterprise`, `area/extensions`, `area/get-started`, `area/guides`,\n`area/hub`, `area/install`, `area/networking`, `area/offload`,\n`area/release-notes`, `area/samples`, `area/scout`, `area/security`,\n`area/storage`, `area/subscription`, `area/swarm`, `area/ux`. Pick one\n(or at most two if the issue clearly spans areas). Skip if none fit.\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n  --method POST --field 'labels[]=area/<name>'\n```\n\n## 7. Report\n\nWrite a short summary: what the issue reports, what you found, and what\nshould happen next. Reference the specific files, URLs, or PRs that support\nyour conclusion. Skip metadata fields — the issue itself has the dates and\nlabels. Mention the action you took (closed, labeled, etc.).\n\n## Notes\n\n- Always check timeline cross-references before deciding to fix an issue\n- Do not narrate your process — produce the final report\n- End every issue comment with an accurate agent-disclosure footer that names\n  the active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n.\n",".agents/skills/write/SKILL.md":"---\nname: write\ndescription: >\n  Write a documentation fix on a branch. Makes the minimal change, formats,\n  self-reviews, and commits. Use after research has identified what to change.\n  \"write the fix\", \"make the changes\", \"implement the fix for #1234\".\nhooks:\n  PostToolUse:\n    - matcher: \"Edit|Write\"\n      hooks:\n        - type: command\n          command: \"bash ${CLAUDE_SKILL_DIR}/scripts/post-edit.sh\"\n---\n\n# Write\n\nMake the minimal change that resolves the issue. Research has already\nidentified what to change — this skill handles the edit, formatting,\nself-review, and commit.\n\n## 1. Create a branch\n\n```bash\ngit checkout -b fix/issue-<number>-<short-desc> main\n```\n\nUse a short kebab-case description derived from the issue title (3-5 words).\n\n## 2. Read then edit\n\nAlways read each file before modifying it. Make the minimal change that\nfixes the issue. Do not improve surrounding content, add comments, or\naddress adjacent problems.\n\nFollow the writing guidelines in CLAUDE.md, STYLE.md, and COMPONENTS.md.\n\n## 3. Front matter check\n\nEvery content page requires `title`, `description`, and `keywords` in its\nfront matter. If any are missing from a file you touch, add them.\n\n## 4. Validate\n\nrumdl runs automatically after each edit via the PostToolUse hook.\nRun lint manually after all edits are complete:\n\n```bash\nscripts/lint.sh <changed-files>\n```\n\nThe lint script runs rumdl and Vale on only the files you pass it,\nso the output is scoped to your changes. Fix any errors it reports.\n\n## 5. Self-review\n\nRe-read each changed file: right file, right lines, change is complete,\nfront matter is present. Run `git diff` and verify only intended changes\nare present.\n\n## 6. Commit\n\nStage only the changed files:\n\n```bash\ngit add <files>\ngit diff --cached --name-only  # verify — no package-lock.json or other noise\ngit commit -m \"$(cat <<'EOF'\ndocs: <short description under 72 chars> (fixes #NNNN)\n\n<What was wrong: one sentence citing the specific problem.>\n<What was changed: one sentence describing the exact edit.>\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\"\n```\n\nThe commit body is mandatory. A reviewer reading only the commit should\nunderstand the problem and the fix without opening the issue.\n\n## Notes\n\n- Never edit `_vendor/` or `data/cli/` — these are vendored\n- If a file doesn't exist, check for renames:\n  `git log --all --full-history -- \"**/filename.md\"`\n- If the fix requires a URL that cannot be verified, stop and report a\n  blocker rather than guessing\n","AGENTS.md":"# AGENTS.md\n\nInstructions for AI agents working on Docker documentation.\nThis site builds https://docs.docker.com/ using Hugo.\n\n## Project structure\n\n```text\ncontent/          # Documentation source (Markdown + Hugo front matter)\n├── manuals/      # Product docs (Engine, Desktop, Hub, etc.)\n├── guides/       # Task-oriented guides\n├── reference/    # API and CLI reference\n└── includes/     # Reusable snippets\nlayouts/          # Hugo templates and shortcodes\ndata/             # YAML data files (CLI reference, etc.)\nassets/           # CSS (Tailwind v4) and JS (Alpine.js)\nstatic/           # Images, fonts\n_vendor/          # Vendored Hugo modules (read-only)\n```\n\n## URL prefix stripping\n\nThe `/manuals` prefix is stripped from published URLs:\n`content/manuals/desktop/install.md` becomes `/desktop/install/` on the live\nsite.\n\nWhen writing internal cross-references in source files, keep the `/manuals/`\nprefix in the path — Hugo requires the full source path. The stripping only\naffects the published URL, not the internal link target. Anchor links must\nexactly match the generated heading ID (Hugo lowercases and slugifies\nheadings).\n\n## Vendored content (do not edit)\n\nContent in `_vendor/` and CLI reference data in `data/cli/` are vendored\nfrom upstream repos. Content pages under `content/reference/cli/` are\ngenerated from `data/cli/` YAML. Do not edit any of these files — changes\nmust go to the source repository:\n\n| Content | Source repo |\n|---------|-------------|\n| CLI reference (`docker`, `docker build`, etc.) | docker/cli |\n| Buildx reference | docker/buildx |\n| Compose reference | docker/compose |\n| Model Runner reference | docker/model-runner |\n| Dockerfile reference | moby/buildkit |\n| Engine API reference | moby/moby |\n| AI Governance API (`content/reference/api/ai-governance/api.yaml`) | docker/governor-services (private) |\n\nIf a validation failure or broken link traces back to vendored content, note\nthe upstream repo that needs fixing. Do not attempt to fix it locally.\n\n`content/reference/api/ai-governance/api.yaml` is a verbatim copy of the\nupstream `openapi.yaml` — do not edit it by hand. Re-vendor it with\n`hack/sync-governance-api.sh`, which fetches the latest spec from the private\n`docker/governor-services` repo (using your own `gh` auth).\n\n## Writing guidelines\n\nRead and follow [STYLE.md](STYLE.md) and [COMPONENTS.md](COMPONENTS.md).\nThese contain all style rules, shortcode syntax, and front matter requirements.\n\n### Style violations to avoid\n\nEvery piece of writing must avoid these words and patterns (enforced by Vale):\n\n- Hedge words: \"simply\", \"easily\", \"just\", \"seamlessly\"\n- Meta-commentary: \"it's worth noting\", \"it's important to understand\"\n- \"allows you to\" or \"enables you to\" — use \"lets you\" or rephrase\n- \"we\" — use \"you\" or \"Docker\"\n- \"click\" — use \"select\"\n- Bold for emphasis or product names — only bold UI elements\n- Time-relative language: \"currently\", \"new\", \"recently\", \"now\"\n\n### Version-introduction notes\n\nExplicit version anchors (\"Starting with Docker Desktop version X...\") are\ndifferent from time-relative language — they mark when a feature was\nintroduced, which is permanently true.\n\n- Recent releases (~6 months): leave version callouts in place\n- Old releases: consider removing if the callout adds little value\n- When in doubt, keep the callout and flag for maintainer review\n\n### Vale gotchas\n\n- Use lowercase \"config\" in prose — `vale.Terms` flags a capital-C \"Config\"\n\n### Updating the vocabulary\n\nIf Vale flags a legitimate tech term, product name, or compound identifier\nas a misspelling, add it to `_vale/config/vocabularies/Docker/accept.txt`.\nThis is optional — only update when a real new term is missing, not to\nsilence individual violations.\n\n- Use the canonical form for case-sensitive product names (`PyTorch`,\n  `GitHub`, `Kubernetes`, `BuildKit`). `Vale.Terms` enforces that exact\n  case across the docs.\n- Use `[Aa]bcd` character-class regex for words that legitimately appear\n  in multiple cases (e.g., sentence-starting capitalization, or a name\n  that's also a generic noun). This covers spelling without enforcing\n  a single canonical form.\n- Avoid broad regex patterns — entries that match many words at once\n  (especially with `(?i)`) suppress other rule checks on every match.\n- Don't add a wrong-cased entry to silence one false positive — it\n  cascades into `Vale.Terms` violations on every correct usage.\n\n## Alpine.js patterns\n\nDo not combine Alpine's `x-show` with the HTML `hidden` attribute on the\nsame element. `x-show` toggles inline `display` styles, but `hidden` applies\n`display: none` via the user-agent stylesheet — the element stays hidden\nregardless of `x-show` state. Use `x-cloak` for pre-Alpine hiding instead.\nThe site defines `[x-cloak=\"\"] { display: none !important }` in `global.css`.\n\n## Front matter requirements\n\nEvery content page under `content/` requires:\n\n- `title:` — page title\n- `description:` — short description for SEO/previews\n- `keywords:` — list of search keywords\n\nAdditional common fields:\n\n- `linkTitle:` — sidebar label (keep under 30 chars)\n- `weight:` — ordering within a section\n\n## Hugo shortcodes\n\nShortcodes are defined in `layouts/shortcodes/`. Syntax reference is in\nCOMPONENTS.md. Wrong shortcode syntax fails silently during build but\nproduces broken HTML — always check COMPONENTS.md for correct syntax.\n\n## Commands\n\n```sh\nnpx --no-install rumdl fmt <file>  # Format Markdown before committing\nnpx prettier --write <file>        # Format non-Markdown files\nscripts/lint.sh <file>...          # Lint specific files (rumdl + Vale)\ndocker buildx bake validate        # Run all validation checks\ndocker buildx bake lint            # Markdown linting only\ndocker buildx bake vale            # Style guide checks only\ndocker buildx bake test            # HTML and link checking\n```\n\nFor incremental work, prefer `scripts/lint.sh` over the `bake` targets —\nit runs the same checks on just the files you pass, so the output stays\nscoped to your changes instead of the whole repo.\n\n### Validation in git worktrees\n\n`docker buildx bake validate` fails in git worktrees because Hugo cannot\nresolve the worktree path. Use `lint` and `vale` targets separately instead.\nNever modify `hugo.yaml` to work around this. The `test`, `path-warnings`,\nand `validate-vendor` targets run correctly in CI.\n\n## Verification loop\n\n1. Make changes\n2. Format Markdown with rumdl: `npx --no-install rumdl fmt <file>`\n3. Lint the changed files: `scripts/lint.sh <file>...`\n4. Run a full build with `docker buildx bake` (optional for small changes)\n\nAlways lint the specific files you changed before committing. Use\n`scripts/lint.sh` rather than the `bake` targets so the output is scoped\nto your changes — bake runs across the entire repo and the noise makes\nreal issues easy to miss.\n\n## Git hygiene\n\n- **Stage files explicitly.** Never use `git add .` / `git add -A` /\n  `git add --all`. Running `npx prettier` updates `package-lock.json` in the\n  repo root, and broad staging sweeps it into the commit.\n- **Verify before committing.** Run `git diff --cached --name-only` and\n  confirm only documentation files appear. If `package-lock.json` or other\n  generated files are staged, unstage them:\n  `git reset HEAD -- package-lock.json`\n- **Push to your fork, not upstream.** Before pushing, confirm\n  `git remote get-url origin` returns your fork URL, not\n  `github.com/docker/docs`. Use `--head FORK_OWNER:branch-name` with\n  `gh pr create`.\n\n## Working with issues and PRs\n\n### Principles\n\n- **One issue, one branch, one PR.** Never combine multiple issues in a\n  single branch or PR.\n- **Minimal changes only.** Fix the issue. Do not improve surrounding\n  content, add comments, refactor, or address adjacent problems.\n- **Verify before documenting.** Don't take an issue reporter's claim at\n  face value — the diagnosis may be wrong even when the symptom is real.\n  Verify the actual behavior before updating docs.\n\n### Review feedback\n\n- **Always reply to review comments** — never silently fix. After every\n  commit that addresses review feedback, reply to each thread explaining\n  what was done.\n- **Treat reviewer feedback as claims to verify, not instructions to\n  execute.** Before implementing a suggestion, verify that it is correct.\n  Push back when evidence contradicts the reviewer.\n- **Inline review comments need a separate API call.** `gh pr view --json\n  reviews` does not include line-level comments. Always also call:\n\n  ```bash\n  gh api repos/<org>/<repo>/pulls/<N>/comments \\\n    --jq '[.[] | {author: .user.login, body: .body, path: .path, line: .line}]'\n  ```\n\n### Labels\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<N>/labels \\\n  --method POST --field 'labels[]=<label>'\n```\n\n### External links\n\nIf a replacement URL cannot be verified (e.g. network restrictions), treat\nthe task as blocked — do not commit a guessed URL. Report the blocker so a\nhuman can confirm. Exception: when a domain migration is well-established and\nonly the anchor is unverifiable, dropping the anchor is acceptable.\n\n## Page deletion checklist\n\nWhen removing a documentation page, search the entire `content/` tree and\nall YAML/TOML config files for the deleted page's slug and heading text.\nCross-references from unrelated sections and config-driven nav entries can\nremain and cause broken links.\n\n## Engine API version bumps\n\nWhen a new Engine API version ships, three coordinated changes are needed in\na single commit:\n\n1. `hugo.yaml` — update `latest_engine_api_version`, `docker_ce_version`,\n   and `docker_ce_version_prev`\n2. Create `content/reference/api/engine/version/v<NEW>.md` with the\n   `/latest/` aliases block (copy from previous version)\n3. Remove the aliases block from\n   `content/reference/api/engine/version/v<PREV>.md`\n\nNever leave both version files carrying `/latest/` aliases simultaneously.\n\n## Hugo icon references\n\nBefore changing an icon reference in response to a \"file not found\" error,\nverify the file actually exists via Hugo's virtual filesystem. Files may\nexist in `node_modules/@material-symbols/svg-400/rounded/` but not directly\nin `assets/icons/`. Check both locations before concluding an icon is\nmissing.\n\n## Self-improvement\n\nAfter completing work that reveals a non-obvious pattern or repo quirk not\nalready documented here, propose an update to this file. For automated\nsessions, note the learning in a comment on the issue. For human-supervised\nsessions, discuss with the user whether to update CLAUDE.md directly.\n","CLAUDE.md":"AGENTS.md","_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md":"---\ntitle: \"Agents\"\ndescription: \"Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents.\"\nkeywords: docker agent, ai agents, concepts, agents\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/agents/\n---\n\n_Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents._\n\n## What is an Agent?\n\nAn agent in Docker Agent is defined by:\n\n- **Model** — The AI model powering it (e.g., Claude, GPT-5, Gemini). See [Models](../models/index.md).\n- **Description** — A brief summary of what the agent does (used by other agents for delegation)\n- **Instruction** — The system prompt that defines the agent's behavior and personality\n- **Tools** — Capabilities like filesystem access, shell commands, or external APIs\n- **Sub-agents** — Other agents it can delegate tasks to\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: |\n      You are an expert developer. Write clean, efficient code\n      and explain your reasoning step by step.\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n```\n\n## The Root Agent\n\nEvery Docker Agent configuration has a **root agent** — the entry point that receives user messages. In a single-agent setup, this is the only agent. In a multi-agent setup, the root agent acts as a coordinator, delegating tasks to specialized sub-agents.\n\n> [!NOTE]\n> **Naming**\n>\n> The first agent defined in your YAML (or the one named `root`) is the root agent by default. You can also specify which agent to start with using `docker agent run config.yaml -a agent_name`.\n\n## Agent Properties\n\n| Property               | Type    | Required | Description                                                    |\n| ---------------------- | ------- | -------- | -------------------------------------------------------------- |\n| `model`                | string  | ✓        | Model reference (inline like `openai/gpt-5` or a named model) |\n| `description`          | string  | ✓        | What the agent does — used by other agents for delegation      |\n| `instruction`          | string  | ✓        | System prompt defining behavior                                |\n| `toolsets`             | array   | ✗        | List of tool configurations                                    |\n| `sub_agents`           | array   | ✗        | Names of agents this agent can delegate to                     |\n| `fallback`             | object  | ✗        | Fallback model configuration for resilience                    |\n| `add_date`             | boolean | ✗        | Include current date in context                                |\n| `add_environment_info` | boolean | ✗        | Include OS, working directory, git info in context             |\n| `max_iterations`       | int     | ✗        | Max tool-calling loops (default: unlimited)                    |\n| `commands`             | object  | ✗        | Named prompts callable via `/command`                          |\n| `skills`               | boolean \\| list | ✗    | Enable skill discovery and loading. `true` = `[\"local\"]`; list values may combine `\"local\"` with remote skill-server URLs. |\n\n## Model Fallbacks\n\nAgents can automatically fail over to alternative models when the primary model is unavailable:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2 # retries per model for 5xx errors\n      cooldown: 1m # stick with fallback after 429\n```\n\n## Named Commands\n\nDefine reusable prompts that can be invoked as commands:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a helpful assistant.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\n```bash\n# Run a named command\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n```\n\nCommands support environment variable interpolation using JavaScript template literal syntax. Undefined variables expand to empty strings.\n\n## Default Agent\n\nRunning `docker agent run` without a config argument uses `docker-agent.yaml`, `docker-agent.yml`, or `docker-agent.hcl` from the current directory when present. Otherwise, it uses a capable built-in default agent for quick tasks without needing any configuration.\n\n```bash\n# Use the project config or built-in default agent\n$ docker agent run\n\n# Override the default with an alias\n$ docker agent alias add default /path/to/my-agent.yaml\n$ docker agent run  # now runs your custom agent\n```\n\n> [!TIP]\n> **See also**\n>\n> For reusable task-specific instructions, see [Skills](../../features/skills/index.md). For multi-agent patterns, see [Multi-Agent](../multi-agent/index.md). For full config reference, see [Agent Config](../../configuration/agents/index.md).\n","_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md":"---\ntitle: \"Tools\"\ndescription: \"Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more.\"\nkeywords: docker agent, ai agents, concepts, tools\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/tools/\n---\n\n_Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more._\n\n## How Tools Work\n\nWhen an agent needs to perform an action, it makes a **tool call**. The Docker Agent runtime executes the tool and returns the result to the agent, which can then use it to continue its work.\n\n1. Agent receives a user message\n2. Agent decides it needs to use a tool (e.g., read a file)\n3. Docker Agent executes the tool and returns the result\n4. Agent incorporates the result and responds\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing tools that have side effects (shell commands, file writes). Use `--yolo` to auto-approve all tool calls.\n\n## Built-in Tools\n\nDocker Agent ships with several built-in tools that require no external dependencies. Each is enabled by adding its `type` to the agent's `toolsets` list:\n\n| Tool | Description |\n| --- | --- |\n| [Filesystem](../../tools/filesystem/index.md) | Read, write, list, search, and navigate files and directories |\n| [Shell](../../tools/shell/index.md) | Execute shell commands synchronously |\n| [Background Jobs](../../tools/background-jobs/index.md) | Run and manage long-running shell commands |\n| [Think](../../tools/think/index.md) | Step-by-step reasoning scratchpad for planning and decision-making |\n| [Todo](../../tools/todo/index.md) | Task list management for complex multi-step workflows |\n| [Tasks](../../tools/tasks/index.md) | Persistent task database shared across sessions |\n| [Memory](../../tools/memory/index.md) | Persistent key-value storage backed by SQLite |\n| [Fetch](../../tools/fetch/index.md) | Read content from HTTP/HTTPS URLs (GET only) |\n| [Script](../../tools/script/index.md) | Define custom shell scripts as named tools |\n| [LSP](../../tools/lsp/index.md) | Connect to Language Server Protocol servers for code intelligence |\n| [API](../../tools/api/index.md) | Create custom tools that call HTTP APIs without writing code |\n| [OpenAPI](../../tools/openapi/index.md) | Generate tools from an OpenAPI 3.x document |\n| [RAG](../../tools/rag/index.md) | Retrieval-augmented generation over indexed sources |\n| [Model Picker](../../tools/model-picker/index.md) | Let the agent pick between several models per turn |\n| [User Prompt](../../tools/user-prompt/index.md) | Ask users questions and collect interactive input |\n| [Open URL](../../tools/open-url/index.md) | Open a fixed URL in the user's default browser |\n| [Transfer Task](../../tools/transfer-task/index.md) | Delegate tasks to sub-agents (auto-enabled with `sub_agents`) |\n| [Background Agents](../../tools/background-agents/index.md) | Dispatch work to sub-agents concurrently |\n| [Handoff](../../tools/handoff/index.md) | Hand the conversation off to another local agent in the same config (auto-enabled with `handoffs:`) |\n| [A2A](../../tools/a2a/index.md) | Connect to remote agents via the Agent-to-Agent protocol |\n| [MCP Catalog](../../tools/mcp-catalog/index.md) | Discover and activate remote MCP servers from the Docker MCP Catalog on demand |\n| [Git](../../tools/git/index.md) | Read-only git repository inspection |\n| [Scheduler](../../tools/scheduler/index.md) | Schedule instructions to run at a time or on a recurring interval |\n| [Webhook](../../tools/webhook/index.md) | Outbound notifications to Slack, Discord, Telegram, IFTTT, and more |\n| [Plan](../../tools/plan/index.md) | Shared persistent scratchpad for multi-agent collaboration |\n| [Session Plan](../../tools/session_plan/index.md) | Per-session plan tracker for the draft/review/execute workflow |\n| [Session Context](../../tools/session_context/index.md) | Reference a previous session as context |\n\n## MCP Tools\n\nDocker Agent supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for extending agents with external tools. There are three ways to connect MCP tools:\n\n- **Docker MCP** (recommended) — Run MCP servers in Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway). Browse the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n- **Local MCP (stdio)** — Run MCP servers as local processes communicating over stdin/stdout.\n- **Remote MCP (Streamable HTTP / SSE)** — Connect to MCP servers running on a network. See [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n```\n\nSee [Tool Config](../../configuration/tools/index.md#mcp-tools) for full MCP configuration reference.\n\n> [!TIP]\n> **See also**\n>\n> For full configuration reference, see [Tool Config](../../configuration/tools/index.md).\n","_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md":"---\ntitle: \"Agent Configuration\"\ndescription: \"Complete reference for defining agents in your YAML configuration.\"\nkeywords: docker agent, ai agents, configuration, yaml, agent configuration\nlinkTitle: \"Agent Config\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/agents/\n---\n\n_Complete reference for defining agents in your YAML configuration._\n\nA configuration must define at least one agent under `agents`.\n\n## Full Schema\n\n<!-- yaml-lint:skip -->\n```yaml\nagents:\n  agent_name:\n    model: string # Required: model reference\n    description: string # Required: what this agent does\n    instruction: string # Required (unless instruction_file): system prompt\n    instruction_file: string | [list] # Optional: load the system prompt from one or more files relative to this config (mutually exclusive with instruction)\n    sub_agents: [list] # Optional: local or external sub-agent references\n    toolsets: [list] # Optional: tool configurations (use `type: rag` for RAG sources)\n    fallback: # Optional: fallback config\n      models: [list]\n      retries: 2\n      cooldown: 1m\n    add_date: boolean # Optional: add date to context\n    add_environment_info: boolean # Optional: add env info to context\n    add_prompt_files: [list] # Optional: include additional prompt files\n    add_description_parameter: bool # Optional: add description to tool schema\n    redact_secrets: boolean # Optional: scrub detected secrets out of tool args, outgoing chat messages, and tool output\n    code_mode_tools: boolean # Optional: let the agent write JavaScript to orchestrate tool calls (see Code Mode)\n    max_iterations: int # Optional: max tool-calling loops\n    max_consecutive_tool_calls: int # Optional: max identical consecutive tool calls\n    max_old_tool_call_tokens: int # Optional: token budget for old tool call content (disabled unless positive)\n    max_tool_result_tokens: int # Optional: per-tool-result token cap with middle-out truncation (disabled unless positive)\n    num_history_items: int # Optional: limit conversation history\n    session_compaction: boolean # Optional: disable automatic session compaction (default: true)\n    compaction_threshold: float # Optional: context-window fraction that triggers auto-compaction (0–1, default: 0.9)\n    compaction_model: string # Optional: model used for session-compaction (summary generation)\n    use_toolsets: [list] # Optional: names of top-level toolsets to merge into this agent\n    readonly: boolean # Optional: restrict all toolsets to read-only tools only\n    skills: boolean | [list] # Optional: enable skill discovery (true/false or list of names and/or sources)\n    use_commands: [list] # Optional: names of top-level commands groups to merge into this agent\n    use_skills: [list] # Optional: names of top-level skills groups to merge into this agent\n    commands: # Optional: named prompts\n      name: \"prompt text\" # or {instruction: \"prompt\", agent: \"sub_agent_name\"} or {url: \"https://...\"} (TUI only)\n    welcome_message: string # Optional: message shown at session start\n    handoffs: [list] # Optional: agent names this agent can hand off to\n    force_handoff: string # Optional: agent that always receives the conversation when this agent stops\n    hooks: # Optional: lifecycle hooks\n      pre_tool_use: [list]\n      tool_response_transform: [list]\n      post_tool_use: [list]\n      session_start: [list]\n      session_end: [list]\n      on_user_input: [list]\n      stop: [list]\n      notification: [list]\n    structured_output: # Optional: constrain output format\n      name: string\n      schema: object\n    cache: # Optional: response cache (skip the model on repeat questions)\n      enabled: boolean\n      case_sensitive: boolean\n      trim_spaces: boolean\n      path: string\n    harness: # Optional: delegate to an external coding CLI (Claude Code, Codex, opencode, pi)\n      type: string # Required: claude-code | codex | opencode | pi\n      model: string # Optional: model override forwarded to the CLI (omit for the CLI's own default)\n      effort: string # claude-code only: low | medium | high | xhigh | max (omit for the Claude Code default)\n      agent: string # opencode only: agent profile name\n      thinking: boolean # opencode only: enable extended thinking\n```\n\n> [!TIP]\n> **See also**\n>\n> For model parameters, see [Model Config](../models/index.md). For tool details, see [Tool Config](../tools/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n\n## Properties Reference\n\n| Property                    | Type    | Required | Description                                                                                                                                                                   |\n| --------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `model`                     | string  | ✓        | Model reference. Either inline (`openai/gpt-5`) or a named model from the `models` section.                                                                              |\n| `description`               | string  | ✓        | Brief description of the agent's purpose. Used by coordinators to decide delegation.                                                                                          |\n| `instruction`               | string  | ✓        | System prompt that defines the agent's behavior, personality, and constraints. Required unless `instruction_file` is set.                                                      |\n| `instruction_file`          | string \\| array  | ✗        | Path(s) to a file or files (relative to the config file's directory) whose contents become the agent's instruction, loaded at startup. Accepts a single path or a list; multiple files are concatenated in order, separated by a blank line. Mutually exclusive with `instruction`. Each path must be a local relative path inside the config directory (absolute paths and `..` traversal are rejected). Only supported for local file-based configs, not OCI/URL sources. See [External Instruction Files](#external-instruction-files) below. |\n| `sub_agents`                | array   | ✗        | List of agent names or external OCI references this agent can delegate to. Supports local agents, registry references (e.g., `myorg/agent:tag`), and named references (`name:reference`). Automatically enables the `transfer_task` tool. Pin external OCI references to a digest (`name@sha256:…`) to skip the per-run registry lookup that tag references incur. See [External Sub-Agents](../../concepts/multi-agent/index.md#external-sub-agents-from-registries). |\n| `toolsets`                  | array   | ✗        | List of tool configurations. See [Tool Config](../tools/index.md).                                                                                                        |\n| `fallback`                  | object  | ✗        | Automatic model failover configuration.                                                                                                                                       |\n| `add_date`                  | boolean | ✗        | When `true`, injects the current date into the agent's context.                                                                                                               |\n| `add_environment_info`      | boolean | ✗        | When `true`, injects working directory, OS, CPU architecture, and git info into context.                                                                                      |\n| `add_prompt_files`          | array   | ✗        | List of file paths whose contents are appended to the system prompt. Useful for including coding standards, guidelines, or additional context.                                |\n| `add_description_parameter` | boolean | ✗        | When `true`, adds agent descriptions as a parameter in tool schemas. Helps with tool selection in multi-agent scenarios.                                                      |\n| `redact_secrets`            | boolean | ✗        | When `true`, scrubs detected secrets (API keys, tokens, private keys, etc.) out of tool-call arguments, outgoing chat messages, and tool output before they reach a tool, the model, or downstream consumers. See [Redacting Secrets](#redacting-secrets) below.   |\n| `code_mode_tools`           | boolean | ✗        | When `true`, replaces the agent's individual tools with a single tool that runs a JavaScript script calling as many of them as needed in one turn. See [Code Mode](../../features/code-mode/index.md). |\n| `max_iterations`            | int     | ✗        | Maximum number of tool-calling loops. Default: unlimited (0). Set this to prevent infinite loops.                                                                             |\n| `max_consecutive_tool_calls` | int     | ✗        | Maximum consecutive identical tool calls before the agent is terminated, preventing degenerate loops. Default: `5`.                                                          |\n| `max_old_tool_call_tokens`  | int     | ✗        | Maximum number of tokens to keep from old tool call arguments and results. Older tool calls beyond this budget have their content replaced with a placeholder, saving context space. Tokens are approximated as `len/4`. Truncation is disabled by default; set a positive value to enable it. Set to `-1` to disable truncation (unlimited). |\n| `max_tool_result_tokens`    | int     | ✗        | Maximum number of tokens to keep from each tool result when it is added to the session. Oversized results are truncated middle-out: the head and tail are kept and the removed middle is replaced with a truncation marker. Textual documents attached to the result share the same budget. Tokens are approximated as `len/4`. The cap is disabled by default; set a positive value to enable it. `0` and `-1` both leave tool results unbounded. |\n| `num_history_items`         | int     | ✗        | Limit the number of conversation history messages sent to the model. Useful for managing context window size with long conversations. Default: unlimited (all messages sent). |\n| `session_compaction`        | boolean | ✗        | When `false`, disables automatic session compaction for this agent: neither the proactive threshold trigger nor the post-overflow auto-recovery runs. The manual `/compact` command remains available. Default: `true`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_threshold`      | float   | ✗        | Fraction of the model's context window at which proactive auto-compaction triggers. Must be greater than `0` and at most `1`. A `compaction_threshold` set on the agent's model takes precedence. Default: `0.9`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_model`          | string  | ✗        | Model used for session compaction (summary generation). Can be a named model or an inline `provider/model` string. This agent-level value takes precedence over a `compaction_model` set on the agent's model or provider; when none is set, the agent's own model compacts. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `skills`                    | bool/array | ✗     | Enable automatic skill discovery. `true` loads all discovered local skills, `false` disables them. A list can mix skill sources (`local` or `https://…` URLs) and skill names to include — see [Skills](../../features/skills/index.md).                                                     |\n| `commands`                  | object  | ✗        | Named prompts that can be run with `docker agent run config.yaml /command_name`. Can be simple strings or objects with `instruction` and/or `agent` fields for agent switching, or a `url` field to open a link in the browser (TUI only). See [Named Commands](#named-commands) below. |\n| `use_commands`              | list of string | ✗   | Names of top-level `commands` groups to merge into this agent. Inline `commands` entries take precedence on name conflicts. Default: `[]`. |\n| `use_skills`                | list of string | ✗   | Names of top-level `skills` groups to merge into this agent. Inline skills are deduplicated by name against merged entries. Default: `[]`. |\n| `use_toolsets`              | list of string | ✗   | Names of top-level `toolsets` groups to merge into this agent. See [Reusable Toolsets](../overview/index.md#reusable-toolsets-toolsets). Default: `[]`. |\n| `readonly`                  | boolean | ✗   | When `true`, every toolset on this agent is filtered to expose only read-only tools (those annotated with a read-only hint). Mutating tools are removed at load time and cannot be called even if the model tries. See [Read-Only Agents](#read-only-agents) below. |\n| `welcome_message`           | string  | ✗        | Message displayed to the user when a session starts. Rendered as Markdown in the TUI. **Not sent to the model** — it exists purely for the user's benefit. Useful for telling users what the agent can do and what commands are available. |\n| `handoffs`                  | array   | ✗        | List of agent names this agent can hand off the conversation to. Enables the `handoff` tool. See [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).                  |\n| `force_handoff`             | string  | ✗        | Name of an agent that unconditionally receives the conversation whenever this agent produces a final response. The runtime performs the switch itself, bypassing the LLM's tool-calling, guaranteeing deterministic pipelines. Must not reference the agent itself, and chains must not form a cycle. See [Forced Handoffs](../../concepts/multi-agent/index.md#forced-handoffs). |\n| `hooks`                     | object  | ✗        | Lifecycle hooks for running commands at various points. See [Hooks](../hooks/index.md).                                                                                   |\n| `structured_output`         | object  | ✗        | Constrain agent output to match a JSON schema. See [Structured Output](../structured-output/index.md).                                                                    |\n| `cache`                     | object  | ✗        | Response cache. When the same user question is asked again, the previous answer is replayed verbatim and the model is not called. See [Response Cache](#response-cache) below.                  |\n| `harness`                   | object  | ✗        | Run this agent through an external coding CLI instead of a model. **Note:** Any `toolsets:` defined on the same agent are silently ignored when `harness:` is set — the external CLI brings its own tools. See [Coding Harnesses](../../features/harnesses/index.md). |\n\n> [!WARNING]\n> **max_iterations**\n>\n> Default is `0` (unlimited). Always set `max_iterations` for agents with powerful tools like `shell` to prevent infinite loops. A value of 20–50 is typical for development agents.\n\n> [!TIP]\n> **Managing long sessions**\n>\n> `max_old_tool_call_tokens`, `max_tool_result_tokens`, `num_history_items`, `session_compaction`, and `compaction_threshold` all help keep long-running sessions inside the model's context window. See the [Context & Compaction guide](../../guides/compaction/index.md) for how to combine them.\n\n## External Instruction Files\n\nLong system prompts can be kept in their own files instead of being inlined in\nthe YAML, using `instruction_file`. This separates infrastructure configuration\n(models, providers, tools) from behavioral content (the prompt), which keeps\nversion-control diffs focused, reduces merge conflicts on shared configs, and\nlets instruction content be edited without risking YAML syntax errors.\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-5-mini\n    description: Routes work between specialist agents\n    instruction_file: instructions/coordinator.md\n    sub_agents:\n      - writer\n  writer:\n    model: openai/gpt-5-mini\n    description: Drafts and edits written content\n    instruction_file: instructions/writer.md\n```\n\nThe path is resolved relative to the config file's directory and the file's\ncontents are loaded as the agent's instruction when the config is loaded. Notes:\n\n- **Mutually exclusive** with `instruction`. Setting both is an error.\n- Each path must be a **local relative path inside the config directory**.\n  Absolute paths and `..` traversal are rejected.\n- A **list** of files is also accepted; their contents are concatenated in\n  order, separated by a blank line. This lets a shared preamble be reused\n  across agents while each agent appends its own specifics:\n\n  ```yaml\n  agents:\n    writer:\n      model: openai/gpt-5-mini\n      description: Drafts and edits written content\n      instruction_file:\n        - instructions/shared-preamble.md\n        - instructions/writer.md\n  ```\n\n- Only supported for **local file-based configs**, not agents loaded from OCI\n  registries or URLs. When an agent is pushed with `docker agent share push`,\n  the file contents are inlined into the pushed artifact, so the published\n  agent stays self-contained.\n\nA runnable example lives in [`examples/instruction_file.yaml`](https://github.com/docker/docker-agent/blob/main/examples/instruction_file.yaml).\n\n## Prompt Files\n\n`add_prompt_files` injects the contents of one or more files into the agent's\ncontext at the start of every turn — handy for repo-wide conventions like\n`AGENTS.md` or `CLAUDE.md` that should stay available without being pasted\ninto `instruction`:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A helpful coding assistant\n    instruction: You are an expert software developer.\n    add_prompt_files:\n      - AGENTS.md\n```\n\nFor each name, the agent loads the closest match found by walking up from the\ncurrent working directory, plus (if it's a different file) a copy at that\nname directly under the user's home directory — so a personal `~/AGENTS.md`\ncan layer on top of a repo-local one. Missing files are skipped rather than\nerroring. Because resolution and the read happen on every turn, edits to the\nfile are picked up without restarting the agent.\n\nUse `--prompt-file` to add files for a single run without editing the\nconfig. It's merged with any `add_prompt_files` already set on the agent,\nwith duplicates dropped:\n\n```bash\n$ docker agent run agent.yaml --prompt-file CONTRIBUTING.md\n```\n\nResolved prompt files show up as their own entries in the `/context` dialog — see [File Attachments](../../features/tui/index.md#file-attachments) in the Terminal UI guide.\n\nSee [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how prompt files compare to `@`/`/attach` attachments, the `rag` toolset, and sending content over the API/chat server.\n\n## Response Cache\n\nThe response cache short-circuits the model when the same user question is asked again. The first time a question is asked, the agent calls the model normally and stores the assistant's reply. Subsequent identical questions skip the model entirely and replay the stored reply verbatim.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Cached assistant\n    instruction: You are a helpful assistant.\n    cache:\n      enabled: true          # required to turn the cache on\n      case_sensitive: false  # default: false (\"Hello\" == \"hello\")\n      trim_spaces: true      # default: false (\"  hello  \" == \"hello\")\n      path: ./cache.json     # optional: persist to disk; omit for in-memory\n```\n\n| Property         | Type    | Default | Description                                                                                                                                                                                                                       |\n| ---------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `enabled`        | boolean | `false` | Master switch. When `false` (or when the `cache` section is omitted), no caching is performed.                                                                                                                                     |\n| `case_sensitive` | boolean | `false` | When `true`, questions must match exactly (including case) to hit the cache.                                                                                                                                                       |\n| `trim_spaces`    | boolean | `false` | When `true`, leading and trailing whitespace is stripped from the question before it is compared.                                                                                                                                  |\n| `path`           | string  | _empty_ | When set, cache entries are persisted to a JSON file at the given path and reloaded on startup so the cache survives restarts. Relative paths resolve against the agent config directory. When empty, the cache lives in memory only. |\n\n**How it works**\n\n- The cache key is the latest user message in the session, normalized according to `case_sensitive` and `trim_spaces`.\n- On a hit, the cached reply is added to the session as the assistant message and stop hooks fire normally — the rest of the agent (tools, sub-agents, the model) is bypassed.\n- On a miss, the agent runs normally; the final assistant message produced by the first stop of the run is then stored under the question's key.\n- Only the response to the original user question of a run is cached; follow-up turns inside the same `RunStream` are not.\n\n**File-backed storage**\n\nWhen `path` is set, every `Store` rewrites the entire cache file. Writes are **atomic**: the new content is written to a sibling temp file, `fsync`'d, and renamed over the destination, so a concurrent reader (or a process that crashes mid-write) will always see either the previous content or the new content in full — never a partially written file. The parent directory is also `fsync`'d after the rename so the rename itself is durable.\n\n**Cross-process sharing**\n\nMultiple processes can share the same `path:` cache file safely. Every `Store` takes an exclusive advisory lock on a sibling `<path>.lock` file (POSIX `flock(2)` on Unix, `LockFileEx` on Windows), reloads the current on-disk state under the lock, merges the new entry, and writes back atomically. Two processes that store *different* keys at the same time both see their writes preserved on disk; the lock window is short (one read + one fsync'd write).\n\n`Lookup` watches the file's modification time and reloads the in-memory map when the file has advanced since its last load, so writes from a sibling process become visible without a restart. The `<path>.lock` sentinel file is created on first write and never deleted: removing it would let two processes lock different inodes and lose mutual exclusion.\n\n## Redacting Secrets\n\nThe `redact_secrets` flag is a single agent-level switch that scrubs accidentally leaked credentials, tokens, and private keys out of an agent's I/O. It wires up three complementary defenses:\n\n1. A `pre_tool_use` built-in hook that scrubs detected secrets from the **arguments of every tool call**, before the tool sees them.\n2. A `before_llm_call` built-in hook that scrubs the same patterns from **outgoing chat messages** — message content, multi-part text content, prior reasoning content, and the JSON-encoded arguments of any tool call still in the conversation — before they reach the model provider.\n3. A `tool_response_transform` built-in hook that scrubs **tool output at the source**, so the secret never reaches event consumers, the persisted session file, the `post_tool_use` hook input, or the next LLM call.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: A helpful assistant that scrubs secrets before they leak\n    instruction: |\n      You are a helpful assistant. If the user accidentally pastes a token,\n      do your best work without echoing the secret back.\n    redact_secrets: true\n    toolsets:\n      - type: shell\n```\n\nDetection uses the [portcullis](https://github.com/docker/portcullis) ruleset, which recognises common secret patterns including:\n\n- GitHub Personal Access Tokens (`ghp_*`, `gho_*`, `ghu_*`, `ghs_*`, `ghr_*`, fine-grained `github_pat_*`)\n- AWS access keys (`AKIA*`, `ASIA*`, …) and secret access keys\n- GitLab PATs (`glpat-*`), Hugging Face tokens (`hf_*`)\n- Stripe (`sk_live_*`, `pk_test_*`, …), Slack (`xoxb-*`, …), Shopify, Twilio, Discord, Atlassian, Mailchimp, SendGrid, and many more\n- JWTs, GCP service-account JSON, Heroku keys, Docker Hub PATs (`dckr_pat_*`)\n- PEM-encoded private keys (`-----BEGIN … PRIVATE KEY-----` blocks)\n\nEach detected span is replaced with the literal string `[REDACTED]`; the surrounding text is preserved so a redacted argument still looks like a legitimate flag (e.g. `--token=[REDACTED]`). Redaction is idempotent — applying it twice yields the same result.\n\n> [!NOTE]\n> **False positives vs. false negatives**\n>\n> False positives are extremely rare: every rule pairs a regex with a discriminating keyword, so plain English never trips detection. **False negatives are possible** — only patterns the ruleset recognises are scrubbed, so this is a defense-in-depth feature, not a substitute for keeping secrets out of the conversation in the first place. Pair it with a proper [secret manager](../../guides/secrets/index.md) for the credentials your agent actually needs.\n\n> [!NOTE]\n> **Equivalent hook entry**\n>\n> Setting `redact_secrets: true` on the agent is shorthand for auto-registering all three legs of the feature as hook entries. They share the _same_ built-in name (`type: builtin`, `command: redact_secrets`) on `pre_tool_use`, `before_llm_call`, and `tool_response_transform` respectively — the implementation dispatches on the hook event. You can spell them out by hand to scope a leg to a subset of tools (set `matcher:` to a regex), stack them with other rewriters in a specific order, or enable just one or two legs. See [`examples/redact_secrets_hooks.yaml`](https://github.com/docker/docker-agent/blob/main/examples/redact_secrets_hooks.yaml) for a complete manual wiring and the [Hooks reference](../hooks/index.md#available-built-ins) for the builtin's event coverage.\n\n## Welcome Message\n\nDisplay a message when users start a session:\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-5\n    description: Development assistant\n    instruction: You are a helpful coding assistant.\n    welcome_message: |\n      👋 Welcome! I'm your development assistant.\n\n      I can help you with:\n      - Writing and reviewing code\n      - Running tests and debugging\n      - Explaining concepts\n\n      What would you like to work on?\n```\n\n## Deferred Tool Loading\n\nToolsets support `defer` to load tools on-demand and speed up agent startup. See [Deferred Tool Loading](../tools/index.md#deferred-tool-loading) for details.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-purpose assistant\n    instruction: You have access to many tools.\n    toolsets:\n      - type: mcp\n        ref: docker:github-official\n        defer: true\n      - type: filesystem\n```\n\n## Fallback Configuration\n\nAutomatically switch to backup models when the primary fails:\n\n| Property   | Type   | Default | Description                                                |\n| ---------- | ------ | ------- | ---------------------------------------------------------- |\n| `models`   | array  | `[]`    | Fallback models to try in order                            |\n| `retries`  | int    | `2`     | Retries per model for 5xx errors. `-1` to disable.         |\n| `cooldown` | string | `1m`    | How long to stick with a fallback after a rate limit (429) |\n\n**Error handling:**\n\n- **Retryable** (same model with backoff): HTTP 5xx, 408, network timeouts\n- **Non-retryable** (skip to next model): HTTP 429, 4xx client errors\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2\n      cooldown: 1m\n```\n\n## Named Commands\n\n> [!TIP]\n> **Full reference**\n>\n> This section covers the basics. For URL commands, agent-switching commands, reusable top-level `commands:` groups, and hiding commands with `--disable-commands`, see [Custom Commands](../commands/index.md).\n\nDefine reusable prompt shortcuts that can send prompts to the current agent, switch to a different sub-agent, or open a URL in the browser:\n\n> **Note:** Named slash commands execute immediately, even while the agent is processing another message. Unlike regular chat messages (which are queued), slash commands interrupt or direct the agent even while it is mid-response.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n      deploy: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n      \n      # Advanced format with agent switching\n      plan:\n        agent: planner  # Switch to the 'planner' agent\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"  # Optional: send this prompt after switching\n      \n      # Agent switching without instruction - forwards remaining text as prompt\n      review:\n        agent: reviewer  # Any text after /review is sent to the reviewer agent\n\n      # URL command - opens a link in the browser instead of messaging the agent\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n```\n\n### Command Formats\n\nCommands support three formats:\n\n1. **Simple string format**: The string becomes the instruction sent to the current agent\n\n   ```yaml\n   df: \"Check disk space\"\n   ```\n\n2. **Advanced object format**: Supports agent switching and optional instructions\n\n   ```yaml\n   plan:\n     agent: planner  # Required: name of any agent defined in the team\n     instruction: \"Plan: ${args.join(\\\" \\\")}\"  # Optional: prompt to send after switching\n     description: \"Switch to planning mode\"  # Optional: shown in help text\n   ```\n\n3. **URL format**: Opens a link in the browser instead of messaging the agent\n\n   ```yaml\n   docs:\n     url: https://docs.docker.com/          # Required: URL to open\n     description: \"Open the documentation\"  # Optional: shown in help text\n   ```\n\nWhen `agent` is set without `instruction`, any text typed after the slash command (e.g., `/plan build a web app`) is forwarded as a prompt to the target agent. The target agent can be **any agent defined in the team configuration** — it does not need to be listed in the current agent's `sub_agents` array.\n\n**Argument and expansion syntax**\n\nAn `instruction` string can reference the command's arguments and expand tool calls:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments, in the order the user typed them after the command\n- `${args.join(\" \")}` — all arguments joined into a single string\n- `${tool_name({...})}` — calls a tool and inlines its return value (any tool available to the agent)\n- `!tool_name(key=value)` — legacy tool-call form: calls a tool with plain `key=value` arguments and inlines its output\n\n### Agent-Switching Commands\n\nCommands with an `agent` field switch the active agent for that command's scope. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the appropriate specialist.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n      # Simple prompt command (no switching)\n      status: \"Summarize what we have accomplished so far\"\n\n  planner:\n    model: openai/gpt-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\n**Agent-switching vs. `handoff`**\n\n| | Agent-switching command | `handoff` tool |\n| --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` |\n| **Session** | Stays in the same session | Stays in the same session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation |\n| **Return** | User must explicitly switch back | Target agent can chain to another agent |\n\n**Agent-switching vs. `transfer_task`**\n\n`transfer_task` launches a **sub-session**: the root agent sends a task, the child runs in isolation, and the result is returned to the root. The root agent stays in control and the child's work is never in the main conversation. Use `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n```bash\n# Run commands from the CLI\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\nCommands use JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation. Undefined variables expand to empty strings.\n\nThe same syntax is also expanded in agent and toolset instructions: `agents.<name>.instruction` and `toolsets[*].instruction` support `${env.X}` placeholders (with optional `||` defaults and ternary expressions). `agents.<name>.description` and `agents.<name>.welcome_message` also support it.\n\nNote that path-like fields (`working_dir`, `path`) primarily use a shell-style syntax (`$VAR`, `${VAR}`, `~`), and also accept `${env.X}` as an alias (though not richer JS expressions). See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full table.\n\n### URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — both standard web URLs and custom schemes such as `docker-desktop://` for deep links. URL commands are TUI-only — they have no effect when run from the CLI.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Read-Only Agents\n\nSet `readonly: true` on an agent to restrict all of its toolsets to tools that are annotated as read-only. Mutating tools are filtered out at load time — the agent cannot list or call them, even if the model hallucinates a call.\n\nYou can also set `readonly: true` on an individual toolset to restrict only that toolset while leaving others unrestricted.\n\n```yaml\nagents:\n  # Agent-level readonly: every toolset is restricted to read-only tools.\n  inspector:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only inspector that can explore but never modify.\n    instruction: Explore the project. Do not make changes.\n    readonly: true\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  # Toolset-level readonly: only the filesystem toolset is restricted;\n  # the shell toolset keeps all of its tools.\n  mixed:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only file access, full shell access.\n    instruction: You can read files and run any shell command.\n    toolsets:\n      - type: filesystem\n        readonly: true\n      - type: shell\n```\n\nSee [`examples/readonly.yaml`](https://github.com/docker/docker-agent/blob/main/examples/readonly.yaml) for a complete example.\n\n> [!NOTE]\n> **Which tools are read-only?**\n>\n> Whether a tool is read-only is determined by its `ReadOnlyHint` annotation. For built-in tools, read-only operations (list/read/search) carry the hint; mutating operations (write/delete/execute) do not. Custom and MCP tools expose the hint via their own annotations.\n\n## Complete Example\n\n```yaml\nmodels:\n  claude:\n    provider: anthropic\n    model: claude-sonnet-4-5\n    max_tokens: 64000\n\nagents:\n  root:\n    model: claude\n    description: Technical lead coordinating development\n    instruction: |\n      You are a technical lead. Analyze requests and delegate\n      to the right specialist. Always review work before responding.\n    welcome_message: \"👋 I'm your tech lead. How can I help today?\"\n    sub_agents: [developer, researcher]\n    add_date: true\n    add_environment_info: true\n    fallback:\n      models: [openai/gpt-5]\n    toolsets:\n      - type: think\n    commands:\n      review: \"Review all recent code changes for issues\"\n    hooks:\n      session_start:\n        - type: command\n          command: \"./scripts/setup.sh\"\n\n  developer:\n    model: claude\n    description: Expert software developer\n    instruction: Write clean, tested, production-ready code.\n    max_iterations: 30\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n\n  researcher:\n    model: openai/gpt-5\n    description: Web researcher with memory\n    instruction: Search for information and remember findings.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n      - type: memory\n        path: ./research.db\n```\n","_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md":"---\ntitle: \"Custom Commands\"\ndescription: \"Define slash commands that send prompts, open URLs, or switch agents, and reuse them across agents with top-level command groups.\"\nkeywords: docker agent, ai agents, configuration, yaml, custom commands, slash commands\nlinkTitle: \"Custom Commands\"\nweight: 55\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/commands/\n---\n\n_Define slash commands that send prompts, open URLs, or switch agents._\n\n## What Slash Commands Are\n\nA slash command is a named shortcut a user types in the TUI (`/df`, `/deploy`, `/plan`) or on the CLI (`docker agent run agent.yaml /df`) instead of typing out a full prompt. Every agent can declare its own commands under `commands:`, and top-level `commands:` groups let multiple agents share the same set without duplicating them.\n\nUnlike regular chat messages — which are queued while the agent is busy — slash commands (both built-in and named) execute immediately, even mid-response.\n\nCommands come in three shapes:\n\n| Shape | What it does |\n| --- | --- |\n| [Prompt command](#prompt-commands) | Sends a prompt to the current agent |\n| [URL command](#url-commands) | Opens a link in the user's browser (full TUI only) |\n| [Agent-switching command](#agent-switching-commands) | Switches the active agent, optionally with a prompt (full TUI and CLI) |\n\n> [!IMPORTANT]\n> **Behavior differs by frontend**\n>\n> `url` and `agent` are only fully honored in the **full TUI**, which checks `url` before `agent` (a URL command opens the browser and stops there; an agent-switching command switches before sending any instruction). The **lean TUI** doesn't special-case either field — it only resolves a command's expanded text and sends it as a chat message, so a URL-only command silently sends whatever trailing text followed the slash (often nothing, opening no browser) and an agent-switching command sends its instruction to the *current* agent instead of the target. The **CLI** (`docker agent run agent.yaml /command`) switches agents like the full TUI, but has no browser to open, so `url` has no effect there. The **HTTP API** (`POST /api/sessions/:id/agent/:agent`) resolves agent-switching commands server-side: if the message content starts with a slash command whose `agent` field is set, the active agent is switched and the message is rewritten before the turn runs. Prompt-only and URL commands are not resolved server-side and pass through to the model unchanged.\n\n## Prompt Commands\n\nThe simplest form: a string value that becomes the instruction sent to the current agent.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A system administrator assistant.\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\nFor more control, use the object form with an `instruction:` field, plus an optional `description:` shown in completion dialogs and help text:\n\n```yaml\ncommands:\n  deploy:\n    description: \"Deploy the application to staging\"\n    instruction: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n```\n\nCommands support JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation, with optional `||` defaults and ternary expressions — the same syntax as agent `instruction` and `description`. Undefined variables expand to the empty string. See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full picture.\n\nPrompt commands can also reference the text typed after the slash and call tools, using the same `${...}` expansion engine as `${env.VAR}`:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments (whitespace-tokenized; quoted substrings keep their spaces together).\n- `${args}` or `${args.join(\" \")}` — the full argument list.\n- `${tool_name({key: value, ...})}` — calls an agent tool and inlines its output. JS expressions are evaluated before tool commands, so tool output is never itself re-evaluated as JS.\n- `` !tool_name(key=value) `` — legacy bang syntax for the same tool-call inlining; still supported alongside `${tool_name({...})}`.\n\nIf `instruction` uses none of the `${args...}` placeholders, any text typed after the slash is appended to the resolved instruction automatically.\n\n```yaml\ncommands:\n  fix:\n    description: \"Fix a file, with optional extra options\"\n    instruction: \"Fix the file ${args[0]} with options ${args[1]}\"\n  run:\n    description: \"Run a command with all the typed arguments\"\n    instruction: 'Run command with args: ${args.join(\" \")}'\n  lint:\n    description: \"Show the current lint output\"\n    instruction: 'Lint: ${shell({cmd: \"task lint\"})}'\n```\n\n```bash\n# Run commands from the CLI too\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ docker agent run agent.yaml /fix main.go --verbose\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\n## URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — standard web URLs and custom schemes such as `docker-desktop://` for deep links.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\n> [!NOTE]\n> **Full TUI only**\n>\n> URL commands only open a browser in the full TUI. The CLI and lean TUI don't check the `url` field at all, so `docker agent run agent.yaml /docs` never opens a browser there — but the command is still dispatched: its resolved text (usually empty, for a URL-only command) is sent as a prompt and can trigger a model turn.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Agent-Switching Commands\n\nA command with an `agent` field switches the active agent for the rest of the conversation. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the right specialist.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(' ')}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\nWhen `agent` is set **without** `instruction`, any text typed after the slash command (e.g. `/review fix the auth bug`) is forwarded as a prompt to the target agent. When both are set, the agent is switched first, then the instruction is sent to the new agent. Either way, the target can be **any agent defined in the team**, not just one of the current agent's own `sub_agents` — `sub_agents` above is shown because `planner` and `reviewer` also happen to be delegation targets, not because `agent:` requires it.\n\nAgent switching stays in the same session — the target agent sees the full conversation history, and the user must explicitly switch back (there's no automatic return). This is different from the two other ways agents hand off work:\n\n| | Agent-switching command | `handoff` tool | `transfer_task` |\n| --- | --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` | Model calls `transfer_task()` |\n| **Session** | Stays in the same session | Stays in the same session | Launches an isolated sub-session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation | Child runs in isolation; only the result returns |\n| **Control** | User must explicitly switch back | Target agent can chain to another agent | Root agent stays in control |\n\nUse `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n## Reusable Command Groups\n\nRepeated command sets across agents can be hoisted into the top-level `commands:` section and pulled in by name with `use_commands:` — the same reuse pattern as `mcps:` for MCP servers and `toolsets:` for shared toolsets.\n\n```yaml\ncommands:\n  ci:\n    deploy: \"Deploy the application\"\n    test: \"Run the test suite\"\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Lead developer\n    instruction: You are the lead developer. Coordinate the team.\n    use_commands: [ci]      # reuse the \"ci\" command group\n    commands:\n      lint: \"Run the linter\"  # inline command, merged in (wins on conflict)\n\n  docs-writer:\n    model: anthropic/claude-sonnet-4-5\n    description: Documentation writer\n    instruction: You write and maintain the project documentation.\n    use_commands: [ci]      # same group, reused without duplication\n```\n\nAn agent's own inline `commands:` entries take precedence over merged `use_commands:` entries on name conflicts. See [`examples/shared-commands-skills.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared-commands-skills.yaml) for a complete example that also covers the equivalent `skills:` / `use_skills:` pattern.\n\n## Hiding Commands\n\nUse `--disable-commands` to hide and disable specific slash commands in the TUI — built-in ones (`/cost`, `/eval`, `/model`, …) or your own named ones. Accepts a comma-separated list; the leading slash is optional and matching is case-insensitive.\n\n```bash\n$ docker agent run agent.yaml --disable-commands=\"/cost,/eval,/model\"\n```\n\nThis is useful for shipping a distributed agent with a narrower command surface — for example, hiding `/model` so a published agent always runs its intended model.\n\n## Built-in Commands\n\nThe TUI ships its own slash commands (`/new`, `/compact`, `/sessions`, `/settings`, …) alongside whatever an agent defines. See [Slash Commands](../../features/tui/index.md#slash-commands) in the TUI reference for the full list.\n\n## Command Configuration Reference\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `description` | string | Shown in completion dialogs and help text. |\n| `instruction` | string | The prompt sent to the agent. Supports argument expansion (`${args[0]}`, `${args.join(\" \")}`, …), tool calls (`${tool_name({...})}`), and the legacy bang syntax `!tool_name(...)`. |\n| `agent` | string | Name of an agent in the team to switch to when this command is invoked — any agent in the team's `agents:` map, not just one of the current agent's `sub_agents`. When set without `instruction`, any text typed after the slash command is forwarded as a prompt to the target agent. |\n| `url` | string | URL to open in the user's default browser when this command is invoked, instead of sending a prompt to the agent (full TUI only — see [URL Commands](#url-commands)). The token `{{session_id}}` is replaced at invocation time with the current session ID (URL-query-escaped). |\n\n`instruction` and `agent` can be combined (the agent is switched first, then the instruction is sent to the new agent). In the full TUI, if `url` is set, it takes precedence over `agent` and `instruction` — the command only opens the browser; the lean TUI and CLI don't check `url` at all, so a URL-only command instead sends its (usually empty) resolved text as a prompt. See [Behavior differs by frontend](#what-slash-commands-are) above. The simple string form is shorthand for `{ instruction: \"...\" }`.\n","_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md":"---\ntitle: \"Tool Configuration\"\ndescription: \"Complete reference for configuring built-in tools, MCP tools, and Docker-based tools.\"\nkeywords: docker agent, ai agents, configuration, yaml, tool configuration\nlinkTitle: \"Tool Config\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/tools/\naliases:\n  - /ai/docker-agent/reference/toolsets/\n---\n\n_Complete reference for configuring built-in tools, MCP tools, and Docker-based tools._\n\n## Built-in Tools\n\nBuilt-in tools are included with Docker Agent and require no external dependencies. Add them to your agent's `toolsets` list by `type`. Each tool's dedicated page covers its full configuration options, available operations, and examples.\n\n| Type | Description | Page |\n| --- | --- | --- |\n| `filesystem` | Read, write, list, search, navigate | [Filesystem](../../tools/filesystem/index.md) |\n| `git` | Read-only repository inspection (status, log, branches, show, blame) | [Git](../../tools/git/index.md) |\n| `shell` | Execute shell commands synchronously | [Shell](../../tools/shell/index.md) |\n| `background_jobs` | Run and manage long-running shell commands | [Background Jobs](../../tools/background-jobs/index.md) |\n| `scheduler` | Schedule instructions to run at a time or on a recurring interval | [Scheduler](../../tools/scheduler/index.md) |\n| `think` | Reasoning scratchpad | [Think](../../tools/think/index.md) |\n| `plan` | Shared persistent scratchpad for multi-agent collaboration | [Plan](../../tools/plan/index.md) |\n| `session_plan` | Per-session markdown plan for the draft-review-execute workflow | [Session Plan](../../tools/session_plan/index.md) |\n| `session_context` | Reference a previous session as context (read-only) | [Session Context](../../tools/session_context/index.md) |\n| `todo` | Task list management | [Todo](../../tools/todo/index.md) |\n| `memory` | Persistent key-value storage (SQLite) | [Memory](../../tools/memory/index.md) |\n| `tasks` | Persistent task database shared across sessions | [Tasks](../../tools/tasks/index.md) |\n| `fetch` | HTTP `GET` requests with text/markdown/html output | [Fetch](../../tools/fetch/index.md) |\n| `script` | Custom shell scripts as tools | [Script](../../tools/script/index.md) |\n| `lsp` | Language Server Protocol integration | [LSP](../../tools/lsp/index.md) |\n| `api` | Custom HTTP API tools | [API](../../tools/api/index.md) |\n| `openapi` | Import every operation of an OpenAPI 3.x document as tools | [OpenAPI](../../tools/openapi/index.md) |\n| `rag` | Retrieval-augmented generation over indexed sources | [RAG](../../tools/rag/index.md) |\n| `model_picker` | Let the agent pick between several models per turn | [Model Picker](../../tools/model-picker/index.md) |\n| `user_prompt` | Interactive user input | [User Prompt](../../tools/user-prompt/index.md) |\n| `open_url` | Open a fixed URL in the user's default browser | [Open URL](../../tools/open-url/index.md) |\n| `transfer_task` | Delegate to sub-agents (auto-enabled) | [Transfer Task](../../tools/transfer-task/index.md) |\n| `background_agents` | Parallel sub-agent dispatch | [Background Agents](../../tools/background-agents/index.md) |\n| `webhook` | Reliable notifications to a configured destination, with retries (Slack, Discord, Telegram, IFTTT, Teams, …) | [Webhook](../../tools/webhook/index.md) |\n| `handoff` | Local conversation handoff to another agent in the same config (auto-enabled by `handoffs:`) | [Handoff](../../tools/handoff/index.md) |\n| `a2a` | A2A remote agent connection | [A2A](../../tools/a2a/index.md) |\n| `mcp_catalog` | Discover and activate remote MCP servers from the Docker MCP Catalog on demand | [MCP Catalog](../../tools/mcp-catalog/index.md) |\n\n**Example:**\n\n```yaml\ntoolsets:\n  - type: filesystem\n  - type: shell\n  - type: background_jobs\n  - type: think\n  - type: todo\n  - type: memory\n    path: ./dev.db\n```\n\n## MCP Tools\n\nExtend agents with external tools via the [Model Context Protocol](https://modelcontextprotocol.io/). For a standalone overview of the `mcp` toolset see the [MCP tool page](../../tools/mcp/index.md).\n\n> [!TIP]\n> **Reusable MCP definitions**\n>\n> Repeated MCP server definitions can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`. See [Reusable MCP Servers](../overview/index.md#reusable-mcp-servers-mcps).\n\n### Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway):\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo # web search\n  - type: mcp\n    ref: docker:github-official # GitHub integration\n```\n\nBrowse available tools at the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`)                             |\n| `tools`       | array  | Optional: only expose these tools                                |\n| `instruction` | string | Custom instructions injected into the agent's context            |\n| `config`      | any    | MCP server-specific configuration (passed during initialization) |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n\n### Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `command` | string | Command to execute the MCP server |\n| `args` | array | Command arguments |\n| `tools` | array | Optional: only expose these tools |\n| `env` | object | Environment variables (key-value pairs) |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context |\n| `version` | string | Package reference for [auto-installing](#auto-installing-tools) the command binary |\n\n### Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp-server.example.com\"\n      transport_type: \"streamable\"\n      headers:\n        Authorization: \"Bearer your-token\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: true\n    tools: [\"search_web\", \"fetch_url\"]\n```\n\n| Property                | Type    | Description                                                                                                           |\n| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |\n| `remote.url`            | string  | URL of the MCP server. Accepts `https://`, `http://`, and `unix://` (Unix domain socket) schemes.                     |\n| `remote.transport_type` | string  | `streamable` or `sse`                                                                                                 |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. `${env.VAR}` reads an environment variable; `${headers.NAME}` forwards a header from the caller's incoming request (useful when Docker Agent runs as an API server). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers.        |\n\n## Auto-Installing Tools\n\nWhen configuring MCP or LSP tools that require a binary command, Docker Agent can **automatically download and install** the command if it's not already available on your system. This uses the [aqua registry](https://github.com/aquaproj/aqua-registry) — a curated index of CLI tool packages.\n\n### How It Works\n\n1. When a toolset with a `command` is loaded, Docker Agent checks if the command is available in your `PATH`\n2. If not found, it checks the Docker Agent tools directory (`~/.cagent/tools/bin/`)\n3. If still not found, it looks up the command in the aqua registry and installs it automatically\n\n### Explicit Package Reference\n\nUse the `version` property to specify exactly which package to install:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls\n    version: \"golang/tools@v0.21.0\"\n    args: [\"mcp\"]\n  - type: lsp\n    command: rust-analyzer\n    version: \"rust-lang/rust-analyzer@2024-01-01\"\n    file_types: [\".rs\"]\n```\n\nThe format is `owner/repo` or `owner/repo@version`. When a version is omitted, the latest release is used.\n\n### Automatic Detection\n\nIf the `version` property is not set, Docker Agent tries to auto-detect the package from the command name by searching the aqua registry:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls  # auto-detected as golang/tools\n    args: [\"mcp\"]\n```\n\n### Checksum Verification\n\nWhere the aqua registry includes a checksum manifest, downloaded binaries are verified against it before installation. Verification behaviour depends on the checksum type advertised:\n\n- **Strong checksums (sha256, sha512, etc.)** — verified before the binary is installed. If the downloaded archive does not match, the install is aborted and an error is returned (fails closed).\n- **Unsupported or weak checksum types (e.g. md5, sha1)** — skipped with a warning; installation proceeds without verification.\n- **No manifest** — if no checksum is advertised in the registry entry, the binary is installed without verification.\n\n### version_overrides Resolution\n\nThe auto-installer correctly resolves **`version_overrides`** entries in the aqua registry. Many common tools (for example, `fzf`) keep their package configuration — including download URLs and checksums — under `version_overrides` rather than at the top level of their registry entry. These tools previously failed to install silently; they are now handled correctly.\n\n### Disabling Auto-Install\n\n**Per toolset** — set `version` to `\"false\"` or `\"off\"`:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: my-custom-server\n    version: \"false\"\n```\n\n**Globally** — set the `DOCKER_AGENT_AUTO_INSTALL` environment variable:\n\n```bash\nexport DOCKER_AGENT_AUTO_INSTALL=false\n```\n\n### Environment Variables\n\n| Variable                     | Default            | Description                                      |\n| ---------------------------- | ------------------ | ------------------------------------------------ |\n| `DOCKER_AGENT_AUTO_INSTALL`  | (enabled)          | Set to `false` to disable all auto-installation  |\n| `DOCKER_AGENT_TOOLS_DIR`     | `~/.cagent/tools/` | Base directory for installed tools               |\n| `GITHUB_TOKEN`               | —                  | GitHub token to raise API rate limits (optional) |\n\nInstalled binaries are placed in `~/.cagent/tools/bin/` and cached so they are only downloaded once.\n\n> [!TIP]\n> Auto-install supports both Go packages (via `go install`) and GitHub release binaries (via archive download). The aqua registry metadata determines which method is used.\n\n## Toolset Lifecycle\n\nLong-running toolsets — local MCP servers (stdio), remote MCP servers (Streamable HTTP / SSE), and LSP servers — are managed by a single supervisor that can auto-reconnect them when they crash, time out, or drop their session. The `lifecycle` block on the toolset lets you tune that supervisor per toolset. It applies to every `type: mcp` and `type: lsp` toolset.\n\nThe simplest knob is `profile`, which picks a preset:\n\n| Profile | Auto-restart | Use case |\n| --- | --- | --- |\n| `resilient` | Yes | Default. Exponential backoff on disconnect; the agent keeps running if the toolset is unavailable. Matches the historical Docker Agent behaviour. |\n| `strict` | No | Fail-fast. Marks the toolset as required. Intended for CI / headless runs where a missing dependency should be a hard error. |\n| `best-effort` | No | Single attempt, no retries. Good for experimental MCPs whose flakiness should not amplify into a restart loop. |\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; shown here for clarity\n\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: strict\n\n  - type: mcp\n    ref: docker:openbnb-airbnb\n    lifecycle:\n      profile: best-effort\n```\n\n### Tuning the defaults\n\nAny field set on `lifecycle` overrides the profile preset, so you can mix-and-match: pick a profile and only override the knobs you care about.\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: [\"docker\", \"mcp\", \"gateway\"]\n    lifecycle:\n      profile: resilient\n      max_restarts: 10        # keep trying longer than the default of 5\n      backoff:\n        initial: 500ms\n        max: 1m\n        multiplier: 2\n        jitter: 0.2           # 20% random offset to avoid thundering-herd retries\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `profile` | string | One of `resilient` (default), `strict`, `best-effort`. Picks defaults for every other field. |\n| `restart` | string | When the supervisor should reconnect after a disconnect: `never`, `on_failure` (default), or `always`. For **remote** MCP toolsets (Streamable HTTP / SSE), `on_failure` is automatically promoted to `always` so idle-timeout closes reconnect gracefully — `never` is still honored. |\n| `max_restarts` | int | Maximum consecutive restart attempts before the toolset is marked `Failed`. `0` uses the profile default (5); `-1` means unlimited. |\n| `backoff.initial` | duration | First wait between attempts (Go duration: `500ms`, `1s`, …). Default: `1s`. |\n| `backoff.max` | duration | Cap on the wait between attempts. Default: `32s`. |\n| `backoff.multiplier` | number | Multiplier applied each attempt. Default: `2`. |\n| `backoff.jitter` | number | Fraction (0..1) of the computed delay applied as a uniform random offset. `0` disables jitter (default). |\n| `required` | boolean | Marks the toolset as critical. Today this is informational; a future eager-startup phase will refuse to start the agent when a required toolset cannot reach Ready. Defaults to `true` under `strict`, `false` otherwise. |\n| `startup_timeout` | duration | Cap on the initial connect+initialize duration. Enforced since v1.94.0: on expiry the toolset stays stopped and the runtime retries on the next turn. |\n| `call_timeout` | duration | Cap on an individual tool call, including one reconnect-retry. Enforced: on expiry the call is cancelled and surfaced to the model as a tool error; cancellation is propagated to the server. `0`/unset means no timeout — opt-in only, no profile default. |\n\n> [!NOTE]\n> **`required` is not yet enforced**\n>\n> The schema validates this field and the supervisor stores it, but no code path acts on it yet. It is documented now so config files written today keep working when the planned eager-startup phase lands. Picking the `strict` profile is forward-compatible — it will start enforcing `required=true` automatically.\n\n### Inspecting and restarting toolsets at runtime\n\nThe TUI exposes the supervisor through two slash commands:\n\n- `/tools` — the unified tools dialog. Its top section lists every toolset on the current agent with its lifecycle state (`Stopped`, `Starting`, `Ready`, `Degraded`, `Restarting`, `Failed`), restart count, and last error; its bottom section lists every tool the agent can call, grouped by category. Use this to answer both \"what can the agent do?\" and \"is anything degraded?\" with one command.\n- `/toolset-restart <name>` — force the supervisor to reconnect the named toolset. Useful after completing OAuth, when a remote MCP server has been redeployed, or when an LSP like `gopls` is stuck.\n\nSee the [TUI reference](../../features/tui/index.md) for the full list of slash commands.\n\nSee [`examples/lifecycle.yaml`](https://github.com/docker/docker-agent/blob/main/examples/lifecycle.yaml) for a complete lifecycle configuration example.\n\n## TOON-Encoded Tool Outputs\n\nMany MCP servers return verbose JSON responses that consume a lot of context budget. The `toon` field on a toolset transparently re-encodes matching tools' JSON output as [TOON](https://github.com/alpkeskin/gotoon) — a compact, model-friendly key/value format — before the result is shown to the model.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"          # toonify every tool from this MCP server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `toon`   | string | Comma-delimited list of regular expressions matching tool names whose JSON output should be re-encoded as TOON. Non-JSON outputs and non-matching tools are passed through untouched. |\n\nWhen a tool's output is not valid JSON, it is returned unchanged — TOON encoding is best-effort and never breaks tools that emit plain text.\n\n> [!NOTE]\n> **When to use TOON**\n>\n> TOON typically yields 30-60% smaller payloads than equivalent JSON for MCP tools that return arrays of records (issue lists, search results, file listings, …). It works best when the schema is regular; one-off responses with deeply nested or heterogeneous shapes may benefit less.\n\n## Per-Toolset Model Routing\n\nThe `model` field on a toolset overrides which LLM is invoked for the **next turn** after a tool from that toolset returns — letting you process simple tool results (file reads, knowledge-base lookups, shell stdout) with a cheaper or faster model while keeping the agent's primary model for reasoning.\n\n```yaml\nmodels:\n  primary:\n    provider: anthropic\n    model: claude-sonnet-4-5\n  fast:\n    provider: anthropic\n    model: claude-haiku-4-5\n\nagents:\n  root:\n    model: primary\n    toolsets:\n      - type: filesystem\n        model: fast            # process file reads with the fast model\n      - type: shell\n        model: fast            # ditto for shell stdout\n      - type: mcp\n        ref: docker:github-official\n        model: openai/gpt-4o-mini  # inline provider/model also works\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `model`  | string | Model used for the LLM turn that processes tool results from this toolset. Either a name from the `models:` section or an inline `provider/model` (e.g. `openai/gpt-4o-mini`). The override is **one-shot**: subsequent turns return to the agent's primary model. |\n\nWhen multiple tool calls in a single turn come from toolsets with different `model` overrides, the runtime picks the override of the **first** tool call that has one set. See [`examples/per_tool_model_routing.yaml`](https://github.com/docker/docker-agent/blob/main/examples/per_tool_model_routing.yaml) for a complete configuration.\n\n## Tool Filtering\n\nToolsets may expose many tools. Use the `tools` property to whitelist only the ones your agent needs. This works for any toolset type — not just MCP:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n  - type: filesystem\n    tools: [\"read_file\", \"search_files_content\"]\n  - type: shell\n    tools: [\"shell\"]\n```\n\n> [!TIP]\n> Filtering tools improves agent performance — fewer tools means less confusion for the model about which tool to use.\n\n## Tool Instructions\n\nAdd context-specific instructions that get injected when a toolset is loaded:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\nBy default, the `instruction:` field **replaces** the toolset's built-in instructions (if any). To keep the built-in guidance and add your own rules on top, include the `{ORIGINAL_INSTRUCTIONS}` placeholder anywhere in your instruction text. At runtime it expands to the toolset's default instructions:\n\n```yaml\ntoolsets:\n  # Enrich: keep built-in instructions, then add your own rules\n  - type: filesystem\n    instruction: |\n      {ORIGINAL_INSTRUCTIONS}\n\n      ## Project-specific rules\n      - Never modify files outside the `src/` directory.\n      - Always create a backup before overwriting a file.\n\n  # Enrich: prepend your rules before the built-in instructions\n  - type: shell\n    instruction: |\n      Important: only run commands inside the project root.\n      {ORIGINAL_INSTRUCTIONS}\n\n  # Replace: omit the placeholder to discard built-in instructions entirely\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Only read GitHub issues. Never create, edit, or close anything.\n```\n\nThree patterns at a glance:\n\n| Pattern | Description |\n| --- | --- |\n| `{ORIGINAL_INSTRUCTIONS}` then your text | Append your rules after the defaults |\n| Your text then `{ORIGINAL_INSTRUCTIONS}` | Prepend your rules before the defaults |\n| No placeholder | Replace the defaults entirely |\n\nSee [`examples/toolset_instructions.yaml`](https://github.com/docker/docker-agent/blob/main/examples/toolset_instructions.yaml) for a complete example.\n\n## Deferred Tool Loading\n\nLoad tools on-demand to speed up agent startup. When a toolset is deferred, its tools are registered lazily — the tool server process is not started until the agent first calls one of its tools. This is useful for large toolsets (e.g., an MCP server with hundreds of tools) where startup time matters.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  - type: mcp\n    ref: docker:slack\n    defer: true\n  - type: filesystem\n```\n\nOr defer specific tools within a toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer:\n      - \"list_issues\"\n      - \"search_repos\"\n```\n\nWhen `defer` is a list of tool names, only those specific tools are deferred; all other tools in the toolset load eagerly. Setting `defer: true` defers the entire toolset.\n\n### Tool Discovery with `search_tool`\n\nWhen an entire toolset is deferred (`defer: true`), the deferred toolset exposes two built-in tools to the agent:\n\n- **`search_tool`** — Discover available deferred tools by keyword. The search uses **fuzzy matching** against both tool names and descriptions: all characters of the query must appear in the target string in order (but not necessarily adjacently), so a query like `\"crfil\"` matches `\"create_file\"`. Returns a list of matching tool names with descriptions.\n- **`add_tool`** — Activate a discovered tool by name so it becomes available for use.\n\nThese tools let the agent browse a large toolset on-demand without activating every tool upfront.\n\nSee [`examples/deferred.yaml`](https://github.com/docker/docker-agent/blob/main/examples/deferred.yaml) for a complete example.\n\n## Combined Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Built-in tools\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n      - type: memory\n        path: ./dev.db\n      - type: user_prompt\n      # LSP for code intelligence\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      # Custom scripts\n      - type: script\n        shell:\n          run_tests:\n            description: Run the test suite\n            cmd: task test\n          lint:\n            description: Run the linter\n            cmd: task lint\n      # Custom API tool\n      - type: api\n        api_config:\n          name: get_status\n          method: GET\n          endpoint: \"https://api.example.com/status\"\n          instruction: Check service health\n      # Docker MCP tools\n      - type: mcp\n        ref: docker:github-official\n        tools: [\"list_issues\", \"create_issue\"]\n      - type: mcp\n        ref: docker:duckduckgo\n      # Remote MCP\n      - type: mcp\n        remote:\n          url: \"https://internal-api.example.com/mcp\"\n          transport_type: \"streamable\"\n          headers:\n            Authorization: \"Bearer ${env.INTERNAL_TOKEN}\"\n```\n\n> [!WARNING]\n> **Toolset Order Matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins. Order your toolsets intentionally.\n","_vendor/github.com/docker/docker-agent/docs/features/skills/index.md":"---\ntitle: \"Skills\"\ndescription: \"Skills provide specialized instructions that agents can load on demand when a task matches a skill's description.\"\nkeywords: docker agent, ai agents, features, skills\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/features/skills/\n---\n\n_Skills provide specialized instructions that agents can load on demand when a task matches a skill's description._\n\n## How Skills Work\n\n1. Docker Agent scans standard directories for `SKILL.md` files\n2. Skill metadata (name, description) is injected into the agent's system prompt\n3. When a user request matches a skill, the agent reads the full instructions\n4. The agent follows the skill's detailed instructions to complete the task\n\n## Enabling Skills\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills: true\n    toolsets:\n      - type: filesystem # required for reading skill files\n```\n\n> [!TIP]\n> Skills are perfect for encoding team-specific workflows (PR review, deployment, coding standards) that apply across projects.\n\n## Filtering Skills\n\nThe `skills` field also accepts a list, letting you restrict the agent to a specific subset of skills instead of exposing every discovered one. List items are classified automatically:\n\n- `\"local\"` or any `http://` / `https://` URL → a **source** to load skills from\n- any other string → the **name** of a skill to include\n\nWhen only names are given, local sources are used by default.\n\n```yaml\nagents:\n  # Load every discovered local skill (same as `skills: true`).\n  full:\n    skills: true\n\n  # Load local skills, but only expose \"commit\" and \"poem\".\n  scoped:\n    skills:\n      - commit\n      - poem\n\n  # Combine an explicit source with a name filter.\n  remote_filtered:\n    skills:\n      - https://skills.example.com\n      - commit\n\n  # Disable skills entirely.\n  none:\n    skills: false\n```\n\nA name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored.\n\n## Inline Skills\n\nInstead of (or alongside) loading skills from files and URLs, you can define skills directly in the agent config. An inline skill is a mapping item in the `skills` list, freely mixed with the string items above:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills:\n      - name: changelog\n        description: Write a concise changelog entry from a diff or description.\n        instructions: |\n          Produce a single changelog entry in Keep a Changelog style.\n          Pick the right category (Added, Changed, Fixed, Removed) and write\n          one imperative sentence summarising the user-visible change.\n\n      # A fork-mode inline skill runs in an isolated sub-agent.\n      - name: triage\n        description: Triage a bug report in an isolated context.\n        context: fork\n        instructions: |\n          Restate the problem, list likely root causes most-probable-first,\n          and propose the smallest reproduction and next concrete action.\n\n      # Inline skills mix freely with sources and name filters.\n      - local\n    toolsets:\n      - type: filesystem\n```\n\nInline skills carry their body in the config itself, so they need no `SKILL.md` file and require no filesystem source. They are **always exposed** — the name filter only applies to file- and URL-based skills. Because inline skills travel inside the agent YAML, they also work in `--sandbox` mode without any kit staging, and they can be shared with the agent via `share push`.\n\n### Inline Skill Fields\n\n| Field           | Required | Description                                                                |\n| --------------- | -------- | -------------------------------------------------------------------------- |\n| `name`          | Yes      | Skill identifier used by `read_skill` / `run_skill` and the `/<name>` command |\n| `description`   | Yes      | Short description shown to the agent for skill matching                    |\n| `instructions`  | Yes      | The skill body (what a `SKILL.md` would contain below its frontmatter)     |\n| `context`       | No       | Set to `fork` to run the skill as an isolated sub-agent                    |\n| `model`         | No       | Override the model used while running a fork-mode skill                    |\n| `allowed_tools` | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (glob or exact). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`      | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session on top of the inherited tools. |\n\n> [!NOTE]\n> **Inline vs. file-based skills**\n>\n> Inline skills support the subset of the SKILL.md format that fits in YAML. They cannot bundle supporting files (no `read_skill_file`) or use `` !`command` `` expansion. For skills that need bundled resources or executable helpers, use a `SKILL.md` directory instead.\n\n## SKILL.md Format\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\nlicense: Apache-2.0\nmetadata:\n  author: my-org\n  version: \"1.0\"\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Minimize image size by using slim base images\n4. Follow security best practices (non-root user, etc.)\n```\n\n### Frontmatter Fields\n\n| Field            | Required | Description                                                                 |\n| ---------------- | -------- | --------------------------------------------------------------------------- |\n| `name`           | Yes      | Unique skill identifier                                                     |\n| `description`    | Yes      | Short description shown to the agent for skill matching                     |\n| `context`        | No       | Set to `fork` to run the skill as an isolated sub-agent (see below)         |\n| `model`          | No       | Override the model used while running the skill as a sub-agent (fork only)  |\n| `allowed-tools`  | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (YAML list or comma-separated string). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`       | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session (YAML list or comma-separated string). |\n| `license`        | No       | License identifier (e.g. `Apache-2.0`)                                      |\n| `compatibility`  | No       | Free-text compatibility notes                                               |\n| `metadata`       | No       | Arbitrary key-value pairs (e.g. `author`, `version`)                        |\n\n## Running a Skill as a Sub-Agent\n\nBy default, when an agent invokes a skill it reads the instructions inline into its own conversation. For complex, multi-step skills this can consume a large portion of the agent's context window and pollute the parent conversation with intermediate tool calls.\n\nAdding `context: fork` to the SKILL.md frontmatter tells the agent to run the skill in an **isolated sub-agent** instead:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\n---\n\n# Bump Dependencies\n\n1. List outdated deps\n2. Update each one, run tests, commit or revert\n3. Produce a summary table\n```\n\nWhen the agent encounters a task that matches a `context: fork` skill, it uses the `run_skill` tool instead of `read_skill`. This:\n\n- **Spawns a child session** with the skill content as the system prompt and the caller's task as the user message\n- **Isolates the context window** — the sub-agent has its own conversation history, so lengthy tool-call chains don't eat into the parent's token budget\n- **Folds the result** — only the sub-agent's final answer is returned to the parent as the tool result\n- **Inherits the parent's model and tools** — the sub-agent can use all tools available to the parent agent (scope this with `allowed_tools` / `toolsets`, see [Scoping a fork skill's tools](#scoping-a-fork-skills-tools))\n\n> [!TIP]\n> **When to use context: fork**\n>\n> Use `context: fork` for skills that involve many steps, heavy tool usage, or that should not clutter the main conversation — for example dependency bumping, large refactors, or code generation pipelines.\n\n### Overriding the model for a fork skill\n\nFork skills can declare a `model` field in their frontmatter to use a\ndifferent model than the parent agent for the duration of the sub-session.\nThis is useful when a skill is best handled by a faster, cheaper, or more\nspecialised model — for example a powerful reasoning model for refactors,\nor a fast model for routine bookkeeping work. The override only applies\nwhile the skill is running; the parent agent keeps its own model.\n\nThe `model` value accepts either a named model from the agent config or\nan inline `provider/model` reference (and the same comma-separated alloy\nsyntax as the rest of the agent config):\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\nmodel: openai/gpt-4o-mini\n---\n\n# Bump Dependencies\n\n1. ...\n```\n\nIf the model reference cannot be resolved (unknown name, missing\ncredentials, runtime not configured for model switching, …) the skill\nfalls back to the agent's currently-active model (its configured\ndefault, or any override the user previously set via the model picker)\nand a warning is logged.\n\nWhen the skill completes, the agent's previous model is restored — but\nonly if no one else changed the model in the meantime. If the user\nswitches the model via the TUI model picker while the fork skill is\nrunning, their choice is preserved (the deferred restore becomes a\nno-op).\n\n### Scoping a fork skill's tools\n\nBy default a fork skill inherits the parent agent's entire tool set. Two\noptional fields let you scope what the sub-session can use. Both apply\n**only to fork-mode skills** and work the same whether the skill is\ninline or loaded from a `SKILL.md` file.\n\n`allowed_tools` (frontmatter: `allowed-tools`) is an **allow-list** over\nthe inherited tools: only tools whose names match an entry are kept,\neverything else is hidden from the sub-session. Entries support glob\npatterns (e.g. `read_*`) and otherwise match exactly. This is the\nClaude-Code-compatible `allowed-tools` field, now enforced for fork\nskills rather than merely recorded.\n\n`toolsets` references reusable [top-level toolsets](../../configuration/overview/index.md#reusable-toolsets-toolsets)\nby name. The referenced toolsets are exposed in the sub-session **in\naddition to** the inherited tools, and they bypass the `allowed_tools`\nfilter (the skill explicitly asked for them).\n\n```yaml\ntoolsets:\n  web:\n    type: fetch\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    toolsets:\n      - type: filesystem\n      - type: shell\n    skills:\n      # Inherits the parent tools but is restricted to read-only filesystem\n      # access while it runs — shell and write tools are hidden.\n      - name: audit\n        description: Review the repository layout without modifying anything.\n        context: fork\n        allowed_tools:\n          - read_file\n          - list_directory\n          - directory_tree\n        instructions: Inspect the repository structure and summarise it.\n\n      # Brings in the top-level `web` toolset on top of the parent's tools.\n      - name: research\n        description: Research a topic using web fetches in an isolated context.\n        context: fork\n        toolsets:\n          - web\n        instructions: Research the requested topic and summarise with links.\n```\n\nThe equivalent in a `SKILL.md` file uses frontmatter lists:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: research\ndescription: Research a topic using web fetches\ncontext: fork\nallowed-tools:\n  - fetch\ntoolsets:\n  - web\n---\n```\n\n> [!NOTE]\n> **Fork only**\n>\n> Both fields are rejected by config validation when set on a non-fork skill, and a `toolsets` entry that doesn't resolve to a top-level toolset is a load-time error.\n\n## Search Paths\n\nSkills are discovered from these locations (later overrides earlier):\n\n### Global\n\n| Path                | Search Type                             |\n| ------------------- | --------------------------------------- |\n| `~/.codex/skills/`  | Recursive (searches all subdirectories) |\n| `~/.claude/skills/` | Flat (immediate children only)          |\n| `~/.agents/skills/` | Recursive (searches all subdirectories) |\n\n### Project (from git root to current directory)\n\n| Path              | Search Type                                |\n| ----------------- | ------------------------------------------ |\n| `.claude/skills/` | Flat (cwd only)                            |\n| `.github/skills/` | Flat (each directory from git root to cwd) |\n| `.agents/skills/` | Flat (each directory from git root to cwd) |\n\n## Invoking Skills\n\nSkills can be invoked in multiple ways:\n\n- **Automatic:** The agent detects when your request matches a skill's description and loads it automatically\n- **Explicit:** Reference the skill name in your prompt: \"Use the create-dockerfile skill to...\"\n- **Slash command:** Use `/{skill-name}` to invoke a skill directly\n\n```bash\n# In the TUI, invoke skill directly:\n/create-dockerfile\n\n# Or mention it in your message:\n\"Create a dockerfile for my Python app (use the create-dockerfile skill)\"\n```\n\n## Precedence\n\nWhen multiple skills share the same name:\n\n1. Global skills load first\n2. Project skills load next, from git root toward current directory\n3. Skills closer to the current directory override those further away\n4. At the same directory level, `.agents/skills/` overrides `.github/skills/`\n\n## Skills in Sandbox Mode\n\nWhen you run an agent with [`--sandbox`](../../configuration/sandbox/index.md), the sandbox VM has its own filesystem with no access to your host's skill directories. Docker Agent handles this transparently via the [auto-kit](../../configuration/sandbox/index.md#auto-kit): every discovered local skill is staged into a per-agent kit on the host, run through best-effort secret redaction (see the [auto-kit](../../configuration/sandbox/index.md#secret-redaction) docs), and bind-mounted read-only into the sandbox so the agent sees the same skills inside the VM as on the host. No configuration is required — use `--no-kit` only if you explicitly want to run the sandbox without any host skills.\n\n## Creating a Skill\n\n```bash\n# Create the skill directory\n$ mkdir -p ~/.agents/skills/create-dockerfile\n\n# Write the SKILL.md file\n$ cat > ~/.agents/skills/create-dockerfile/SKILL.md << 'EOF'\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Use slim base images to minimize size\n4. Run as non-root user for security\nEOF\n```\n\nThe skill will automatically be available to any agent with skills enabled (`skills: true`, or a list that targets its name — see [Filtering Skills](#filtering-skills)).\n\n> [!NOTE]\n> **See also**\n>\n> Skills are enabled in the [Agent Config](../../configuration/agents/index.md) with the `skills` property (boolean or list). For tool-based capabilities, see [Tools](../../concepts/tools/index.md).\n>\n> Example configs: [`examples/skills_inline.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_inline.yaml) (inline skill definition), [`examples/skills_fork_toolsets.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_fork_toolsets.yaml) (scoping a fork skill's tools), [`examples/skills_filter.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_filter.yaml) (filtering which skills load).\n","_vendor/github.com/docker/docker-agent/docs/tools/_index.md":"---\ntitle: \"Built-in Tools\"\ndescription: \"Built-in toolsets agents can use out of the box.\"\nweight: 40\n---\n","_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md":"---\ntitle: \"A2A Tool\"\ndescription: \"Connect to remote agents via the Agent-to-Agent protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, a2a tool\nlinkTitle: \"A2A\"\nweight: 60\ncanonical: https://docs.docker.com/ai/docker-agent/tools/a2a/\n---\n\n_Connect to remote agents via the Agent-to-Agent protocol._\n\n## Overview\n\nThe A2A tool connects to a remote agent exposed over the A2A (Agent-to-Agent) protocol. Unlike [`handoff`](../handoff/index.md), which only targets local agents declared in the same config, `a2a` reaches out to an agent running on the network.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: a2a\n    url: \"http://localhost:8080/a2a\"\n    # Optional: custom tool name (defaults to a sanitized form of the URL / agent card name)\n    name: research_agent\n    # Optional: custom HTTP headers (typically for auth)\n    headers:\n      Authorization: \"Bearer ${env.A2A_TOKEN}\"\n      X-Tenant: \"acme\"\n```\n\nThe `Authorization` header shown above authenticates to endpoints served with `docker agent serve a2a --auth-token`.\n\n## Properties\n\n| Property   | Type             | Required | Description                                                                                              |\n| ---------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `url`      | string           | ✓        | A2A server endpoint URL (must include scheme).                                                           |\n| `name`     | string           | ✗        | Tool name registered for the remote agent. Defaults to a name derived from the server's agent card.     |\n| `headers`  | map\\[string\\]string | ✗     | Extra HTTP headers sent with every request (useful for `Authorization`, tenant selection, tracing, \\u2026). |\n\n> [!TIP]\n> **See also**\n>\n> For full details on the A2A protocol and serving agents as A2A endpoints, see [A2A Protocol](../../features/a2a/index.md).\n","_vendor/github.com/docker/docker-agent/docs/tools/api/index.md":"---\ntitle: \"API Tool\"\ndescription: \"Create custom tools that call HTTP APIs.\"\nkeywords: docker agent, ai agents, tools, toolsets, api tool\nlinkTitle: \"API\"\nweight: 240\ncanonical: https://docs.docker.com/ai/docker-agent/tools/api/\n---\n\n_Create custom tools that call HTTP APIs._\n\n## Overview\n\nThe API tool type lets you define custom tools that make HTTP requests to external APIs. This is useful for integrating agents with REST APIs, webhooks, or any HTTP-based service without writing code.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Integrating with REST APIs that don't have an MCP server\n> - Simple HTTP operations (GET, POST)\n> - Quick prototyping before building a full MCP server\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant with API access\n    instruction: You can look up weather information.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_weather\n          method: GET\n          endpoint: \"https://api.weather.example/v1/current?city=${city}\"\n          instruction: Get current weather for a city\n          args:\n            city:\n              type: string\n              description: City name to get weather for\n          required: [\"city\"]\n          headers:\n            Authorization: \"Bearer ${env.WEATHER_API_KEY}\"\n```\n\n## Properties\n\nThe `api` toolset accepts the following toolset-level fields in addition to the `api_config` block:\n\n| Property            | Type    | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `api_config`        | object  | ✓        | The HTTP tool definition. See the table below.                                                                                                                                                                                                                    |\n| `timeout`           | int     | ✗        | HTTP client timeout in seconds (default: `30`). Applies to every call the generated tool makes.                                                                                                                                                                   |\n| `allow_private_ips` | boolean | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the configured endpoint legitimately targets internal services. See [Reaching internal services](#reaching-internal-services). |\n\n### `api_config`\n\n| Property        | Type   | Required | Description                                      |\n| --------------- | ------ | -------- | ------------------------------------------------ |\n| `name`          | string | ✓        | Tool name (how the agent references it)          |\n| `method`        | string | ✓        | HTTP method: `GET` or `POST`                     |\n| `endpoint`      | string | ✓        | URL endpoint (supports `${param}` interpolation) |\n| `instruction`   | string | ✗        | Description shown to the agent                   |\n| `args`          | object | ✗        | Parameter definitions (JSON Schema properties)   |\n| `required`      | array  | ✗        | List of required parameter names                 |\n| `headers`       | object | ✗        | HTTP headers to include. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request, useful when docker agent is itself exposed as an HTTP server). |\n| `output_schema` | object | ✗        | JSON Schema for the response. Used by MCP / Code Mode consumers; tool responses are still returned to the model as raw strings.                                                                                          |\n\n## HTTP Methods\n\n### GET Requests\n\nFor GET requests, parameters are interpolated into the URL:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: search_users\n      method: GET\n      endpoint: \"https://api.example.com/users?q=${query}&limit=${limit}\"\n      instruction: Search for users by name\n      args:\n        query:\n          type: string\n          description: Search query\n        limit:\n          type: integer\n          description: Maximum results (default 10)\n      required: [\"query\"]\n```\n\n### POST Requests\n\nFor POST requests, parameters are sent as JSON in the request body:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: create_task\n      method: POST\n      endpoint: \"https://api.example.com/tasks\"\n      instruction: Create a new task\n      args:\n        title:\n          type: string\n          description: Task title\n        description:\n          type: string\n          description: Task description\n        priority:\n          type: string\n          enum: [\"low\", \"medium\", \"high\"]\n          description: Task priority\n      required: [\"title\"]\n      headers:\n        Content-Type: \"application/json\"\n        Authorization: \"Bearer ${env.API_TOKEN}\"\n```\n\n## URL Interpolation\n\nUse `${param}` syntax to insert parameter values into URLs:\n\n```yaml\nendpoint: \"https://api.example.com/users/${user_id}/posts/${post_id}\"\n```\n\nParameter values are inserted as strings by the template expansion. Add URL encoding in the template when needed (for example, `${encodeURIComponent(city)}`).\n\n## Headers\n\nHeaders can include environment variables:\n\n```yaml\nheaders:\n  Authorization: \"Bearer ${env.API_KEY}\"\n  X-Custom-Header: \"static-value\"\n  Content-Type: \"application/json\"\n```\n\n## Output Schema\n\nOptionally document the expected response format:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: get_user\n      method: GET\n      endpoint: \"https://api.example.com/users/${id}\"\n      instruction: Get user details by ID\n      args:\n        id:\n          type: string\n          description: User ID\n      required: [\"id\"]\n      output_schema:\n        type: object\n        properties:\n          id:\n            type: string\n          name:\n            type: string\n          email:\n            type: string\n          created_at:\n            type: string\n```\n\n## Example: GitHub API\n\n```yaml\nagents:\n  github_assistant:\n    model: openai/gpt-4o\n    description: Assistant that can query GitHub\n    instruction: You can look up GitHub repositories and users.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_repo\n          method: GET\n          endpoint: \"https://api.github.com/repos/${owner}/${repo}\"\n          instruction: Get information about a GitHub repository\n          args:\n            owner:\n              type: string\n              description: Repository owner (user or org)\n            repo:\n              type: string\n              description: Repository name\n          required: [\"owner\", \"repo\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n            Authorization: \"Bearer ${env.GITHUB_TOKEN}\"\n\n      - type: api\n        api_config:\n          name: get_user\n          method: GET\n          endpoint: \"https://api.github.com/users/${username}\"\n          instruction: Get information about a GitHub user\n          args:\n            username:\n              type: string\n              description: GitHub username\n          required: [\"username\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n```\n\n## Limitations\n\n- Only supports GET and POST methods\n- Response body is limited to 1MB\n- Default 30-second timeout per request (override with the `timeout` field)\n- Only HTTP and HTTPS URLs are supported\n- No support for file uploads or multipart forms\n- By default, requests to non-public IP ranges (loopback, RFC1918, link-local, the cloud-metadata endpoint, multicast, the unspecified address) are refused at dial time — even when DNS for an otherwise-public host resolves there. Set `allow_private_ips: true` to disable that check.\n\n## Reaching internal services\n\n```yaml\ntoolsets:\n  - type: api\n    timeout: 60\n    allow_private_ips: true\n    api_config:\n      name: get_local_status\n      method: GET\n      endpoint: \"http://localhost:8080/health\"\n      instruction: Check the local service health\n```\n\n> [!WARNING]\n> **SSRF**\n>\n> Setting `allow_private_ips: true` re-exposes the SSRF surface for this tool. Only enable it when the configured `endpoint` is a trusted internal service — a prompt-injected agent cannot redirect the call elsewhere because the endpoint is fixed in config, but redirects from the configured host can still reach unexpected places.\n\n> [!TIP]\n> **For Complex APIs**\n>\n> For APIs that need authentication flows, pagination, or complex request/response handling, consider using an MCP server instead. The API tool is best for simple, stateless HTTP operations.\n\n> [!WARNING]\n> **Security**\n>\n> API keys and tokens in headers are visible in debug logs. Use environment variables (`${env.VAR}`) rather than hardcoding secrets in configuration files.\n","_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md":"---\ntitle: \"Background Agents Tool\"\ndescription: \"Dispatch work to sub-agents concurrently and collect results asynchronously.\"\nkeywords: docker agent, ai agents, tools, toolsets, background agents tool\nlinkTitle: \"Background Agents\"\nweight: 90\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-agents/\n---\n\n_Dispatch work to sub-agents concurrently and collect results asynchronously._\n\n## Overview\n\nThe background agents tool lets an orchestrator dispatch work to sub-agents concurrently and collect results asynchronously. Unlike [transfer_task](../transfer-task/index.md) (which blocks until the sub-agent finishes), background agent tasks run in parallel — the orchestrator can start several tasks, do other work, and check on them later.\n\n## Available Tools\n\n| Tool                     | Description                                                     |\n| ------------------------ | --------------------------------------------------------------- |\n| `run_background_agent`   | Start a sub-agent task in the background; returns a task ID     |\n| `list_background_agents` | List all background tasks with their status and runtime         |\n| `view_background_agent`  | View live output or final result of a task by ID                |\n| `stop_background_agent`  | Cancel a running task by ID                                     |\n\n### `run_background_agent` parameters\n\n| Parameter         | Type   | Required | Description                                                                 |\n| ----------------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to run. Must be listed under the caller's `sub_agents`. |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.        |\n| `expected_output` | string | ✗        | Optional description of the result format the caller expects.               |\n\n`run_background_agent` returns a **task ID** string. Tools run by the sub-agent inherit the parent session's permissions. Because background tasks run non-interactively, any tool call that would normally prompt the user for approval will be automatically denied. To allow background agents to run mutating tools, you must explicitly approve them in the parent session (e.g. via YOLO mode or explicit allow rules).\n\nBackground delegation shares the same runtime guards as `transfer_task`: delegation cycles are rejected and chains are capped at 10 nested delegations. See [Delegation Limits](../transfer-task/index.md#delegation-limits).\n\n### `view_background_agent` and `stop_background_agent` parameters\n\n| Parameter | Type   | Required | Description                                                    |\n| --------- | ------ | -------- | -------------------------------------------------------------- |\n| `task_id` | string | ✓        | Task ID returned by `run_background_agent` or `list_background_agents`. |\n\n`list_background_agents` takes no parameters.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: background_agents\n```\n\nNo configuration options. Requires the agent to have `sub_agents` configured so the background tasks have agents to dispatch to.\n\n## Example\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Orchestrates parallel research\n    instruction: Fan out research tasks and synthesize results.\n    sub_agents: [researcher]\n    toolsets:\n      - type: background_agents\n      - type: think\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Research topics thoroughly.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\n> [!TIP]\n> **When to Use**\n>\n> Use `background_agents` when your orchestrator needs to fan out work to multiple specialists in parallel — for example, researching several topics simultaneously or running independent code analyses side by side.\n\nIn the TUI, each background task's token usage is accounted for live: the sidebar's Agents panel shows the sub-agent's context usage percentage on its roster row, the Agent Inspector shows its exact token counts, and the task's cost joins the session total.\n\n## Using Harness Sub-Agents\n\nBackground agents work equally well with [harness-backed sub-agents](../../features/harnesses/index.md) — sub-agents driven by external coding CLIs such as Claude Code or Codex. This lets you dispatch multiple independent coding tasks in parallel:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Orchestrator that fans out coding tasks\n    instruction: |\n      Dispatch the frontend and backend tasks in parallel,\n      then collect results and produce a summary.\n    sub_agents:\n      - claude-coder\n      - codex-coder\n    toolsets:\n      - type: background_agents\n\n  claude-coder:\n    description: Frontend specialist (Claude Code)\n    harness:\n      type: claude-code\n      effort: medium\n\n  codex-coder:\n    description: Backend specialist (Codex)\n    harness:\n      type: codex\n```\n\nThe orchestrator calls `run_background_agent` for each coding task, then uses `list_background_agents` and `view_background_agent` to collect results when they finish.\n\n> [!NOTE]\n> **Harness toolsets are ignored**\n>\n> Harness agents use the external CLI's own tools — any `toolsets:` configured on the harness agent are silently ignored. See [Coding Harnesses](../../features/harnesses/index.md) for details and caveats.\n\nSee [`examples/coding_harness_background_agents.yaml`](https://github.com/docker/docker-agent/blob/main/examples/coding_harness_background_agents.yaml) for a complete configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md":"---\ntitle: \"Background Jobs Tool\"\ndescription: \"Run and manage long-running shell commands.\"\nkeywords: docker agent, ai agents, tools, toolsets, background jobs, shell\nlinkTitle: \"Background Jobs\"\nweight: 21\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-jobs/\n---\n\n_Run and manage long-running shell commands._\n\n## Overview\n\nThe `background_jobs` toolset starts shell commands that should keep running while the agent continues with other work, such as local servers, file watchers, long builds, or test suites. It returns a job ID immediately, captures combined stdout/stderr up to 10 MB per job, and terminates all running jobs when the agent session ends.\n\nUse the [`shell`](../shell/index.md) toolset for short synchronous commands. Add both toolsets when an agent needs both synchronous commands and long-running processes.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n  - type: background_jobs\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                                                                          |\n| -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `env`    | object  | Environment variables to set for all background job commands.                                                                                        |\n| `recall` | boolean | Let `run_background_job` expose a `recall` parameter so jobs can steer the agent when they finish (see [Background job recall](#background-job-recall)). Default `false`. |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Background job recall\n\nSet `recall: true` to let the `run_background_job` tool expose a `recall` boolean parameter:\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    recall: true\n```\n\nWhen the agent starts a background job with `recall: true`, Docker Agent sends a steering message back into the running agent loop after the job finishes. The message contains a short completion sentence and the job output, so the agent can react without polling `view_background_job`.\n\nUse recall for finite background work where completion matters (for example, a long build or test suite). Avoid it for servers and watchers that are expected to run until stopped. See [`examples/shell_recall.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shell_recall.yaml) for a complete configuration.\n\n## Available Tools\n\nThe background jobs toolset exposes five tools:\n\n| Tool Name              | Description                                                                                    |\n| ---------------------- | ---------------------------------------------------------------------------------------------- |\n| `run_background_job`   | Start a command asynchronously and return a job ID immediately. Use for servers/watchers/etc. |\n| `list_background_jobs` | List all background jobs with their status, runtime, and metadata.                             |\n| `view_background_job`  | View the buffered output and status of a specific background job by ID.                        |\n| `stop_background_job`  | Stop a running background job. Child processes are terminated too.                             |\n| `wait_background_job`  | Block until a job finishes and return its exit code and output. Safe on already-finished jobs. |\n\n### `run_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                                                 |\n| --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute in the background.                                                                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                                                                                     |\n| `recall`  | boolean | ✗        | Only available when the `background_jobs` toolset has `recall: true`. When true, send a steering message with the job output when it finishes. |\n\n`view_background_job` and `stop_background_job` each take a single required `job_id` string returned by `run_background_job` or `list_background_jobs`.\n\n### `wait_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                    |\n| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------- |\n| `job_id`  | string  | ✓        | Job ID returned by `run_background_job` or `list_background_jobs`.                                             |\n| `timeout` | integer | ✗        | Maximum seconds to wait (default: `60`). If the job is still running when the limit fires, the tool returns the current output with a notice and the job continues in the background. |\n\n> [!WARNING]\n> **Safety**\n>\n> Background jobs run shell commands with the same access as the agent process. Stop servers and watchers when they are no longer needed, and use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n","_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md":"---\ntitle: \"Fetch Tool\"\ndescription: \"Read content from HTTP/HTTPS URLs.\"\nkeywords: docker agent, ai agents, tools, toolsets, fetch tool\nlinkTitle: \"Fetch\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/tools/fetch/\n---\n\n_Read content from HTTP/HTTPS URLs._\n\n## Overview\n\nThe fetch tool lets agents retrieve content from one or more HTTP/HTTPS URLs. It is **read-only** — only `GET` requests are supported. The tool respects `robots.txt`, limits response size (1 MB per URL), and can return content as plain text, Markdown (converted from HTML), or raw HTML.\n\n> [!NOTE]\n> **GET only**\n>\n> The fetch tool does **not** support `POST`, `PUT`, `DELETE` or other methods, and does not expose request bodies or per-call custom headers (the toolset can still attach static [credential headers](#custom-headers) to every request). To call REST endpoints with other verbs, use the [API tool](../api/index.md) or an [OpenAPI toolset](../openapi/index.md).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: fetch\n```\n\n### Options\n\n| Property            | Type          | Default | Description                                                                                                                                                                                                                                                                                                      |\n| ------------------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `timeout`           | int           | `30`    | Default request timeout in seconds (overridable per tool call).                                                                                                                                                                                                                                                  |\n| `allowed_domains`   | array[string] | _none_  | Allow-list of hosts the tool may fetch. When set, every URL whose host is **not** in the list is rejected before any network call is made. Mutually exclusive with `blocked_domains`.                                                                                                                            |\n| `blocked_domains`   | array[string] | _none_  | Deny-list of hosts the tool must not fetch. URLs whose host matches one of these patterns are rejected before any network call (including `robots.txt`) is made. Mutually exclusive with `allowed_domains`.                                                                                                      |\n| `allow_private_ips` | boolean       | `false` | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast, and the unspecified address). Required to reach `localhost` / internal services. See [SSRF protection](#ssrf-protection-and-reaching-localhost) below. |\n| `headers`           | map[string]string | _none_ | Static HTTP headers attached to **every** request the toolset issues (including `robots.txt`). Values support `${env.VAR}` for secrets. Caller-supplied entries override the default `User-Agent` and the format-driven `Accept` header. Headers are stripped on cross-host redirects so credentials never leak to a third-party host. See [Custom headers](#custom-headers) below. |\n\n### Domain matching\n\nDomain patterns in `allowed_domains` and `blocked_domains` use the following rules (case-insensitive):\n\n- **Bare domain** — `example.com` matches the host `example.com` _and_ any subdomain such as `docs.example.com`. It does **not** match unrelated hosts that share a suffix (e.g. `badexample.com`).\n- **Leading dot** — `.example.com` matches **only** strict subdomains (`docs.example.com`, `a.b.example.com`), not the apex `example.com`.\n- **Wildcard glob** — `*.example.com` is an alias for the leading-dot form; the apex is excluded. The `*` is only valid as a leading `*.` token (entries like `foo.*`, `*.*.example.com`, or a bare `*` are rejected at config-load time).\n- **IP literal** — IP addresses are matched exactly (`169.254.169.254`).\n- **CIDR range** — `169.254.0.0/16`, `10.0.0.0/8`, `::1/128`, `fc00::/7`. Matches when the URL's host parses as an IP inside the network. Hostname hosts never match a CIDR pattern. Malformed CIDRs are rejected at config-load time.\n- **Trailing dots** in FQDN-form URLs (`http://example.com./`) are stripped before matching, so they cannot bypass a deny-list entry.\n\nThe lists are mutually exclusive: a single fetch toolset may set either `allowed_domains` or `blocked_domains`, but not both.\n\nWhen a list is configured, every redirect target is re-checked against the same list. A request to an allowed origin that redirects to a forbidden host is rejected before any data is read from the redirect.\n\n> [!WARNING]\n> **Limitations**\n>\n> Matching is purely string-based on the URL host. It does **not** perform DNS resolution and does **not** normalise alternative IP encodings (decimal `2852039166`, hex `0xa9.0xfe.0xa9.0xfe`, octal, etc. IPv4-mapped IPv6 addresses ARE normalized to their IPv4 form). If you need to deny access to a specific IP, also list its alternative encodings, or block at the network layer.\n\n### Custom Timeout\n\n```yaml\ntoolsets:\n  - type: fetch\n    timeout: 60\n```\n\n### Custom headers\n\nAttach static headers — typically credentials — to every request. Values support `${env.VAR}` interpolation so secrets stay out of YAML, and headers are dropped on cross-host redirects so a redirect chain cannot leak them to a third-party host:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docs.internal.example.com\n    headers:\n      Authorization: \"Bearer ${env.INTERNAL_DOCS_TOKEN}\"\n      X-Internal-Client: \"docker-agent\"\n```\n\n> [!WARNING]\n> **Pair credential headers with an allow-list**\n>\n> When `headers` carries credentials (e.g. `Authorization`), set `allowed_domains` to the specific hosts that should receive them. Stdlib already strips a small allow-list (`Authorization`, `Cookie`, `WWW-Authenticate`) on cross-domain redirects, and the fetch tool additionally strips every operator-supplied header on cross-host redirects — but an allow-list is the strongest guarantee against accidental exfiltration.\n\n### Restrict to specific domains\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docker.com          # docker.com and *.docker.com\n      - github.com          # github.com and *.github.com\n      - .githubusercontent.com  # only subdomains, e.g. raw.githubusercontent.com\n```\n\n### Block sensitive hosts\n\n```yaml\ntoolsets:\n  - type: fetch\n    blocked_domains:\n      - 169.254.169.254       # cloud metadata endpoint (literal IP)\n      - 169.254.0.0/16        # entire link-local range (CIDR)\n      - 10.0.0.0/8            # RFC1918 private range\n      - \"*.internal.example.com\"  # any subdomain (wildcard)\n      - internal.example.com  # internal corporate hostname\n```\n\n> [!NOTE]\n> **Already blocked by default**\n>\n> You do **not** need to add loopback, RFC1918, link-local (incl. `169.254.169.254`), multicast or the unspecified address to `blocked_domains` to be safe — the fetch tool already refuses connections to those ranges at dial time, after DNS resolution. The example above is only useful if you also want to reject those hosts _before_ any network call (and to surface a clearer error message to the agent), or if you have set `allow_private_ips: true` and want to deny a specific subset.\n\n### SSRF protection and reaching localhost\n\nBy default, the fetch tool refuses connections to **non-public IP addresses** — even when DNS for an otherwise-public host resolves to one of them (so DNS rebinding is also blocked). The check happens at dial time, after DNS resolution, and rejects:\n\n- **Loopback** — `127.0.0.0/8`, `::1` (this is what blocks `http://localhost/...` and `http://127.0.0.1/...`)\n- **RFC1918 private ranges** — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n- **Link-local** — `169.254.0.0/16` (IPv4, including the cloud-metadata endpoint `169.254.169.254`) and `fe80::/10` (IPv6)\n- **Multicast** and the **unspecified** address (`0.0.0.0`, `::`)\n- **IPv4-mapped IPv6** — addresses like `::ffff:127.0.0.1` or `::ffff:169.254.169.254` are normalized to their IPv4 form and blocked accordingly\n\nThis is the default because LLM-driven fetches are a classic Server-Side Request Forgery (SSRF) vector: a prompt-injected URL can otherwise reach internal services, cloud metadata, or admin interfaces on the host running the agent.\n\nIf an agent legitimately needs to call **localhost** or an **internal service**, opt in with `allow_private_ips: true`:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allow_private_ips: true\n    allowed_domains:\n      - localhost\n      - 127.0.0.1\n      - 10.0.0.0/8            # internal corporate range\n```\n\n> [!WARNING]\n> **Pair with an allow-list**\n>\n> Setting `allow_private_ips: true` alone re-exposes the SSRF surface. We strongly recommend combining it with an `allowed_domains` entry that restricts the tool to the specific internal hosts or CIDRs the agent actually needs (e.g. `localhost`, `127.0.0.1`, or your internal CIDR).\n>\n> **Note:** `allowed_domains` is checked _before_ DNS resolution (string-based on hostname), while the SSRF check happens _after_ DNS resolution (on the resolved IP). This means `allowed_domains` and `blocked_domains` are evaluated independently of `allow_private_ips` and continue to apply. A public hostname in `allowed_domains` that resolves to a private IP will still be blocked unless `allow_private_ips: true` is set.\n\n## Tool Interface\n\nThe toolset exposes a single tool, `fetch`, with the following parameters:\n\n| Parameter | Type           | Required | Description                                                                                                 |\n| --------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------- |\n| `urls`    | array[string]  | ✓        | One or more HTTP/HTTPS URLs to fetch (all via `GET`).                                                       |\n| `format`  | string         | ✓        | Output format: `text`, `markdown`, or `html`. HTML responses are converted to text/markdown when requested. |\n| `timeout` | integer        | ✗        | Per-call request timeout in seconds. Overrides the toolset default. Valid range: `1`–`300`.                 |\n\nResponses are capped at **1 MB** per URL. Hosts that disallow the agent's user-agent via `robots.txt` are skipped with a clear error.\n\n> [!TIP]\n> **Fetch vs. API Tool**\n>\n> Use `fetch` when the agent needs to read arbitrary public URLs at runtime. Use the [API tool](../api/index.md) to expose specific, structured HTTP endpoints (including non-`GET` verbs) as named tools.\n\n## Domain Filtering\n\nThe `allowed_domains`, `blocked_domains`, and `allow_private_ips` options let you control which hosts the fetch tool may reach. The complete reference is in the [Options](#options) table and [Domain matching](#domain-matching) section above.\n\n**Key points:**\n\n- `allowed_domains` — allow-list; only listed hosts (and their subdomains for bare-domain entries) are reachable\n- `blocked_domains` — deny-list; mutually exclusive with `allowed_domains` (a config error is thrown if both are set)\n- `allow_private_ips` — defaults to `false`; set to `true` to reach loopback / RFC-1918 / link-local addresses\n- The same `allow_private_ips` flag is also supported on `api`, `openapi`, `a2a`, and remote `mcp` toolsets\n\nSee [`examples/fetch_domain_filtering.yaml`](https://github.com/docker/docker-agent/blob/main/examples/fetch_domain_filtering.yaml) for a complete filtering example, and [`examples/remote_mcp_allow_private_ips.yaml`](https://github.com/docker/docker-agent/blob/main/examples/remote_mcp_allow_private_ips.yaml) for the equivalent pattern on remote MCP toolsets.\n","_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md":"---\ntitle: \"Filesystem Tool\"\ndescription: \"Read, write, list, search, and navigate files and directories.\"\nkeywords: docker agent, ai agents, tools, toolsets, filesystem tool\nlinkTitle: \"Filesystem\"\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/tools/filesystem/\n---\n\n_Read, write, list, search, and navigate files and directories._\n\n## Overview\n\nThe filesystem tool gives agents the ability to explore codebases, read and edit files, create new files, search across files, and navigate directory structures.\n\n### Path resolution\n\nPaths are resolved relative to the **working directory** (the directory where the agent session started, or the directory specified with `--workdir`):\n\n- **Relative paths** (e.g., `src/main.go`, `../README.md`) are joined with the working directory.\n- **Absolute paths** must match the host operating system:\n  - Unix/Linux/macOS: `/home/user/project/file.txt`\n  - Windows: `C:\\Users\\user\\project\\file.txt` or `C:/Users/user/project/file.txt`\n- **Home directory expansion**: paths starting with `~` or `~/` expand to the user's home directory.\n\nWhen a file is not found, error messages include the resolved absolute path to help diagnose incorrect base directories or path formats.\n\n> [!IMPORTANT]\n> Agents must use paths appropriate for the host OS. A Windows absolute path like `C:\\file.txt` on a Unix system (or vice versa) is rejected with a clear error message.\n\n### Empty directory detection\n\nWhen `list_directory` encounters an empty directory or a directory where all entries are hidden by ignore patterns (e.g., only a `.git` folder when `ignore_vcs: true`), it explicitly reports the state:\n\n- **Empty directory**: \"Directory is empty: /path/to/dir\"\n- **All entries ignored**: \"Directory has no visible entries (N hidden by ignore patterns): /path/to/dir\"\n\nThis helps agents distinguish between an empty directory and a tool failure, avoiding unnecessary retries with shell commands.\n\n## Available Tools\n\n| Tool                   | Description                                                               |\n| ---------------------- | ------------------------------------------------------------------------- |\n| `read_file`            | Read the contents of a file (whole file, or a line range of a text file)  |\n| `read_multiple_files`  | Read several files in one call (more efficient than multiple `read_file`) |\n| `write_file`           | Create or overwrite a file with new content                               |\n| `edit_file`            | Make line-based edits (find-and-replace) in an existing file. Each edit must specify a non-empty `oldText` to match and replace; empty `oldText` values are rejected with an error. |\n| `list_directory`       | List files and directories at a given path (explicitly reports empty directories) |\n| `directory_tree`       | Recursive tree view of a directory                                        |\n| `create_directory`     | Create a new directory (creates parent directories as needed)             |\n| `remove_directory`     | Remove an empty directory                                                 |\n| `search_files_content` | Search for text or regex patterns across files                            |\n\n## edit_file Validation\n\nThe `edit_file` tool applies a sequence of find-and-replace edits to a file in memory, then writes the result back atomically. Each edit must provide a non-empty `oldText` value:\n\n- **Valid**: `{\"oldText\": \"line one\", \"newText\": \"LINE ONE\"}`\n- **Invalid**: `{\"oldText\": \"\", \"newText\": \"INJECTED\"}` — rejected with error\n\nAn empty `oldText` is never a meaningful edit: Go's `strings.Contains(s, \"\")` is always `true`, and `strings.Replace(s, \"\", new, 1)` silently inserts at offset 0. Without validation, this would prepend content to the file while still reporting success. The tool now returns an explicit error (\"oldText must not be empty\") when an edit has an empty `oldText`, and no changes are written to disk.\n\nWhen a sequence contains multiple edits and a later one is rejected, the entire operation fails and the file is left untouched — edits are applied in memory and only written once at the end, so partial application is not possible.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: filesystem\n```\n\n### Options\n\n| Property | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ignore_vcs` | boolean | `true` | When `true` (default), `.git` directories and `.gitignore` patterns are excluded from listings and searches. Set to `false` to include them. |\n| `post_edit` | array | `[]` | Commands to run after editing files matching a path pattern |\n| `post_edit[].path` | string | — | Glob pattern for files (e.g., `*.go`, `src/*/*.ts`) |\n| `post_edit[].cmd` | string | — | Command to run (use `${file}` for the edited file path) |\n| `allow_list` | array | `[]` | Directories the tools may access. Empty = unrestricted (default). |\n| `deny_list` | array | `[]` | Directories the tools must not access. Takes precedence over `allow_list`. |\n\n### Path access control\n\nBy default the filesystem tools are unrestricted: relative paths resolve\nfrom the working directory, but absolute paths and `..` traversals can\nreach anywhere the agent process can. Configure `allow_list` and/or\n`deny_list` to sandbox the toolset.\n\nEntries in either list are expanded as follows:\n\n- `\".\"` — the agent's working directory\n- `\"~\"` or `\"~/...\"` — the user's home directory\n- `\"$VAR\"` / `\"${VAR}\"` / `\"${env.VAR}\"` — environment variable expansion\n- absolute paths — used as-is\n- relative paths — anchored at the working directory\n\nSymlinks are resolved before the containment check, so a symlink inside an\nallowed root cannot be used to escape it. When an `allow_list` is set,\neach entry is opened as a Go [`*os.Root`](https://pkg.go.dev/os#Root) so\nthat the kernel's rooted-lookup semantics also reject `..` and symlink\nescapes at I/O time, not just at resolve time.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    # Restrict every operation to the working directory and the user's\n    # home folder, then carve credentials out of the home folder.\n    allow_list:\n      - \".\"\n      - \"~\"\n    deny_list:\n      - \"~/.ssh\"\n      - \"~/.aws\"\n```\n\nWhen the path supplied by the agent is rejected, the tool returns a\nstructured error rather than performing any filesystem I/O. This makes the\nrestriction visible to the model so it can adjust its plan.\n\n### Post-Edit Hooks\n\nAutomatically run formatting, linting, or other commands after the agent edits a file. The command fires once per file after each edit operation (`write_file` and `edit_file`). Use `${file}` as a placeholder for the absolute path of the edited file.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    ignore_vcs: false\n    post_edit:\n      - path: \"*.go\"\n        cmd: \"gofmt -w ${file}\"\n      - path: \"*.ts\"\n        cmd: \"prettier --write ${file}\"\n      - path: \"src/*/*.py\"\n        cmd: \"black ${file}\"\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `path` | string | Glob pattern matched against the file path. `*.go` matches any `.go` file; `src/*/*.ts` matches `.ts` files inside `src/`. |\n| `cmd` | string | Shell command to run. `${file}` expands to the absolute path of the just-edited file. |\n\nPost-edit commands run with the same working directory as the agent. If a command exits non-zero, the error is logged and surfaced to the model as a warning, but the edit is not rolled back.\n\nSee [`examples/post_edit.yaml`](https://github.com/docker/docker-agent/blob/main/examples/post_edit.yaml) for a complete example.\n","_vendor/github.com/docker/docker-agent/docs/tools/git/index.md":"---\ntitle: \"Git Tool\"\ndescription: \"Read-only inspection of the working git repository.\"\nkeywords: docker agent, ai agents, tools, toolsets, git tool\nlinkTitle: \"Git\"\nweight: 125\ncanonical: https://docs.docker.com/ai/docker-agent/tools/git/\n---\n\n_Read-only inspection of the working git repository._\n\n## Overview\n\nThe git toolset gives an agent structured, **read-only** access to the working repository — status, history, branches, a commit's changes, and line-level authorship. It is implemented with go-git, so it needs **no `git` binary**.\n\nCompared with running `git` through the `shell` tool, the git toolset returns clean, structured output the model can read reliably, is **safe by construction** (no command can modify the repository), and works even when `shell` is disabled or no `git` binary is installed.\n\n> [!NOTE]\n> The git toolset is read-only. To stage, commit, or check out, use the [`shell`](../shell/index.md) tool.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: git\n```\n\nNo configuration options. The repository is opened at the agent's working directory; a subdirectory still resolves to the repository root.\n\n> [!WARNING]\n> **The repository is discovered by walking up parent directories.** If the working\n> directory is not itself a repository but an ancestor is (for example a\n> home directory tracked as dotfiles), the toolset resolves to that ancestor and\n> `git_show` / `git_blame` can expose its full history and file contents. The\n> filesystem toolset's allow/deny lists do **not** apply here. Only enable this\n> toolset where the surrounding repository is safe to read.\n\n> [!NOTE]\n> **Performance.** go-git is pure Go, which costs speed on large repositories:\n> `git_status` rehashes the whole worktree, and `git_blame` scales with history\n> depth times file size — its 400-line output cap is applied *after* the full\n> computation, so it does not make blaming a large file cheaper.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `git_status` | Current branch and changed files (staged / unstaged / untracked). |\n| `git_log` | Recent commits (hash, date, author, subject). |\n| `git_branches` | Local branches, current one marked with `*`. |\n| `git_show` | A commit's metadata, message, and changed files with +/- counts. |\n| `git_blame` | Line-by-line authorship for a file. |\n\n### `git_log`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `limit` | No | Maximum number of commits to return (default 20). |\n| `path` | No | Only show commits that touch this path. |\n\n### `git_show`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `ref` | No | Commit hash or revision to show (default HEAD). |\n\n### `git_blame`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `path` | Yes | File path to blame, relative to the repository root. |\n| `rev` | No | Commit or revision to blame at (default HEAD). |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A code review assistant\n    instruction: |\n      Review the working changes: check git_status, then git_show the latest\n      commit, and summarize what changed.\n    toolsets:\n      - type: git\n      - type: filesystem\n```\n\nExample `git_status` output:\n\n```text\nOn branch master\n1 changed file(s) [XY = staged/worktree; M=modified A=added D=deleted R=renamed ?=untracked]:\n   M main.go\n```\n\n> [!TIP]\n> **When to use**\n>\n> Use the git toolset whenever the agent needs repository context — before editing, to review recent history, or to find who last touched a line — without exposing the writable `shell` surface.\n","_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md":"---\ntitle: \"Handoff Tool\"\ndescription: \"Hand off the active conversation to another local agent defined in the same config.\"\nkeywords: docker agent, ai agents, tools, toolsets, handoff tool\nlinkTitle: \"Handoff\"\nweight: 70\ncanonical: https://docs.docker.com/ai/docker-agent/tools/handoff/\n---\n\n_Hand off the active conversation to another local agent defined in the same config._\n\n## Overview\n\nThe `handoff` tool lets an agent transfer control of the **current conversation** to another agent in the **same config file**. Unlike [`transfer_task`](../transfer-task/index.md), which delegates a sub-task and collects the result, `handoff` rewires the session so the receiving agent continues the conversation directly with the user.\n\nThis is the core mechanism for **handoffs routing** — a pattern where a router agent classifies the user's request and hands it off to a specialist, which then owns the rest of the session.\n\n> [!NOTE]\n> **Local only**\n>\n> The `handoff` tool only targets agents declared in the **same** config file by their local name. It does **not** open network connections. To delegate to a remote agent over the network, use the [A2A toolset](../a2a/index.md) instead.\n\n## Configuration\n\nThe tool is enabled implicitly when an agent declares a non-empty `handoffs:` list. You do **not** add `- type: handoff` under `toolsets:` — it is not a toolset type.\n\n```yaml\nagents:\n  router:\n    model: openai/gpt-4o\n    description: Routes questions to the right specialist\n    instruction: |\n      Classify the user's question and hand off to the most appropriate\n      specialist. If unsure, ask a clarifying question first.\n    handoffs: [billing, support]\n\n  billing:\n    model: openai/gpt-4o\n    description: Billing specialist\n    instruction: Answer billing questions.\n\n  support:\n    model: openai/gpt-4o\n    description: Technical support specialist\n    instruction: Help with technical issues.\n```\n\nThe router agent automatically gets a `handoff` tool it can call to switch the conversation to `billing` or `support`.\n\n## Tool Interface\n\nThe `handoff` tool takes a single parameter:\n\n| Parameter | Type   | Required | Description                                                       |\n| --------- | ------ | -------- | ----------------------------------------------------------------- |\n| `agent`   | string | ✓        | The local name of the agent to hand off the conversation to.      |\n\nOnly names listed in the current agent's `handoffs:` field are valid targets.\n\n> [!TIP]\n> **See also**\n>\n> For sub-task delegation (caller stays in control, waits for the result), see [Transfer Task](../transfer-task/index.md). For remote agent connections over the network, see the [A2A toolset](../a2a/index.md). For the broader pattern, see [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).\n","_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md":"---\ntitle: \"LSP Tool\"\ndescription: \"Connect to Language Server Protocol servers for code intelligence.\"\nkeywords: docker agent, ai agents, tools, toolsets, lsp tool\nlinkTitle: \"LSP\"\nweight: 220\ncanonical: https://docs.docker.com/ai/docker-agent/tools/lsp/\n---\n\n_Connect to Language Server Protocol servers for code intelligence._\n\n## Overview\n\nThe LSP tool connects your agent to any Language Server Protocol (LSP) server, providing comprehensive code intelligence capabilities like go-to-definition, find references, diagnostics, and more.\n\n> [!NOTE]\n> **What is LSP?**\n>\n> The [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) is a standard for providing language features like autocomplete, go-to-definition, and diagnostics. Most programming languages have LSP servers available.\n\n## Available Tools\n\nThe LSP toolset provides these tools to the agent:\n\n| Tool                    | Description                                   | Read-Only |\n| ----------------------- | --------------------------------------------- | --------- |\n| `lsp_workspace`         | Get workspace info and available capabilities | ✓         |\n| `lsp_hover`             | Get type info and documentation for a symbol  | ✓         |\n| `lsp_definition`        | Find where a symbol is defined                | ✓         |\n| `lsp_references`        | Find all references to a symbol               | ✓         |\n| `lsp_document_symbols`  | List all symbols in a file                    | ✓         |\n| `lsp_workspace_symbols` | Search symbols across the workspace           | ✓         |\n| `lsp_diagnostics`       | Get errors and warnings for a file            | ✓         |\n| `lsp_code_actions`      | Get available quick fixes and refactorings    | ✓         |\n| `lsp_rename`            | Rename a symbol across the workspace          | ✗         |\n| `lsp_format`            | Format a file                                 | ✗         |\n| `lsp_call_hierarchy`    | Find incoming/outgoing calls                  | ✓         |\n| `lsp_type_hierarchy`    | Find supertypes/subtypes                      | ✓         |\n| `lsp_implementations`   | Find interface implementations                | ✓         |\n| `lsp_signature_help`    | Get function signature at call site           | ✓         |\n| `lsp_inlay_hints`       | Get type annotations and parameter names      | ✓         |\n\n## Configuration\n\n```yaml\nagents:\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code developer with LSP support\n    instruction: You are a software developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        args: []\n        file_types: [\".go\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Properties\n\n| Property      | Type   | Required | Description                                                                                                                  |\n| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `command`     | string | ✓        | LSP server executable command                                                                                                |\n| `args`        | array  | ✗        | Command-line arguments for the LSP server                                                                                    |\n| `env`         | object | ✗        | Environment variables for the LSP process                                                                                    |\n| `file_types`  | array  | ✗        | File extensions this LSP handles (e.g., `[\".go\", \".mod\"]`)                                                                   |\n| `working_dir` | string | ✗        | Working directory for the LSP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. |\n| `version`     | string | ✗        | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary |\n\n## Common LSP Servers\n\nHere are configurations for popular languages:\n\n### Go (gopls)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    version: \"golang/tools@v0.21.0\" # optional: auto-install if not in PATH\n    file_types: [\".go\"]\n```\n\nIf your Go module lives in a subdirectory (e.g. a monorepo where `go.mod` is under `./backend`), set `working_dir` so `gopls` is started from the module root:\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    working_dir: ./backend # gopls must be started from the module root\n```\n\n### TypeScript/JavaScript (typescript-language-server)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: typescript-language-server\n    args: [\"--stdio\"]\n    file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n```\n\n### Python (pylsp)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: pylsp\n    file_types: [\".py\"]\n```\n\n### Rust (rust-analyzer)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: rust-analyzer\n    file_types: [\".rs\"]\n```\n\n### C/C++ (clangd)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: clangd\n    file_types: [\".c\", \".cpp\", \".h\", \".hpp\"]\n```\n\n## Multiple LSP Servers\n\nYou can configure multiple LSP servers for different file types:\n\n```yaml\nagents:\n  polyglot:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-language developer\n    instruction: You are a full-stack developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      - type: lsp\n        command: typescript-language-server\n        args: [\"--stdio\"]\n        file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n      - type: lsp\n        command: pylsp\n        file_types: [\".py\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Workflow Instructions\n\nThe LSP tool includes built-in instructions that guide the agent on how to use it effectively. The agent learns to:\n\n1. Start with `lsp_workspace` to understand available capabilities\n2. Use `lsp_workspace_symbols` to find relevant code\n3. Use `lsp_references` before modifying any symbol\n4. Check `lsp_diagnostics` after every code change\n5. Apply `lsp_format` after edits are complete\n\n> [!TIP]\n> **Best Practice**\n>\n> Always include the `filesystem` tool alongside LSP. The agent needs filesystem access to read and write code files, while LSP provides intelligence about the code.\n\n## Capability Detection\n\nNot all LSP servers support all features. During the `initialize` handshake, Docker Agent reads the server's `ServerCapabilities` and **filters out the `lsp_*` tools the server does not advertise**. The model never sees, for example, `lsp_inlay_hints` against a server that doesn't support it, so it can't waste a turn calling a tool that would only fail.\n\nThe agent uses `lsp_workspace` to discover what's available:\n\n```text\nWorkspace Information:\n- Root: /path/to/project\n- Server: gopls v0.14.0\n- File types: .go\n\nAvailable Capabilities:\n- Hover: Yes\n- Go to Definition: Yes\n- Find References: Yes\n- Rename: Yes\n- Code Actions: Yes\n- Formatting: Yes\n- Call Hierarchy: Yes\n- Type Hierarchy: Yes\n...\n```\n\n## Auto-Restart and Lifecycle\n\nLSP toolsets are managed by the same supervisor as MCP toolsets, so a crashed `gopls` (or any other language server) is reconnected automatically with exponential backoff. Use the [`lifecycle`](../../configuration/tools/index.md#toolset-lifecycle) block to tune the policy per toolset — for example, mark `gopls` as `strict` if your CI flow requires it to be available, or use `/toolset-restart gopls` from the TUI to force a reconnect when the server gets stuck.\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: resilient # default: auto-restart on crash with exponential backoff\n```\n\n## Position Format\n\nAll LSP tools use **1-based** line and character positions:\n\n- Line 1 is the first line of the file\n- Character 1 is the first character on a line\n\n```json\n{\n  \"file\": \"/path/to/file.go\",\n  \"line\": 42,\n  \"character\": 15\n}\n```\n\n> [!TIP]\n> **Auto-Installation**\n>\n> Docker Agent can automatically download and install LSP servers if they are not found in your PATH. Use the `version` property to specify a package, or let Docker Agent auto-detect it from the command name. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) for details.\n","_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md":"---\ntitle: \"MCP Catalog Tool\"\ndescription: \"Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp catalog tool\nlinkTitle: \"MCP Catalog\"\nweight: 120\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp-catalog/\n---\n\n_Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand._\n\n## Overview\n\nThe `mcp_catalog` toolset gives an agent access to a curated subset of the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp) — every server in this subset is reachable over the **streamable-http** transport, so Docker Agent can talk to it directly without the MCP gateway or a local subprocess.\n\nServers are **not** active by default. Instead, the toolset exposes a small set of meta-tools the agent uses to search, enable, and disable servers as a turn unfolds. Tools from un-enabled servers stay hidden, so the prompt is not flooded with hundreds of tool definitions the agent will never use.\n\n> [!NOTE]\n> **When to use it**\n>\n> Use `mcp_catalog` when you want the agent to _decide at runtime_ which third-party services it needs (Notion, Stripe, Brave Search, …) instead of pinning that decision in YAML up front. For a fixed set of servers, declare each one with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) directly — the catalog adds an extra layer of meta-tools that pure `type: mcp` entries do not need.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n```\n\nThe catalog is embedded in the `docker-agent` binary and refreshed with each release. By default every server in the embedded subset is offered.\n\n### Restricting the offered servers\n\nTwo optional lists narrow what the toolset offers, so an agent sees a focused, predictable menu instead of the full catalog:\n\n- **`allowed_servers`** — when non-empty, **only** these catalog server ids are searchable and enableable; every other entry is hidden.\n- **`blocked_servers`** — removes individual ids from the offered set. It is applied **after** `allowed_servers`, so a server listed in both is blocked (block wins over allow).\n\nBoth take server ids (the `id` field returned by `search_remote_mcp_servers`). An empty or omitted list disables that filter.\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n    allowed_servers:\n      - docker-docs\n      - microsoft-learn\n      - hugging-face\n    blocked_servers:\n      - gitmcp\n```\n\n## Meta-Tools\n\nUp to five tools are exposed to the model. The disable / reset-auth pair only appears once at least one server is enabled, so the meta-tool surface stays minimal until the agent activates something.\n\n| Tool                            | When visible            | Description                                                                                                                                          |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `search_remote_mcp_servers`     | Always                  | Case-insensitive fuzzy search over id, title, description, category and tags. Returns id, auth requirements (`oauth` / `none`) and URL. |\n| `enable_remote_mcp_server`      | Always                  | Activate a server by id. **Blocks** until the connection (and any required OAuth handshake) completes; on success the server's tools are immediately live and the model continues with the user's original request in the same turn. |\n| `list_remote_mcp_servers`       | Always                  | Show currently enabled servers and their connection state.                                                                                           |\n| `disable_remote_mcp_server`     | After first enable      | Stop a server and remove its tools from the active set.                                                                                              |\n| `reset_remote_mcp_server_auth`  | After first enable      | Drop persisted OAuth credentials so the next enable triggers a fresh authorization flow. No-op for `none` servers.                       |\n\n### Workflow\n\n1. The agent calls `search_remote_mcp_servers` with a keyword matching the user's intent (`\"notion\"`, `\"stripe\"`, `\"docs\"`, `\"browser\"`, `\"grafana\"`, …).\n2. It picks a matching server id and calls `enable_remote_mcp_server`. **`enable` blocks** until the MCP handshake (and any required OAuth flow) completes:\n   - on success the server's tools are available **in the same turn** — the agent goes straight to the user's original request, no re-ask required;\n   - on failure (user dismissed the authorization dialog, server refused) the tool returns an error result naming the specific reason so the agent can recover instead of pretending the server is connected.\n3. It uses the newly activated tools as it would any other.\n4. When done, it calls `disable_remote_mcp_server` to remove the server from the active set.\n\n## Authentication\n\nThe catalog only includes servers Docker Agent can authenticate itself, so there are two auth flavours:\n\n- **`oauth`** — `enable_remote_mcp_server` surfaces an authorization URL through the elicitation pipeline (the same one used by YAML-declared remote MCP toolsets) and blocks until the user either authorizes or cancels. Once the user authorizes, tokens are persisted in the OS keyring and re-used on subsequent runs. Use `reset_remote_mcp_server_auth` to wipe them. If the user dismisses the dialog, `enable` returns an error result naming the decline so the agent can ask whether to retry.\n- **`none`** — No authentication. The server is reachable as soon as it is enabled.\n\nServers that require a caller-provided API key are intentionally excluded from the catalog. To use one, declare it explicitly with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) and supply the key via an environment variable.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Agent that can on-demand connect to remote MCP servers from the Docker MCP Catalog.\n    instruction: |\n      You can discover and activate remote MCP servers on demand.\n      Use search_remote_mcp_servers to find a server matching the\n      user's intent, then enable_remote_mcp_server to activate it.\n      Be conservative: enable only the servers you actually need for\n      the task at hand. Disable a server with disable_remote_mcp_server\n      once you are done with it.\n    toolsets:\n      - type: mcp_catalog\n```\n\nA complete, runnable configuration lives in [`examples/mcp_catalog.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog.yaml). A curated, allow/block-listed variant lives in [`examples/mcp_catalog_filtered.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog_filtered.yaml).\n\n## Notes and Limitations\n\n- **Streamable-http only.** The catalog deliberately excludes servers that require a local subprocess or the MCP gateway — declare those with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) instead.\n- **Catalog membership changes between releases.** The set of available servers is updated with each Docker Agent release as integrations are added or removed. Servers present in one release may not appear in the next.\n- **Blocking enable.** DNS, TCP, MCP handshake and any OAuth flow happen synchronously inside `enable_remote_mcp_server` so the agent gets a deterministic result in the same turn. On startup, however, the runtime probes tools non-interactively (`mcp.WithoutInteractivePrompts`); OAuth-pending servers fail fast there and are silently deferred to the next interactive turn — including the sidebar-only tool-count pass, where a dialog would be impossible.\n- **No prompt discovery.** MCP prompt lookups (`/prompts`) walk YAML-declared `mcp` toolsets directly; prompts exposed by servers activated through the catalog are not surfaced. Tools — the primary interface — work fine.\n- **Frozen at build time.** The list of servers is embedded in the binary. New entries land with each Docker Agent release.\n\n> [!TIP]\n> **Pair with permissions**\n>\n> Because the agent decides which third-party services to talk to, this toolset works best with explicit [permissions](../../configuration/permissions/index.md) on the surrounding tools (filesystem writes, shell commands) so a misrouted server cannot exfiltrate data unnoticed.\n","_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md":"---\ntitle: \"MCP Tool\"\ndescription: \"Extend agents with external tools via the Model Context Protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp tool\nlinkTitle: \"MCP\"\nweight: 130\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp/\naliases:\n  - /ai/docker-agent/integrations/mcp/\n---\n\n_Extend agents with external tools via the Model Context Protocol (MCP)._\n\n## Overview\n\nThe `mcp` toolset connects your agent to any MCP server — a process or remote service that exposes tools, resources, and prompts over the [Model Context Protocol](https://modelcontextprotocol.io/). Three flavours are supported:\n\n| Flavour | Transport | Best for |\n| --- | --- | --- |\n| **Docker MCP** | Container via the [MCP Gateway](https://github.com/docker/mcp-gateway) | Curated, sandboxed servers from the [Docker MCP Catalog](https://hub.docker.com/u/mcp) |\n| **Local stdio** | Subprocess over stdin/stdout | Custom or community MCP servers run from a binary or `npx`/`pip` package |\n| **Remote** | Streamable HTTP or SSE | Cloud services with hosted MCP endpoints (Linear, Notion, Atlassian, …) |\n\n> [!NOTE]\n> **What is MCP?**\n>\n> The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI tools. Docker Agent can both _use_ MCP servers (this page) and _expose_ agents as MCP servers — see [MCP Mode](../../features/mcp-mode/index.md).\n\n## Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the MCP Gateway. The `ref: docker:<name>` syntax pulls a curated definition from the Docker MCP Catalog:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo        # web search\n  - type: mcp\n    ref: docker:github-official   # GitHub integration\n    tools: [\"list_issues\", \"create_issue\"]\n```\n\nBrowse available servers at the [Docker MCP Catalog](https://hub.docker.com/u/mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`) or a name from the [reusable `mcps:`](../../configuration/overview/index.md#reusable-mcp-servers-mcps) block. |\n| `tools`       | array  | Optional whitelist — only expose these tools to the model.       |\n| `instruction` | string | Custom instructions injected into the agent's context.           |\n| `config`      | any    | MCP server-specific configuration passed during initialization.  |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n\n## Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property      | Type   | Description |\n| ------------- | ------ | ----------- |\n| `command`     | string | Command to execute the MCP server. |\n| `args`        | array  | Command arguments. |\n| `tools`       | array  | Optional whitelist — only expose these tools. |\n| `env`         | object | Environment variables (key-value pairs). |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context. |\n| `version`     | string | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary. |\n\n> [!TIP]\n> **Auto-installation**\n>\n> If the `command` is not in your `PATH`, Docker Agent looks it up in the [aqua registry](https://github.com/aquaproj/aqua-registry) and installs it for you. Use `version: \"false\"` to opt out, or set `DOCKER_AGENT_AUTO_INSTALL=false` globally. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools).\n\n## Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network. OAuth flows (including [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591)) are handled automatically — Docker Agent opens your browser when authentication is required and caches tokens for subsequent sessions. Tokens are refreshed silently when they expire or are revoked server-side; if a silent refresh is not possible, the OAuth prompt reappears on the next message.\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp.linear.app/mcp\"\n      transport_type: \"streamable\"               # or \"sse\" for legacy servers\n      headers:\n        Authorization: \"Bearer ${env.LINEAR_TOKEN}\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: false\n    tools: [\"search_issues\", \"create_issue\"]\n```\n\n| Property                | Type    | Description |\n| ----------------------- | ------- | ----------- |\n| `remote.url`            | string  | Base URL of the MCP server. |\n| `remote.transport_type` | string  | `streamable` or `sse`. |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. See [Remote MCP Servers](../../features/remote-mcp/index.md#per-request-header-template-expansion) for details. |\n| `remote.oauth`          | object  | Explicit OAuth client credentials for servers that don't support DCR. See [Remote MCP Servers](../../features/remote-mcp/index.md#oauth-for-servers-without-dynamic-client-registration). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers. |\n\nFor a curated list of public remote MCP endpoints (Linear, GitHub, Vercel, Notion, …) and full OAuth configuration details, see [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n## MCP Prompts\n\nMCP servers can expose **prompts** — named, parameterized templates that the server provides via the `/prompts` endpoint. Docker Agent discovers these at toolset startup and registers them as **slash commands** in the TUI, so you can invoke them directly from the input box.\n\n```text\n# Type / to see available prompts alongside built-in commands\n/review         # invoke an MCP prompt named \"review\"\n/summarize My text here   # invoke with the first argument filled in\n```\n\n**How it works:**\n\n- Each MCP prompt appears in the command palette (accessible via <kbd>Ctrl</kbd>+<kbd>K</kbd>) under the **MCP Prompts** category.\n- Typing `/<prompt-name>` in the input box invokes the prompt immediately.\n- If the prompt declares arguments and you provide text after the slash command, that text is mapped to the first declared argument.\n- If a required argument is missing, Docker Agent opens the argument input dialog before running the prompt.\n- When no argument is needed or all required arguments are supplied, the prompt runs immediately.\n\n> [!NOTE]\n> MCP prompt discovery requires a YAML-declared `mcp` toolset. Prompts from servers activated through the [Docker MCP Catalog](../../tools/mcp-catalog/index.md) (`ref: docker:<name>`) are not currently surfaced.\n\n## Embedded Resources\n\nMCP tool results can include embedded resources — images, PDFs, and text files returned directly in the tool response. Docker Agent preserves these as attachments and forwards them to the model as native content blocks:\n\n- **Anthropic** — images become `image` blocks in the `tool_result`; PDFs and other documents become `document` blocks.\n- **OpenAI** — images are forwarded as `input_image` data URIs; PDFs as `input_file` data URIs in the tool result content.\n- **Bedrock** and **Gemini** — receive equivalent provider-native representations.\n\nNo configuration is required. When an MCP server returns an embedded resource alongside its text output, the resource is automatically attached and sent to the model on the next turn. This is useful for MCP servers that generate charts, export PDFs, or return binary data as part of their responses.\n\n## Reusable Definitions (`mcps:`)\n\nRepeated MCP server configurations can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`:\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n  playwright:\n    command: npx\n    args: [\"-y\", \"@modelcontextprotocol/server-playwright\"]\n\nagents:\n  root:\n    model: openai/gpt-5\n    toolsets:\n      - type: mcp\n        ref: github\n      - type: mcp\n        ref: playwright\n```\n\nSee [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) for the full reference.\n\n## Common Options\n\nThese properties apply to every MCP toolset regardless of flavour:\n\n### Tool filtering\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n```\n\nWhitelisting tools improves model accuracy — fewer choices means less confusion.\n\n### Deferred loading\n\nSkip the toolset's startup cost until its tools are actually called:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  # Or defer specific tools within a toolset:\n  - type: mcp\n    ref: docker:slack\n    defer: [\"list_channels\", \"search_messages\"]\n```\n\n### Custom instructions\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\n### TOON-encoded outputs\n\nRe-encode verbose JSON outputs as the compact [TOON](https://github.com/alpkeskin/gotoon) format to save context budget. Typically yields 30–60% smaller payloads on list/search tools.\n\n`toon` is a regex string that is matched against tool names. Any tool whose name matches the pattern has its JSON output transparently re-encoded as TOON before it is shown to the model. The re-encoding reduces schema verbosity, which is especially useful when a model struggles with large or repetitive tool output.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"            # toonify every tool from this server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\nThe value is a comma-separated list of regexes (or a single regex). A tool name must match at least one pattern to be re-encoded. Setting `toon: \".*\"` re-encodes all tools from that toolset.\n\nSee [`examples/github-toon.yaml`](https://github.com/docker/docker-agent/blob/main/examples/github-toon.yaml) for a practical example using the GitHub MCP server.\n\n### Per-toolset model routing\n\nProcess tool results from this toolset with a different (typically cheaper / faster) model. The override is one-shot — subsequent turns return to the agent's primary model:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    model: openai/gpt-4o-mini\n```\n\nSee [Per-Toolset Model Routing](../../configuration/tools/index.md#per-toolset-model-routing).\n\n### Lifecycle (auto-restart, profiles)\n\nLocal stdio and remote MCP servers are supervised: crashed servers reconnect automatically with exponential backoff. **Remote** MCP servers (Streamable HTTP / SSE) also reconnect after idle/clean connection closes — services like Notion and Linear periodically close idle connections, and Docker Agent reconnects transparently. Tune the policy with the `lifecycle` block:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; auto-restart with backoff\n  - type: mcp\n    command: docker\n    args: [\"mcp\", \"gateway\"]\n    lifecycle:\n      profile: strict      # fail-fast: required, no retries\n```\n\nSee [Toolset Lifecycle](../../configuration/tools/index.md#toolset-lifecycle) for all profiles and tuning knobs, and [`/toolset-restart`](../../features/tui/index.md) to force a reconnect from the TUI.\n\n## Combined Example\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Docker MCP catalog entry\n      - type: mcp\n        ref: docker:duckduckgo\n\n      # Reusable definition from the top-level mcps: block\n      - type: mcp\n        ref: github\n        tools: [\"list_issues\", \"create_issue\"]\n        toon: \"list_.*\"\n\n      # Local stdio server with auto-install\n      - type: mcp\n        command: gopls\n        version: \"golang/tools@v0.21.0\"\n        args: [\"mcp\"]\n\n      # Remote MCP with OAuth (handled automatically)\n      - type: mcp\n        remote:\n          url: \"https://mcp.linear.app/mcp\"\n          transport_type: \"streamable\"\n        instruction: Use Linear for issue tracking.\n```\n\n> [!WARNING]\n> **Toolset order matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins: the duplicate from the later toolset is ignored and a warning identifies both toolsets. Order your toolsets intentionally. To keep both tools callable, give the MCP toolset a unique `name:` (its tools are then exposed as `<name>_<tool>`) or restrict the overlapping toolset with its `tools:` filter.\n\n## See Also\n\n- [Tool Configuration](../../configuration/tools/index.md) — full reference for every toolset type, plus shared options (lifecycle, TOON, model routing, …).\n- [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) — the top-level `mcps:` block.\n- [Remote MCP Servers](../../features/remote-mcp/index.md) — catalog of public remote MCP endpoints + OAuth recipes.\n- [MCP Mode](../../features/mcp-mode/index.md) — expose your own agents as MCP tools to Claude Desktop, Claude Code, etc.\n- [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) — automatic installation of MCP server binaries.\n","_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md":"---\ntitle: \"Memory Tool\"\ndescription: \"Persistent key-value storage backed by SQLite for cross-session recall.\"\nkeywords: docker agent, ai agents, tools, toolsets, memory tool\nlinkTitle: \"Memory\"\nweight: 100\ncanonical: https://docs.docker.com/ai/docker-agent/tools/memory/\n---\n\n_Persistent key-value storage backed by SQLite for cross-session recall._\n\n## Overview\n\nThe memory tool provides persistent key-value storage backed by SQLite. Data survives across sessions, allowing agents to remember facts, user preferences, project context, and past decisions. Memories can be organized with categories and searched by keyword.\n\nBy default, the database is stored at `~/.cagent/memory/<config-name>/memory.db`, where `<config-name>` is derived from the loaded configuration (typically the YAML file name) and falls back to `default` when unavailable. When the agent is loaded from an OCI reference (e.g. `docker/my-agent:latest`), characters that are reserved in filesystem paths (such as `:`) are sanitised in the `<config-name>` segment — the agent's display name elsewhere is unchanged. Agents declared in the same configuration share this database by default; set an explicit `path` per toolset to isolate them.\n\n## Available Tools\n\n| Tool              | Description                                                                      |\n| ----------------- | -------------------------------------------------------------------------------- |\n| `add_memory`      | Store a new memory with optional category                                        |\n| `get_memories`    | Retrieve all stored memories                                                     |\n| `delete_memory`   | Delete a specific memory by ID                                                   |\n| `search_memories` | Search memories by keywords and/or category (more efficient than `get_memories`) |\n| `update_memory`   | Update an existing memory's content and/or category by ID                        |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: memory\n```\n\n### Options\n\n| Property | Type   | Default                                   | Description                      |\n| -------- | ------ | ----------------------------------------- | -------------------------------- |\n| `path`   | string | `~/.cagent/memory/<config-name>/memory.db` | Path to the SQLite database file |\n\n### Custom Database Path\n\n```yaml\ntoolsets:\n  - type: memory\n    path: ./agent_memory.db\n```\n\n## Categories\n\nMemories support an optional `category` field for organization and filtering. Common categories include:\n\n- `preference` — User preferences and settings\n- `fact` — Factual information about the project or user\n- `project` — Project-specific context\n- `decision` — Past decisions and their rationale\n\n> [!TIP]\n> Memory is especially useful for long-running assistants that need to recall information across conversations — like coding preferences, project conventions, or context discovered during previous sessions.\n","_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md":"---\ntitle: \"Model Picker Tool\"\ndescription: \"Let the agent pick between several models per turn.\"\nkeywords: docker agent, ai agents, tools, toolsets, model picker tool\nlinkTitle: \"Model Picker\"\nweight: 200\ncanonical: https://docs.docker.com/ai/docker-agent/tools/model-picker/\n---\n\n_Let the agent pick between several models per turn._\n\n## Overview\n\nThe model picker tool gives an agent the ability to dynamically choose which model to use for each turn of the conversation. This is useful when you want the agent to route different types of requests to different models — for example, using a fast, inexpensive model for simple queries and a more capable model for complex reasoning tasks.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: model_picker\n    models:\n      - openai/gpt-5-mini\n      - anthropic/claude-sonnet-4-5\n      - openai/gpt-5\n```\n\n### Options\n\n| Property | Type           | Required | Description                                                  |\n| -------- | -------------- | -------- | ------------------------------------------------------------ |\n| `models` | array[string]  | ✓        | List of model references the agent can choose from. Use `provider/model` format. |\n\n## How It Works\n\nWhen the model picker toolset is enabled, the agent gets two tools: `change_model` to switch to one of the configured models, and `revert_model` to return to its default model. The agent decides which model to use based on the complexity of the task, cost considerations, or other factors you describe in its instruction.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini  # Default model\n    instruction: |\n      You are a helpful assistant. For simple questions, use gpt-5-mini.\n      For complex reasoning or coding tasks, switch to claude-sonnet-4-5 or gpt-5.\n    toolsets:\n      - type: model_picker\n        models:\n          - openai/gpt-5-mini\n          - anthropic/claude-sonnet-4-5\n          - openai/gpt-5\n```\n\n> [!TIP]\n> **Cost optimization**\n>\n> The model picker tool is particularly useful for cost optimization: let the agent use a cheap model by default and only escalate to expensive models when necessary.\n\n## Tool Interface\n\nThe toolset exposes two tools:\n\n### `change_model`\n\n| Parameter | Type   | Required | Description                                                                 |\n| --------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `model`   | string | ✓        | The model to switch to. Must be one of the configured models.               |\n\n### `revert_model`\n\nTakes no parameters. Reverts the agent to its original/default model.\n\nThe switch takes effect immediately: the next inference call — including the remainder of the current agentic loop — uses the new model.\n","_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md":"---\ntitle: \"Open URL Tool\"\ndescription: \"Open a fixed URL in the user's default browser.\"\nkeywords: docker agent, ai agents, tools, toolsets, open url tool\nlinkTitle: \"Open URL\"\nweight: 40\ncanonical: https://docs.docker.com/ai/docker-agent/tools/open-url/\n---\n\n_Open a fixed URL in the user's default browser._\n\n## Overview\n\nThe `open_url` toolset exposes a single, argument-less tool that opens a URL\nbaked into the toolset definition in the user's default browser. The model\nnever supplies the URL — it just calls the tool by name. Launching the browser\nis cross-platform: Docker Agent uses `open` on macOS, `xdg-open` on Linux, and\n`rundll32` on Windows.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Letting an agent open a dashboard, documentation page, or deep link on demand\n> - Deep-linking into a desktop app via a custom URI scheme (e.g. `docker-desktop://`)\n> - Any \"take me there\" action where the destination is fixed and known up front\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant that can open the dashboard\n    instruction: When the user asks to see the dashboard, call open_dashboard.\n    toolsets:\n      - type: open_url\n        name: open_dashboard\n        url: https://example.com/dashboard\n```\n\n## Properties\n\n| Property | Type   | Required | Description                                                                                          |\n| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |\n| `url`    | string | ✓        | URL to open. Supports `${env.VAR}` interpolation. Any scheme the OS can dispatch is allowed.         |\n| `name`   | string | ✗        | Tool name the agent references. Defaults to `open_url`. Use a descriptive name when configuring several. |\n\n## Multiple URLs\n\nAdd one toolset entry per destination, each with its own `name`:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_dashboard\n    url: https://example.com/dashboard\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## URL Interpolation\n\nThe `url` field supports `${env.VAR}` placeholders, expanded at call time\nagainst the runtime environment:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## Custom URI Schemes\n\nAny scheme the operating system knows how to dispatch works, including deep\nlinks into desktop applications:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_in_docker_desktop\n    url: docker-desktop://dashboard/apps\n```\n\n## Limitations\n\n- The URL must include a scheme (e.g. `https://`); bare paths are rejected.\n- URLs that look like a command-line flag (starting with `-`) are refused to\n  prevent argument injection into the platform `open` helper.\n- The tool opens the URL on the **host** running Docker Agent; in headless or\n  remote environments where no browser/launcher is available, the call fails\n  gracefully and reports the error to the agent.\n\nSee [`examples/open_url.yaml`](https://github.com/docker/docker-agent/blob/main/examples/open_url.yaml) for a complete configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md":"---\ntitle: \"OpenAPI Tool\"\ndescription: \"Automatically generate tools from an OpenAPI specification.\"\nkeywords: docker agent, ai agents, tools, toolsets, openapi tool\nlinkTitle: \"OpenAPI\"\nweight: 230\ncanonical: https://docs.docker.com/ai/docker-agent/tools/openapi/\n---\n\n_Automatically generate tools from an OpenAPI specification._\n\n## Overview\n\nThe OpenAPI tool fetches an OpenAPI 3.x specification from a URL and creates one tool per API operation. Each endpoint's parameters, request body, and description are translated into a callable tool that the agent can invoke directly.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://petstore3.swagger.io/api/v3/openapi.json\"\n```\n\n### With custom headers\n\nPass custom headers to every HTTP request made by the generated tools (for example, for authentication):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    headers:\n      Authorization: \"Bearer ${env.API_TOKEN}\"\n      X-Custom-Header: \"my-value\"\n```\n\n### Custom timeout\n\nOverride the default 30-second HTTP timeout (applies both to fetching the spec and to the generated tool calls):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    timeout: 60\n```\n\n### Reaching internal services\n\nBy default the OpenAPI tool refuses connections to non-public IP addresses, blocking SSRF attempts even when DNS resolves an otherwise-public host to an internal range. Opt in with `allow_private_ips` when the spec or its `servers` entries legitimately target localhost or your internal network:\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"http://localhost:8080/openapi.json\"\n    allow_private_ips: true\n```\n\n## Properties\n\n| Property            | Type              | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `url`               | string            | ✓        | URL of the OpenAPI specification (JSON format). Supports `${env.VAR}` interpolation.                                                                                                                                                                              |\n| `headers`           | map[string]string | ✗        | Custom HTTP headers sent with every request — both the spec fetch and every generated tool call. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request when docker agent is exposed as a server). |\n| `timeout`           | int               | ✗        | HTTP client timeout in seconds (default: `30`). Applies to both the spec fetch and the generated tools' requests.                                                                                                                                                 |\n| `allow_private_ips` | boolean           | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the spec or its servers legitimately target internal services. By default such addresses are refused at dial time, after DNS resolution, so DNS rebinding cannot bypass the check. |\n\n## How it works\n\n1. The spec is fetched from the configured `url` at startup.\n2. Each operation (GET, POST, PUT, …) becomes a separate tool named after its `operationId` (or `method_path` when no `operationId` is set).\n3. Path and query parameters are exposed as tool parameters. Request body properties are prefixed with `body_`.\n4. Read-only operations (GET, HEAD, OPTIONS) are annotated accordingly.\n5. Responses are returned as text; errors include the HTTP status code.\n\n## Limits\n\n- The OpenAPI spec must be **10 MB or less**.\n- Individual API responses are truncated at **1 MB**.\n\n## Example\n\nSee the full [Pet Store example](https://github.com/docker/docker-agent/blob/main/examples/openapi-petstore.yaml) for a working agent configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md":"---\ntitle: \"Plan Tool\"\ndescription: \"Shared persistent scratchpad for multi-agent collaboration.\"\nkeywords: docker agent, ai agents, tools, toolsets, plan tool\nlinkTitle: \"Plan\"\nweight: 150\ncanonical: https://docs.docker.com/ai/docker-agent/tools/plan/\n---\n\n_Shared persistent scratchpad for multi-agent collaboration._\n\n## Overview\n\nThe plan tool gives agents a shared, persistent scratchpad of named documents. Any agent in a multi-agent config that loads the `plan` toolset can read and write the same plans, and those plans survive across sessions. This makes it straightforward to wire a planner agent that sketches work and one or more executor agents that consume it without any custom tool wiring.\n\nPlans are stored as JSON files in the Docker Agent data directory (`~/.cagent/plans/` by default). Agents that share a process serialize on a single mutex, and every write or delete additionally holds an advisory lock on a sentinel file in the plans directory, so writers in *separate* Docker Agent processes are serialized too: concurrent edits can never silently overwrite each other, and a stale revision always fails with a deterministic version conflict. Writes are atomic (temp file + rename), so a reader never observes partial content.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: plan\n```\n\nNo additional options are required. All agents that include `type: plan` in their toolsets share the same plans.\n\n## Available Tools\n\n| Tool                    | Description                                                                                       |\n| ----------------------- | ------------------------------------------------------------------------------------------------- |\n| `write_plan`            | Create or update a shared plan by name. Replaces the entire plan content — read it first to preserve what you want to keep. Each write bumps the revision number. |\n| `read_plan`             | Read a shared plan by name, including its title, content, author, status, revision number, and last-updated timestamp. |\n| `list_plans`            | List all shared plans with their name, title, author, status, revision, and last-updated timestamp. |\n| `delete_plan`           | Delete a shared plan by name.                                                                     |\n| `update_plan_from_file` | Create or update a plan, taking the new content from a file on disk instead of inline. Use it with `export_plan_to_file` to edit a large plan without re-sending its whole body. |\n| `export_plan_to_file`   | Write a plan's content to a file. The content goes to disk and is **not** returned as tool output, so materialising a plan costs no tokens. |\n| `set_plan_status`       | Set a plan's free-form status without rewriting its body. The plan must already exist. |\n| `get_plan_status`       | Read a plan's status and current revision without fetching its body.                  |\n\n### Cheap edits with file-based revisions\n\nRe-sending a whole plan on every revision is expensive. The file-based tools let\nan agent edit a plan without paying input-token cost for its body:\n\n1. `export_plan_to_file` writes the current plan content to a path. The content\n   is written to disk and is **not** returned.\n2. The agent edits that file in place with its filesystem tools.\n3. `update_plan_from_file` commits the file's new contents as the next revision.\n\n### Free-form status\n\nEach plan carries a free-form `status` string. There is no fixed vocabulary:\ndefine your own in the system prompt (e.g. `idle`, `in-progress`, `blocked`,\n`done`, `canceled`). Read and write it independently of the body with\n`get_plan_status` and `set_plan_status`, or pass `status` to `write_plan` and\n`update_plan_from_file`. The TUI surfaces the status next to the plan title.\n\n### Optimistic locking\n\nWhen several sessions edit the same plan, concurrent writes could silently\noverwrite each other. Every read returns a `revision` number; pass the value you\nlast read as `last_known_revision` to `write_plan`, `update_plan_from_file`,\n`set_plan_status`, or `delete_plan`. If the plan changed since (its current\nrevision no longer matches), the write is rejected with a version-conflict\nerror and the caller should re-read the plan and retry. The revision check and\nthe write happen under the storage's cross-process file lock, so the conflict\nis detected reliably even when the competing writer runs in a different Docker\nAgent process. Omit `last_known_revision` to write unconditionally (last\nwriter wins).\n\n### Plan Names\n\nPlan names must match the pattern `[a-z0-9][a-z0-9_-]*` (lowercase letters, digits, `-`, `_`). This is enforced structurally so two different inputs can never collapse onto the same file and path-traversal is impossible by construction.\n\n### Plan Fields\n\nEach plan document contains:\n\n| Field      | Description                                               |\n| ---------- | --------------------------------------------------------- |\n| `name`     | The plan's unique slug name                               |\n| `title`    | A short human-readable title (optional)                   |\n| `content`  | The full Markdown or free-form plan text                  |\n| `author`   | Free-form label identifying who last wrote the plan       |\n| `status`   | Free-form lifecycle label (optional), e.g. `in-progress`  |\n| `revision` | Monotonically increasing version counter, bumped on every write |\n| `updatedAt`| ISO 8601 timestamp of the last write                      |\n\n## Example\n\nTwo agents collaborate on a shared plan — the architect drafts it and the builder refines it:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Coordinator\n    instruction: |\n      Route work between the architect and the builder.\n    handoffs: [architect, builder]\n\n  architect:\n    model: anthropic/claude-sonnet-4-5\n    description: Drafts high-level plans\n    instruction: |\n      Use list_plans and read_plan to inspect existing plans, then write_plan\n      to create or revise one. Always read before writing. When done, hand off\n      to the builder.\n    toolsets:\n      - type: plan\n    handoffs: [builder]\n\n  builder:\n    model: openai/gpt-4o\n    description: Adds implementation steps to plans\n    instruction: |\n      Read the architect's plan with read_plan, then use write_plan to append\n      concrete implementation steps. Always read before writing. When done,\n      hand off back to root.\n    toolsets:\n      - type: plan\n    handoffs: [root]\n```\n\nSee [`examples/shared_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_plan` returns a distinct \"not found\" error when a plan does not exist, as opposed to any other I/O error, so callers can tell \"plan missing\" from \"plan unreadable.\"\n- `list_plans` skips corrupt entries but reports them in a `warnings` field so an agent can detect and recover from a bad state (e.g., by calling `delete_plan`).\n- `delete_plan` can remove a corrupt plan to recover from a bad state.\n\n## Managing plans from the host\n\nShared plans can also be inspected and managed outside a session with the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group: list, get, create, update, set status, export, and delete — with the same optimistic-locking semantics as the tools (`--expected-version` guards a write and a stale version fails with exit code 3; `--force` writes unconditionally). Session plans (the per-session \"draft, review, execute\" plan) can be listed, read, and exported through the same commands but stay owned by their session and cannot be mutated from the host.\n\n```bash\n$ docker agent plans list\n$ docker agent plans get release > plan.md\n$ docker agent plans update release --file ./plan.md --expected-version 1\n```\n\n### The `/plans` browser in the TUI\n\nInside the full-screen TUI, the `/plans` slash command (also in the <kbd>Ctrl</kbd>+<kbd>K</kbd> command palette) opens a plan browser over the same store the agents use, so changes made by agents mid-session appear immediately. The list shows every shared plan plus the current session's [session plan](../session_plan/index.md), with each plan's scope, identity (name, or session ID for the session plan), status, version (`-` for the unversioned session plan), last update time, and title.\n\nKeybindings:\n\n| Key | Action |\n| --- | ------ |\n| <kbd>↑</kbd>/<kbd>↓</kbd>, mouse | Navigate; <kbd>Enter</kbd> or double-click opens a detail view with the full metadata and scrollable markdown content |\n| <kbd>/</kbd> | Filter by name, title, status, or scope (<kbd>Esc</kbd> leaves filter mode) |\n| <kbd>r</kbd> | Refresh from storage |\n| <kbd>x</kbd> | Export the selected plan to `<name>.md` (shared) or `session-plan-<short-id>.md` (session) in the session's working directory. An existing file is never overwritten — the export fails with a notification instead |\n| <kbd>s</kbd> | Set a shared plan's free-form status via a small input dialog |\n| <kbd>e</kbd> | Edit a shared plan's content in `$VISUAL`/`$EDITOR` |\n| <kbd>n</kbd> | Create a new shared plan: pick a name, then draft the content in `$VISUAL`/`$EDITOR` (an empty draft aborts) |\n| <kbd>d</kbd> | Delete a shared plan after a confirmation that names the plan and its version |\n| <kbd>Esc</kbd> | Close the detail view / the browser |\n\nEvery mutation is guarded by the version shown on screen (the same optimistic locking as `last_known_revision`): if an agent changed the plan in the meantime, the write is rejected, a notification reports the current version, the newer content is left intact and re-read into the browser, and an edit draft is kept in a temp file so nothing is lost. Session plans are read-only here — status, edit, and delete report why instead of attempting the write. The browser also refreshes live when agents in the same process write, re-status, or delete plans (and when this session's agent updates its session plan); in the lean TUI, which has no overlays, `/plans` is unavailable.\n\n> [!TIP]\n> **Plan vs. Todo vs. Tasks**\n>\n> Use **plan** for shared, free-form documents that multiple agents collaborate on (design docs, requirements, work items). Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md":"---\ntitle: \"RAG Tool\"\ndescription: \"Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search.\"\nkeywords: docker agent, ai agents, tools, toolsets, rag tool\nlinkTitle: \"RAG\"\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/tools/rag/\naliases:\n  - /ai/docker-agent/rag/\n---\n\n_Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search._\n\n## Overview\n\nThe `rag` toolset lets agents search through your documents to find relevant information before responding. Knowledge bases are declared once at the top of the config under `rag:` and then referenced from any agent via `type: rag, ref: <name>`. Docker Agent supports:\n\n- **Background indexing** — Files are indexed automatically and re-indexed on change\n- **Multiple strategies** — Semantic embeddings, BM25 keyword search, and LLM-enhanced search\n- **Hybrid search** — Combine strategies with result fusion for best results\n- **Reranking** — Re-score results with specialized models for improved relevance\n\nRAG is the strategy to reach for when a document collection is too large to inline directly, or gets queried repeatedly across turns/sessions — see [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how it compares to `@`/`/attach` attachments and prompt files.\n\n## Quick Start\n\n```yaml\nrag:\n  my_docs:\n    tool:\n      description: \"Technical documentation\"\n    docs: [./documents, ./some-doc.md]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./docs.db\n        vector_dimensions: 1536\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: |\n      You have access to a knowledge base. Use it to answer questions.\n    toolsets:\n      - type: rag\n        ref: my_docs\n```\n\n## Retrieval Strategies\n\n### Chunked Embeddings (Semantic Search)\n\nUses embedding models to find semantically similar content. Best for understanding intent, synonyms, and paraphrasing.\n\n```yaml\nstrategies:\n  - type: chunked-embeddings\n    embedding_model: openai/text-embedding-3-small\n    database: ./vector.db\n    vector_dimensions: 1536\n    similarity_metric: cosine_similarity\n    threshold: 0.5\n    limit: 10\n    embedding_batch_size: 50\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n### Semantic Embeddings (LLM-Enhanced)\n\nUses an LLM to generate semantic summaries of each chunk before embedding, capturing meaning and intent. Best for code search and understanding implementations.\n\n```yaml\nstrategies:\n  - type: semantic-embeddings\n    embedding_model: openai/text-embedding-3-small\n    vector_dimensions: 1536\n    chat_model: openai/gpt-4o-mini\n    database: ./semantic.db\n    ast_context: true # include AST metadata\n    chunking:\n      size: 1000\n      code_aware: true # AST-aware chunking\n```\n\n> [!NOTE]\n> **Trade-offs**\n>\n> Semantic embeddings provide higher quality retrieval but slower indexing (LLM call per chunk) and additional API costs.\n\n### BM25 (Keyword Search)\n\nTraditional keyword matching using the BM25 algorithm. Best for exact terms, technical jargon, and code identifiers.\n\n```yaml\nstrategies:\n  - type: bm25\n    database: ./bm25.db\n    k1: 1.5 # term frequency saturation\n    b: 0.75 # length normalization\n    threshold: 0.3\n    limit: 10\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n## Hybrid Search\n\nCombine multiple strategies for best results. Strategies run in parallel and results are fused together:\n\n```yaml\nrag:\n  hybrid:\n    docs: [./docs]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./vector.db\n        vector_dimensions: 1536\n        limit: 20\n        chunking: { size: 1000, overlap: 100 }\n      - type: bm25\n        database: ./bm25.db\n        limit: 15\n        chunking: { size: 1000, overlap: 100 }\n    results:\n      fusion:\n        strategy: rrf # Reciprocal Rank Fusion\n        k: 60\n      deduplicate: true\n      limit: 5\n```\n\n## Fusion Strategies\n\n| Strategy   | Best For                          | Description                                                        |\n| ---------- | --------------------------------- | ------------------------------------------------------------------ |\n| `rrf`      | General use (recommended)         | Reciprocal Rank Fusion — rank-based, no score normalization needed |\n| `weighted` | Known performance characteristics | Weight strategies differently (e.g., embeddings: 0.7, BM25: 0.3)   |\n| `max`      | Same scoring scale                | Takes the maximum score from any strategy                          |\n\n## Reranking\n\nRe-score retrieved documents with a specialized model to improve relevance:\n\n```yaml\nresults:\n  reranking:\n    model: openai/gpt-4o-mini\n    top_k: 10 # only rerank top 10\n    threshold: 0.3 # minimum score after reranking\n    criteria: |\n      Prioritize official documentation over blog posts.\n      Prefer recent information and practical examples.\n  limit: 5\n```\n\nSupported reranking providers: **DMR** (native `/rerank` endpoint), **OpenAI**, **Anthropic**, **Gemini**.\n\n## Code-Aware Chunking\n\nFor source code, enable AST-based chunking to keep functions and methods intact:\n\n```yaml\nchunking:\n  size: 2000\n  code_aware: true # Uses tree-sitter for AST-based chunking\n```\n\n> [!NOTE]\n> **Language Support**\n>\n> Currently supports Go (`.go`) files. More languages will be added. Falls back to plain text chunking for unsupported file types.\n\n## Debugging RAG\n\nEnable debug logging to see retrieval details:\n\n```bash\n$ docker agent run config.yaml --debug --log-file debug.log\n```\n\nLook for log tags: `[RAG Manager]`, `[Chunked-Embeddings Strategy]`, `[BM25 Strategy]`, `[RRF Fusion]`, `[Reranker]`.\n\n**Permanent model errors abort early.** If the embedding model, semantic-LLM model, or reranking model returns a permanent error (HTTP 400, 401, 404, or 429 — invalid config, bad auth, unknown model, or rate limit), Docker Agent treats the model configuration as invalid and stops immediately rather than retrying doomed requests:\n\n- **Indexing** — the entire indexing run is aborted after the first permanent failure (including 429). The error is surfaced in the logs so you know immediately if a model name or API key is wrong, rather than silently producing incomplete results.\n- **Reranking** — a permanent error (including 429) permanently disables the reranker for the lifetime of the manager. Subsequent queries fall back to un-reranked results. Only transient errors (5xx, timeouts) fall back and retry on the next query.\n\n> [!TIP]\n> **Examples**\n>\n> See the [RAG examples](https://github.com/docker/docker-agent/tree/main/examples/rag) in the GitHub repo for complete, runnable configurations.\n\n## Configuration Reference\n\n### Top-Level RAG Fields\n\n| Field         | Type     | Default | Description                                                    |\n| ------------- | -------- | ------- | -------------------------------------------------------------- |\n| `docs`        | []string | —       | Document paths/directories (shared across strategies)          |\n| `description` | string   | —       | Human-readable description of this RAG source                  |\n| `respect_vcs` | boolean  | `true`  | Respect `.gitignore` files when indexing documents             |\n| `strategies`  | []object | —       | Array of retrieval strategy configurations                     |\n| `results`     | object   | —       | Post-processing: fusion, reranking, deduplication, final limit |\n\n### Chunked-Embeddings Strategy\n\n| Field                       | Type   | Default             | Description                                                  |\n| --------------------------- | ------ | ------------------- | ------------------------------------------------------------ |\n| `embedding_model`           | string | —                   | **Required.** Embedding model reference                      |\n| `database`                  | string | —                   | Path to local SQLite database                                |\n| `vector_dimensions`         | int    | —                   | Embedding dimensions (e.g., 1536 for text-embedding-3-small) |\n| `similarity_metric`         | string | `cosine_similarity` | Similarity metric                                            |\n| `threshold`                 | float  | `0.5`               | Minimum similarity score (0–1)                               |\n| `limit`                     | int    | `5`                 | Max results from this strategy                               |\n| `embedding_batch_size`      | int    | `50`                | Chunks per embedding request                                 |\n| `max_embedding_concurrency` | int    | `3`                 | Max concurrent embedding requests                            |\n| `chunking.size`             | int    | `1500`              | Chunk size in characters (`4000` when `code_aware` is set)   |\n| `chunking.overlap`          | int    | `75`                | Overlap between chunks in characters                         |\n| `chunking.code_aware`       | bool   | `false`             | AST-based chunking (Go files only)                           |\n\n### Semantic-Embeddings Strategy\n\n| Field                      | Type   | Default    | Description                                                        |\n| -------------------------- | ------ | ---------- | ------------------------------------------------------------------ |\n| `embedding_model`          | string | —          | **Required.** Embedding model reference                            |\n| `chat_model`               | string | —          | **Required.** LLM for generating semantic summaries                |\n| `vector_dimensions`        | int    | —          | **Required.** Embedding dimensions                                 |\n| `database`                 | string | —          | Path to local SQLite database                                      |\n| `semantic_prompt`          | string | (built-in) | Custom prompt template (`${path}`, `${content}`, `${ast_context}`) |\n| `ast_context`              | bool   | `false`    | Include tree-sitter AST metadata in prompts                        |\n| `threshold`                | float  | `0.5`      | Minimum similarity score (0–1)                                     |\n| `limit`                    | int    | `5`        | Max results                                                        |\n| `max_indexing_concurrency` | int    | `3`        | Max concurrent file indexing                                       |\n| `chunking.size`            | int    | `1500`     | Chunk size in characters (`4000` when `code_aware` is set)         |\n| `chunking.overlap`         | int    | `75`       | Overlap between chunks                                             |\n| `chunking.code_aware`      | bool   | `false`    | AST-based chunking                                                 |\n\n### BM25 Strategy\n\n| Field              | Type   | Default | Description                                     |\n| ------------------ | ------ | ------- | ----------------------------------------------- |\n| `database`         | string | —       | Path to local SQLite database                   |\n| `k1`               | float  | `1.5`   | Term frequency saturation (1.2–2.0 recommended) |\n| `b`                | float  | `0.75`  | Length normalization (0–1)                      |\n| `threshold`        | float  | `0.0`   | Minimum BM25 score                              |\n| `limit`            | int    | `5`     | Max results                                     |\n| `chunking.size`    | int    | `1500`  | Chunk size in characters                        |\n| `chunking.overlap` | int    | `75`    | Overlap between chunks                          |\n\n### Results (Post-Processing)\n\n| Field                 | Type   | Default | Description                                                 |\n| --------------------- | ------ | ------- | ----------------------------------------------------------- |\n| `fusion.strategy`     | string | `rrf`   | Fusion method: `rrf`, `weighted`, or `max`                  |\n| `fusion.k`            | int    | `60`    | RRF rank constant                                           |\n| `deduplicate`         | bool   | `true`  | Remove duplicate results                                    |\n| `limit`               | int    | `15`    | Final number of results                                     |\n| `include_score`       | bool   | `false` | Include relevance scores in results                         |\n| `return_full_content` | bool   | `false` | Return full document content instead of just matched chunks |\n| `reranking.model`     | string | —       | Reranking model reference                                   |\n| `reranking.top_k`     | int    | (`limit`) | Only rerank top K results. Defaults to the results `limit` when set.  |\n| `reranking.threshold` | float  | `0.5`   | Minimum relevance score after reranking                     |\n| `reranking.criteria`  | string | —       | Custom relevance guidance for the reranking model           |\n","_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md":"---\ntitle: \"Scheduler Tool\"\ndescription: \"Schedule instructions to run at a time or on a recurring interval.\"\nkeywords: docker agent, ai agents, tools, toolsets, scheduler tool, cron\nlinkTitle: \"Scheduler\"\nweight: 135\ncanonical: https://docs.docker.com/ai/docker-agent/tools/scheduler/\n---\n\n_Schedule instructions to run at a time or on a recurring interval._\n\n## Overview\n\nThe scheduler toolset lets an agent make something happen at a chosen time or on a repeating cadence during a session. You give it an instruction and a schedule; when the schedule is due, the instruction is delivered back to the agent, which then carries out the action with its normal tools (`shell`, `api`, `fetch`, and so on).\n\nThe scheduler does not run shell or API calls itself. When a schedule fires it injects the instruction into the agent loop via the runtime's recall mechanism — the same primitive [`background_jobs`](../background-jobs/index.md) uses to report completed work — and the agent decides how to act. This keeps every action under the agent's normal tools and permissions rather than adding a second, unattended\ncommand runner.\n\n> [!NOTE]\n> Schedules only fire while the session is running (interactive TUI or a server mode) and are not persisted across restarts. Scheduling requires a host that supports recall; if it does not, `create_schedule` returns an error.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: scheduler\n```\n\nNo configuration options.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `create_schedule` | Register an instruction to run at a time or interval. |\n| `list_schedules` | List active schedules with their id, spec, and next fire time. |\n| `cancel_schedule` | Remove a schedule by id. |\n\n### `create_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `prompt` | Yes | The instruction to deliver to the agent when the schedule fires. |\n| `when` | Yes | When to fire (see [Schedule specs](#schedule-specs)). |\n| `name` | No | Optional human-readable label. |\n\nReturns the new schedule's id and its next fire time.\n\n### `cancel_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `id` | Yes | The id of the schedule to cancel (from `create_schedule` or `list_schedules`). |\n\n## Schedule specs\n\nThe `when` argument accepts:\n\n| Form | Meaning | Example |\n| --- | --- | --- |\n| `in:<duration>` | One-shot, after a delay | `in:10m` |\n| `at:<RFC3339>` | One-shot, at an absolute future time | `at:2026-07-14T09:00:00Z` |\n| `every:<duration>` | Recurring, at a fixed interval | `every:1h` |\n| `minutely` / `hourly` / `daily` / `weekly` | Recurring preset intervals | `hourly` |\n\nDurations use Go's duration syntax (`30s`, `15m`, `2h`). Preset and `every:` intervals are measured from the schedule's creation time (for example `hourly` fires every hour after it is created), not aligned to wall-clock slots.\n\n> [!IMPORTANT]\n> **Recurring schedules have a one-minute minimum.** Every fire injects a message into the agent loop and typically costs an LLM turn, so `every:` values below `1m` are rejected — a typo such as `every:1s` in place of `every:1h` would otherwise become a runaway token burn. One-shot schedules (`in:` / `at:`) are not restricted, since they fire once.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A monitoring assistant\n    instruction: |\n      Every 15 minutes, run `git fetch` and tell me if origin/main moved.\n    toolsets:\n      - type: scheduler\n      - type: shell\n```\n\nThe agent calls:\n\n```text\ncreate_schedule(prompt=\"Run git fetch and report if origin/main moved\", when=\"every:15m\")\n```\n\nEvery 15 minutes it is reminded, runs the command with the `shell` tool, and reports back.\n\n> [!TIP]\n> **When to use**\n>\n> Use the scheduler for recurring monitoring, timed one-shots, and unattended housekeeping loops during a long-running session. For work that should run immediately and be awaited, use [`background_jobs`](../background-jobs/index.md) instead.\n","_vendor/github.com/docker/docker-agent/docs/tools/script/index.md":"---\ntitle: \"Script Tool\"\ndescription: \"Define custom shell scripts as named tools with typed parameters.\"\nkeywords: docker agent, ai agents, tools, toolsets, script tool\nlinkTitle: \"Script\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/tools/script/\n---\n\n_Define custom shell scripts as named tools with typed parameters._\n\n## Overview\n\nThe script tool lets you define custom shell scripts as named tools. Unlike the generic [shell tool](../shell/index.md) where the agent writes the command, script tools execute predefined commands — ideal for exposing safe, well-scoped operations with descriptive names.\n\n## Configuration\n\n### Simple Scripts\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      run_tests:\n        cmd: task test\n        description: Run the project test suite\n      lint:\n        cmd: task lint\n        description: Run the linter\n```\n\n### Scripts with Parameters\n\nUse `${param}` interpolation and JSON Schema to define typed arguments:\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      deploy:\n        cmd: ./scripts/deploy.sh ${env}\n        description: Deploy to an environment\n        args:\n          env:\n            type: string\n            enum: [staging, production]\n        required: [env]\n```\n\n## Properties\n\n| Property                          | Type   | Description                                                |\n| --------------------------------- | ------ | ---------------------------------------------------------- |\n| `shell.<name>.cmd`                | string | Shell command to execute (supports `${arg}` interpolation) |\n| `shell.<name>.description`        | string | Description shown to the model                             |\n| `shell.<name>.args`               | object | Parameter definitions (JSON Schema properties)             |\n| `shell.<name>.required`           | array  | Required parameter names                                   |\n| `shell.<name>.env`                | object | Environment variables for this script                      |\n| `shell.<name>.working_dir`        | string | Working directory for script execution                     |\n\n> [!TIP]\n> **Script vs. Shell**\n>\n> Use the [shell tool](../shell/index.md) when the agent needs to run arbitrary commands. Use the script tool when you want to expose specific, predefined operations with clear names and typed parameters — giving the agent less freedom but more safety.\n","_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md":"---\ntitle: \"Session Context Tool\"\ndescription: \"Reference a previous session as context in the current one.\"\nkeywords: docker agent, ai agents, tools, toolsets, session context tool\nlinkTitle: \"Session Context\"\nweight: 210\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_context/\n---\n\n_Reference a previous session as context, without manual export/import._\n\n## Overview\n\nThe `session_context` toolset lets an agent discover earlier sessions and pull one in as context for the current session. It removes the manual workaround of exporting a conversation to HTML and re-attaching it with an `@` mention.\n\nThe tool surface is two read-only tools:\n\n| Tool            | Description                                                                                                  |\n| --------------- | ------------------------------------------------------------------------------------------------------------ |\n| `list_sessions` | List previous sessions (most recent first) with id, title, creation time and message count.                  |\n| `read_session`  | Return the transcript of a previous session, by id or by a relative reference like `-1`.                      |\n\nThe session the agent is currently running in is never listed by `list_sessions` and cannot be read by `read_session` (a circular reference returns an error).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_context\n```\n\nNo configuration options. Both tools are read-only and operate against the same session store the runtime already uses for persistence.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that may browse but never pull a full transcript into context.\ntoolsets:\n  - type: session_context\n    tools:\n      - list_sessions\n```\n\n## Selecting a session\n\n`read_session` accepts either form:\n\n- A concrete id returned by `list_sessions`, e.g. `read_session(\"a1b2c3...\")`.\n- A relative reference: `-1` is the most recent session, `-2` the second most recent, and so on. Relative references resolve against the same ordering `list_sessions` uses (most recent first), excluding sub-sessions.\n\n## Transcript size\n\nA long session could overflow the current context window, so `read_session` caps the rendered transcript. When a transcript is larger than the budget, the oldest messages are dropped (the most recent are usually the most useful for continuing work) and a note records how many were omitted:\n\n```text\n[12 earlier message(s) omitted to fit the context budget; showing the most recent 8]\n```\n\n## Notes\n\n- `list_sessions` defaults to 20 sessions and is capped at 100; pass `limit` to request fewer.\n- `read_session` returns an error when the session is not found, when the reference cannot be resolved, or when it points at the current session.\n- Both tools are read-only: they never modify, branch, or delete sessions.\n\n## Example\n\nSee [`examples/session_context.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_context.yaml) for a complete working example.\n","_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md":"---\ntitle: \"Session Plan Tool\"\ndescription: \"Per-session plan tracker for the draft, review, execute workflow.\"\nkeywords: docker agent, ai agents, tools, toolsets, session plan tool\nlinkTitle: \"Session Plan\"\nweight: 160\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_plan/\n---\n\n_Per-session plan tracker for the \"draft, review, execute\" workflow._\n\n## Overview\n\nThe `session_plan` toolset gives one agent a place to write a plan for the current session, signal that the plan is ready, and let the host route the next turn to an executing agent.\n\nDifferent from the [`plan` toolset](../plan/index.md) — `plan` is for shared, named plans multiple agents collaborate on over many sessions. `session_plan` is for one ephemeral plan per session, scoped to that session by ID.\n\nPlans live as Markdown files under:\n\n```text\n~/.cagent/session_plans/<session-id>.md\n```\n\nThe tool surface is three tools:\n\n| Tool                 | Description                                                                                          |\n| -------------------- | ---------------------------------------------------------------------------------------------------- |\n| `write_session_plan` | Create or replace this session's plan as markdown. There's exactly one plan per session.             |\n| `read_session_plan`  | Read the plan written for the current session and return it as markdown.                             |\n| `exit_plan_mode`     | Signal that the plan is ready for review. Does not switch agents on its own.                         |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_plan\n```\n\nNo configuration options. The plan path is derived from the session ID; the agent does not name plans.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that consumes a plan but should not be able to (re)write or finalize one.\ntoolsets:\n  - type: session_plan\n    tools:\n      - read_session_plan\n```\n\n## When to call exit_plan_mode\n\nCall `exit_plan_mode` once the plan is complete and you do not intend to change it on the next turn. It validates that a plan exists for the session and returns a \"ready for review\" tool result. It does **not** switch agents or solicit user approval on its own — the host application owns the next-turn routing (for example, by reading the tool result, by a UI affordance the user toggles, or by a `handoff` declared on the agent).\n\nThis separation keeps the tool reusable across UIs: a CLI that prints tool results inline, a chat UI with a plan-mode toggle, and a server that auto-routes the next turn through a `handoff` can all consume the same signal without one stepping on another.\n\n## Storage and cleanup\n\n- Plans are markdown files written atomically (temp + rename), so concurrent readers — in this process or another — never observe a partial write.\n- A best-effort sweep on first use of the toolset removes plan files older than 30 days under the plans directory. Stranded plans for long-gone sessions do not accumulate.\n- The session ID identifies the file directly. There is no in-process mutex or revision counter, because two sessions cannot map to the same path.\n\n## Events\n\nA `session_plan_updated` event is emitted whenever `write_session_plan` succeeds:\n\n```json\n{\n  \"type\": \"session_plan_updated\",\n  \"session_id\": \"...\",\n  \"path\": \"/Users/.../.cagent/session_plans/<session-id>.md\",\n  \"content\": \"# my plan\\n...\",\n  \"agent_name\": \"planner\"\n}\n```\n\nEmbedders that render the plan inline can subscribe and update without re-reading the file.\n\n## Managing session plans from the host\n\nA session plan belongs to its session: hosts can read and export it, never change it.\n\n- **CLI** — the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group lists, reads (`get --session <session-id>`), and exports session plans alongside shared plans. Mutations (`update`, `status`, `delete`) are refused with an `unsupported` error explaining the ownership rule.\n- **TUI** — the `/plans` browser (see the [plan toolset docs](../plan/index.md#the-plans-browser-in-the-tui) for the full keybinding table) includes the **current session's** plan as the `session` scope row; plans of other sessions are never enumerated. Its identity is the session ID and its version column shows `-` — session plans have no versions. <kbd>Enter</kbd> opens the detail view (scope, session ID, update time, scrollable markdown) and <kbd>x</kbd> exports to `session-plan-<short-id>.md` in the working directory (refusing to overwrite an existing file). <kbd>e</kbd> opens the plan body in your external editor (`$VISUAL` or `$EDITOR`) for editing — the write is unguarded and last-write-wins by design. Status and delete visibly report that session plans don't support them (session plans belong to their session and carry no shared-plan metadata). The browser refreshes live on the `session_plan_updated` event, so a plan the agent just wrote appears without reopening.\n\n## Example\n\nA two-agent workflow: `root` executes, `planner` plans. `/plan` hands off to the planner; `exit_plan_mode` signals \"ready\", and the host decides what happens next.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Executes approved plans\n    instruction: |\n      You execute plans the planner has handed off. When you see a message\n      that a plan has been approved, read it with read_session_plan and work\n      through its steps in order.\n    toolsets:\n      - type: session_plan\n        tools:\n          - read_session_plan\n      - type: filesystem\n      - type: shell\n    commands:\n      plan:\n        description: \"Switch to the planner\"\n        agent: planner\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Investigates and writes plans for review\n    instruction: |\n      Investigate the user's request, then write the plan with\n      write_session_plan. Iterate with the user until the plan is complete,\n      then call exit_plan_mode to mark it ready for review.\n    toolsets:\n      - type: session_plan\n      - type: filesystem\n        readonly: true\n      - type: user_prompt\n```\n\nSee [`examples/session_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_session_plan` and `exit_plan_mode` return a \"no plan written yet\" error when called before `write_session_plan`.\n- `write_session_plan` validates the session ID and refuses to write anything that could escape the plans directory; in practice the runtime generates UUIDs so this only triggers if an embedder supplies a hand-crafted ID.\n\n> [!TIP]\n> **session_plan vs. plan vs. todo vs. tasks**\n>\n> Use **session_plan** when one agent drafts an approach for the user to review before another agent executes it (ephemeral, one per session). Use [plan](../plan/index.md) for shared, named plans multiple agents collaborate on over many sessions. Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md":"---\ntitle: \"Shell Tool\"\ndescription: \"Execute arbitrary shell commands in the user's environment.\"\nkeywords: docker agent, ai agents, tools, toolsets, shell tool\nlinkTitle: \"Shell\"\nweight: 20\ncanonical: https://docs.docker.com/ai/docker-agent/tools/shell/\n---\n\n_Execute arbitrary shell commands in the user's environment._\n\n## Overview\n\nThe shell tool allows agents to execute arbitrary shell commands synchronously. This is one of the most powerful tools — it lets agents run builds, install dependencies, query APIs, and interact with the system. Each call runs in a fresh, isolated shell session — no state persists between calls.\n\nCommands have a default 30-second timeout and require user confirmation unless `--yolo` is used. For servers, watchers, and other long-running commands, add the [`background_jobs`](../background-jobs/index.md) toolset alongside `shell`.\n\n### Shell interpreter detection\n\nThe shell tool automatically detects and names the resolved shell interpreter (e.g., `bash`, `zsh`, `powershell`, `pwsh`, `cmd`) in its description to the model, along with the operating system (Linux, macOS, Windows). This helps models use the correct shell syntax for the host environment.\n\nFor example:\n\n- On Linux with bash: \"Executes the given shell command with bash on Linux.\"\n- On Windows with PowerShell: \"Executes the given shell command with powershell on Windows. Use Windows PowerShell 5.1 syntax: chain commands with \";\" (not \"&&\"), and avoid POSIX commands/flags like \"ls -la\".\"\n\nThis reduces wasted turns where models assume POSIX syntax on Windows or vice versa.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                          |\n| -------------- | ------- | --------------------------------------------------------------------------------------------------- |\n| `env`          | object  | Environment variables to set for all shell commands                                                 |\n| `safer`        | boolean | Deprecated and ignored — shell commands are always classified now (see [Command classification](#command-classification)). Kept so existing YAMLs still parse. |\n| `sudo_askpass` | boolean | Opt in to prompting for a `sudo` password (see [Sudo support](#sudo-support)). Default `false`.     |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: shell\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Command classification\n\nEvery shell command is classified against an embedded taxonomy before the approval decision — no opt-in required:\n\n- **Destructive matches** (`rm -rf <path>`, `docker volume rm`, `mkfs`, `dd if=… of=/dev/<disk>`, …) are labelled `destructive` with a `blast_radius` (`low` / `medium` / `high`) and a `category` tag. The TUI confirmation dialog renders the blast radius with a color badge.\n- **Known-safe reads** (`ls`, `cat`, `git status`, `git diff`, `docker ps`, `docker logs`, `kubectl get`, …) are labelled `safe`.\n- **Everything else** is labelled `unknown`.\n\nThe session's [safety mode](../../configuration/permissions/index.md#safety-modes) decides what each label means: `strict` asks about everything, `balanced` auto-runs safe commands and asks about destructive/unknown ones, `restricted` auto-runs safe commands and denies destructive/unknown ones without asking (fail-closed for unattended runs), `autonomous` runs everything. Custom permission rules always win over the mode.\n\nCompound shell (`a && b`, `a; b`, `a | b`) is never matched against the safe allowlist; any destructive segment falls through to ask. The full taxonomy lives in [`pkg/safety/safety_patterns.json`](https://github.com/docker/docker-agent/blob/main/pkg/safety/safety_patterns.json).\n\nSee [`examples/safety_modes.yaml`](https://github.com/docker/docker-agent/blob/main/examples/safety_modes.yaml) for a full example. The legacy `safer: true` toolset flag is deprecated and ignored.\n\n### Sudo support\n\nBy default a shell command has no controlling terminal, so a `sudo` command that needs a password hangs until it times out (the agent usually gives up and falls back to printing manual instructions).\n\nSet `sudo_askpass: true` to enable a sudo privilege escalation flow:\n\n```yaml\ntoolsets:\n  - type: shell\n    sudo_askpass: true\n```\n\nWhen enabled, `sudo` commands prompt you for your password through the host UI (the input is masked). The password is handed to `sudo` over a private, per-session socket via the standard `SUDO_ASKPASS` mechanism — it is never written to the command line, the logs, or stored by the agent.\n\nThe bridge environment variables (`SUDO_ASKPASS`, `CAGENT_ASKPASS_SOCKET`, `CAGENT_ASKPASS_TOKEN`) are added only to commands that invoke `sudo`, but within such a command they are visible to every child process, not just `sudo`. They carry a socket path and a session token, not the password; the socket lives in a `0700` directory, so only your own user can reach it.\n\nNotes and limitations:\n\n- Unix only. The flag has no effect on Windows.\n- Interactive UI only. In headless / non-interactive runs the prompt is declined automatically and `sudo` fails as before.\n- Only a bare `sudo ...` invocation in a POSIX shell (`sh`, `bash`, `zsh`, ...) is handled. `sudo` called by absolute path (`/usr/bin/sudo`), via `env sudo`, from inside a nested script, or under a non-POSIX shell (e.g. `fish`) is not intercepted and behaves as before.\n- Caching is `sudo`'s own. Because each shell tool call runs in a fresh shell with no controlling terminal, `sudo`'s credential cache does not persist across separate tool calls: you are prompted once per shell command that uses `sudo`. Within a single command, multiple `sudo` calls (e.g. `sudo a && sudo b`) usually share one prompt, subject to `sudo`'s own timestamp configuration.\n- The prompt must be answered within the command's timeout; raise the `timeout` parameter for `sudo` commands that may wait on input.\n- Prompts are serialized: if a single command runs two `sudo` calls in parallel (e.g. `sudo a & sudo b`), the second waits for the first prompt to be answered rather than opening two dialogs at once.\n\n## Available Tools\n\nThe shell toolset exposes one tool:\n\n| Tool Name | Description                                                                  |\n| --------- | ---------------------------------------------------------------------------- |\n| `shell`   | Run a command synchronously and return its combined output when it finishes. |\n\n### `shell` parameters\n\n| Parameter | Type    | Required | Description                                                               |\n| --------- | ------- | -------- | ------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute.                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                   |\n| `timeout` | integer | ✗        | Per-call execution timeout in seconds (default: `30`).                    |\n\n> [!WARNING]\n> **Safety**\n>\n> The shell tool gives agents full access to the system shell. Always set `max_iterations` on agents that use the shell tool to prevent infinite loops. A value of 20–50 is typical for development agents. Use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing shell commands. Use `--yolo` to auto-approve all tool calls.\n","_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md":"---\ntitle: \"Tasks Tool\"\ndescription: \"Persistent task database with priorities and dependencies, shared across sessions.\"\nkeywords: docker agent, ai agents, tools, toolsets, tasks tool\nlinkTitle: \"Tasks\"\nweight: 180\ncanonical: https://docs.docker.com/ai/docker-agent/tools/tasks/\n---\n\n_Persistent task database with priorities and dependencies, shared across sessions._\n\n## Overview\n\nThe tasks tool provides a persistent task database that survives across agent sessions. Unlike the [Todo tool](../todo/index.md), which maintains an in-memory task list for the current session only, the tasks tool stores tasks in a JSON file on disk so they can be accessed and updated across multiple sessions. Tasks support priorities and dependencies — a task is _blocked_ until every task it depends on is `done`.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: tasks\n    path: ./tasks.json  # Optional: custom database path\n```\n\n### Options\n\n| Property | Type   | Default       | Description                                                                                                                  |\n| -------- | ------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `path`   | string | `tasks.json`  | Path to the JSON task database. Relative paths resolve against the agent config directory (or `--working-dir` when set).     |\n\n## Available Tools\n\nThe tasks toolset exposes these tools:\n\n| Tool Name           | Description                                                                                                              |\n| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `create_task`       | Create a new task with a title, description (or markdown file path), optional priority, and optional dependencies.       |\n| `get_task`          | Get full details of a single task by ID, including its effective status (`blocked` if any dependency is not `done`).     |\n| `update_task`       | Update a task's title, description, priority, status, or dependency list.                                                |\n| `delete_task`       | Delete a task by ID. Also removes it from other tasks' dependency lists.                                                 |\n| `list_tasks`        | List tasks sorted by priority (critical first) with blocked tasks last. Optionally filter by status or priority.         |\n| `next_task`         | Return the highest-priority actionable task — one that is not blocked and not done. Great for \"what should I work on?\". |\n| `add_dependency`    | Add a dependency: a task is blocked until the task it depends on is `done`.                                              |\n| `remove_dependency` | Remove a dependency from a task.                                                                                         |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    toolsets:\n      - type: tasks\n        path: ./project-tasks.json\n```\n\n> [!TIP]\n> **Tasks vs. Todo**\n>\n> Use the **tasks** tool when you need persistence across sessions, priorities, or dependencies (e.g., long-running projects, recurring work). Use the [todo tool](../todo/index.md) for ephemeral, session-scoped task lists.\n","_vendor/github.com/docker/docker-agent/docs/tools/think/index.md":"---\ntitle: \"Think Tool\"\ndescription: \"Step-by-step reasoning scratchpad for planning and decision-making.\"\nkeywords: docker agent, ai agents, tools, toolsets, think tool\nlinkTitle: \"Think\"\nweight: 140\ncanonical: https://docs.docker.com/ai/docker-agent/tools/think/\n---\n\n_Step-by-step reasoning scratchpad for planning and decision-making._\n\n## Overview\n\nThe think tool is a reasoning scratchpad that lets agents think step-by-step before acting. The agent can write its thoughts without producing visible output to the user — ideal for planning complex tasks, breaking down problems, and reasoning through multi-step solutions.\n\nThis is a lightweight tool with no side effects. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: think\n```\n\nNo configuration options.\n\n> [!TIP]\n> **When to use**\n>\n> Use the think tool with models that don't have native reasoning capabilities. If your model already supports a [thinking budget](../../configuration/models/index.md#thinking-budget), you likely don't need this tool.\n","_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md":"---\ntitle: \"Todo Tool\"\ndescription: \"Task list management for complex multi-step workflows.\"\nkeywords: docker agent, ai agents, tools, toolsets, todo tool\nlinkTitle: \"Todo\"\nweight: 170\ncanonical: https://docs.docker.com/ai/docker-agent/tools/todo/\n---\n\n_Task list management for complex multi-step workflows._\n\n## Overview\n\nThe todo tool provides task list management. Agents can create, update, list, and track progress on tasks with status tracking (pending, in-progress, completed). Useful for complex multi-step workflows where the agent needs to stay organized and ensure all steps are completed.\n\n## Available Tools\n\n| Tool           | Description                              |\n| -------------- | ---------------------------------------- |\n| `create_todo`  | Create a new task                        |\n| `create_todos` | Create multiple tasks at once            |\n| `update_todos` | Update status of one or more tasks       |\n| `list_todos`   | List all current tasks with their status |\n\n### Task Statuses\n\n| Status        | Description                  |\n| ------------- | ---------------------------- |\n| `pending`     | Task has not been started    |\n| `in-progress` | Task is currently being done |\n| `completed`   | Task is finished             |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: todo\n```\n\n### Options\n\n| Property | Type    | Default | Description                                                             |\n| -------- | ------- | ------- | ----------------------------------------------------------------------- |\n| `shared` | boolean | `false` | When `true`, todos are shared across all agents in a multi-agent config |\n\n### Shared Todos\n\nIn multi-agent setups, enable shared todos so all agents can see and update the same task list:\n\n```yaml\ntoolsets:\n  - type: todo\n    shared: true\n```\n","_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md":"---\ntitle: \"Transfer Task Tool\"\ndescription: \"Delegate tasks to sub-agents in multi-agent setups.\"\nkeywords: docker agent, ai agents, tools, toolsets, transfer task tool\nlinkTitle: \"Transfer Task\"\nweight: 80\ncanonical: https://docs.docker.com/ai/docker-agent/tools/transfer-task/\n---\n\n_Delegate tasks to sub-agents in multi-agent setups._\n\n## Overview\n\nThe `transfer_task` tool allows an agent to delegate tasks to specialized sub-agents and receive their results. This is the core mechanism for multi-agent orchestration.\n\n**You don't need to add it manually** — it's automatically available when an agent has `sub_agents` configured.\n\n## Configuration\n\nThe tool is enabled implicitly when `sub_agents` is set:\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Coordinates work across specialists\n    instruction: Analyze requests and delegate to the right specialist.\n    sub_agents: [developer, researcher]\n\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: Write clean, production-ready code.\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Search for information online.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\nThe coordinator agent automatically gets a `transfer_task` tool that can delegate to `developer` or `researcher`.\n\n## Tool Interface\n\nThe `transfer_task` tool takes three parameters:\n\n| Parameter         | Type   | Required | Description                                                                                 |\n| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to delegate to. Must be listed under the caller's `sub_agents`.        |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.                        |\n| `expected_output` | string | ✓        | Description of the result/format the caller expects back.                                   |\n\nThe call blocks until the sub-agent returns its result, which becomes the tool's response. For non-blocking parallel delegation, use [`background_agents`](../background-agents/index.md) instead.\n\n## Delegation Limits\n\nSub-agents can have `sub_agents` of their own, so multi-level delegation chains are supported. Two runtime guards keep chains sane, applied to both `transfer_task` and `run_background_agent`:\n\n- **Cycles are rejected.** A delegation targeting an agent that is already part of the active delegation chain (for example `a -> b -> a`) fails with an error naming the cycle.\n- **Depth is capped at 10 nested delegations.** The root agent delegating to its first sub-agent counts as depth 1; a call that would exceed the cap fails with an error stating the attempted depth.\n\nA rejected delegation returns a tool error to the calling agent and never starts the sub-agent.\n\n> [!TIP]\n> **See also**\n>\n> For parallel task delegation, see [Background Agents](../background-agents/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n","_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md":"---\ntitle: \"User Prompt Tool\"\ndescription: \"Ask the user questions and collect interactive input during agent execution.\"\nkeywords: docker agent, ai agents, tools, toolsets, user prompt tool\nlinkTitle: \"User Prompt\"\nweight: 190\ncanonical: https://docs.docker.com/ai/docker-agent/tools/user-prompt/\n---\n\n_Ask the user questions and collect interactive input during agent execution._\n\n## Overview\n\nThe user prompt tool allows agents to ask questions and collect input from users during execution. This enables interactive workflows where the agent needs clarification, confirmation, or additional information before proceeding.\n\n> [!NOTE]\n> **When to Use**\n>\n> - When the agent needs clarification before proceeding\n> - Collecting credentials or configuration values\n> - Presenting choices and getting user decisions\n> - Confirming destructive or important actions\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Interactive assistant\n    instruction: |\n      You are a helpful assistant. When you need information\n      from the user, use the user_prompt tool to ask them.\n    toolsets:\n      - type: user_prompt\n      - type: filesystem\n      - type: shell\n```\n\n## Tool Interface\n\nThe `user_prompt` tool takes these parameters:\n\n| Parameter | Type   | Required | Description                                                                                        |\n| --------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |\n| `message` | string | ✓        | The question or prompt to display.                                                                 |\n| `title`   | string | ✗        | Optional title for the dialog window in the TUI. Defaults to `\"Question\"` when not provided.       |\n| `schema`  | object | ✗        | JSON Schema defining the expected response structure (object or primitive).                        |\n\n## Response Format\n\nThe tool returns a JSON response:\n\n```json\n{\n  \"action\": \"accept\",\n  \"content\": {\n    \"field1\": \"user value\",\n    \"field2\": true\n  }\n}\n```\n\n### Action Values\n\n| Action    | Meaning                                    |\n| --------- | ------------------------------------------ |\n| `accept`  | User provided a response (check `content`) |\n| `decline` | User declined to answer                    |\n| `cancel`  | User cancelled the prompt                  |\n\n## Schema Examples\n\n### Simple String Input\n\n```json\n{\n  \"type\": \"string\",\n  \"title\": \"API Key\",\n  \"description\": \"Enter your API key\"\n}\n```\n\n### Multiple Choice\n\n```json\n{\n  \"type\": \"string\",\n  \"enum\": [\"development\", \"staging\", \"production\"],\n  \"title\": \"Environment\",\n  \"description\": \"Select the target environment\"\n}\n```\n\n### Boolean Confirmation\n\n```json\n{\n  \"type\": \"boolean\",\n  \"title\": \"Confirm\",\n  \"description\": \"Are you sure you want to proceed?\"\n}\n```\n\n### Object with Multiple Fields\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"username\": {\n      \"type\": \"string\",\n      \"description\": \"Your username\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"description\": \"Your password\"\n    },\n    \"remember\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remember credentials\"\n    }\n  },\n  \"required\": [\"username\", \"password\"]\n}\n```\n\n### Number Input\n\n```json\n{\n  \"type\": \"integer\",\n  \"title\": \"Port Number\",\n  \"description\": \"Enter the port number (1024-65535)\",\n  \"minimum\": 1024,\n  \"maximum\": 65535\n}\n```\n\n## Example Usage\n\nHere's how an agent might use the user prompt tool:\n\n```text\nAgent: I need to deploy this application. Let me ask which environment to target.\n\n[Calls user_prompt with message: \"Which environment should I deploy to?\"\n and schema with enum: [\"development\", \"staging\", \"production\"]]\n\nUser selects: \"staging\"\n\nAgent: Great, I'll deploy to staging. Let me confirm this action.\n\n[Calls user_prompt with message: \"Deploy to staging? This will replace the current version.\"\n and schema with type: \"boolean\"]\n\nUser confirms: true\n\nAgent: Deploying to staging...\n```\n\n## UI Presentation\n\nHow the prompt appears depends on the interface:\n\n- **TUI**: Displays an interactive dialog with appropriate input controls\n- **CLI (exec mode)**: Prints the prompt and reads from stdin\n- **API/MCP**: Returns an elicitation request to the client\n\n> [!TIP]\n> **Best Practice**\n>\n> Provide clear, concise messages. Include context about why you're asking and what the information will be used for. Use schemas with descriptions to guide users on expected input format.\n\n## Handling Responses\n\nThe agent should handle all possible actions:\n\n- **accept**: Process the `content` and continue\n- **decline**: Acknowledge and try an alternative approach or explain what's needed\n- **cancel**: Stop the current operation gracefully\n\n> [!WARNING]\n> **Context Requirement**\n>\n> The user prompt tool requires an elicitation handler to be configured. It works in the TUI and CLI modes but may not be available in all contexts (e.g., some MCP client configurations).\n","_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md":"---\ntitle: \"Webhook Tool\"\ndescription: \"Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more.\"\nkeywords: docker agent, ai agents, tools, toolsets, webhook, slack, discord, telegram, ifttt, notifications\nlinkTitle: \"Webhook\"\nweight: 145\ncanonical: https://docs.docker.com/ai/docker-agent/tools/webhook/\n---\n\n_Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more._\n\n## Overview\n\nThe webhook toolset delivers a notification to a destination **you configure**. The\nagent supplies only the message text: it never sees or chooses the URL, because a\nwebhook URL is itself a credential (Slack and Mattermost embed a secret path,\nDiscord a token, IFTTT a key, Telegram a bot token).\n\nThis is not a general HTTP client — that is the [`api`](../api/index.md) toolset.\nThe webhook toolset owns *delivery*:\n\n- **At-least-once delivery.** Transient failures (`429`, `5xx`, network errors) are\n  retried with exponential backoff, honouring the server's `Retry-After`. A `4xx`\n  is permanent and fails immediately without wasting retries.\n- **Non-blocking.** The call returns as soon as the notification is queued, so a\n  slow or retrying endpoint never stalls the agent's turn. The agent is messaged\n  back **only if delivery ultimately fails**.\n- **Storm protection.** An identical message to the same destination inside a short\n  window is suppressed, and notifications are rate limited, so a looping agent\n  cannot flood a channel.\n- **Provider-shaped payloads.** Each service's wire format is applied for you.\n\n## Configuration\n\nThe destination lives in `webhook_config`. Use `${env.VAR}` for anything secret —\nvalues are expanded at call time and never stored in the config file.\n\n```yaml\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n| Field | Required | Description |\n| --- | --- | --- |\n| `url` | Yes | Webhook endpoint. Usually embeds a secret — prefer `${env.VAR}`. |\n| `provider` | No | Payload shape (default `generic`). |\n| `headers` | No | Extra headers, for endpoints authenticating with a token. |\n| `chat_id` | No | Destination chat — required for `provider: telegram`. |\n\n`timeout` on the toolset (seconds) overrides the per-request HTTP timeout.\n\n## Providers\n\n| Provider | Payload sent | Where the secret lives |\n| --- | --- | --- |\n| `slack`, `mattermost`, `rocketchat`, `googlechat`, `teams`, `generic` | `{\"text\": message}` | secret webhook URL |\n| `discord` | `{\"content\": message}` | token in the webhook URL |\n| `ifttt` | `{\"value1\": message, \"value2\": …, \"value3\": …}` | key in the webhook URL |\n| `telegram` | `{\"chat_id\": …, \"text\": message}` | bot token in the URL, plus `chat_id` |\n\nAliases are accepted: `msteams`/`microsoft_teams` → `teams`, `google_chat`/`gchat`\n→ `googlechat`, `rocket.chat` → `rocketchat`.\n\n### Per-service examples\n\n```yaml\n# Slack / Mattermost / Rocket.Chat — the URL is the credential\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n```yaml\n# Discord — the token is part of the webhook URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: discord\n      url: ${env.DISCORD_WEBHOOK_URL}\n```\n\n```yaml\n# Telegram — bot token in the URL, chat_id selects the destination chat\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: telegram\n      url: https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage\n      chat_id: \"123456789\"\n```\n\n```yaml\n# IFTTT — the key is part of the trigger URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: ifttt\n      url: https://maker.ifttt.com/trigger/build_failed/with/key/${env.IFTTT_KEY}\n```\n\n```yaml\n# Generic endpoint authenticating with a bearer token\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: generic\n      url: https://alerts.example.com/notify\n      headers:\n        Authorization: Bearer ${env.ALERTS_TOKEN}\n```\n\n## `send_webhook`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `message` | Yes | The message text to deliver. |\n| `value2`, `value3` | No | Extra IFTTT data fields (`provider: ifttt`). |\n\nReturns immediately once queued. On success nothing further happens; if delivery\nultimately fails, the agent receives a message saying so.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    instruction: If a check fails, notify the team with send_webhook.\n    toolsets:\n      - type: webhook\n        webhook_config:\n          provider: slack\n          url: ${env.SLACK_WEBHOOK_URL}\n```\n\n> [!NOTE]\n> Requests to non-public addresses are refused (the SSRF-safe HTTP client), and the\n> configured URL is never echoed back to the model or into error messages.\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md":"---\ntitle: Build checks\ndescription: |\n  BuildKit has built-in support for analyzing your build configuration based on\n  a set of pre-defined rules for enforcing Dockerfile and building best\n  practices.\nkeywords: buildkit, linting, dockerfile, frontend, rules\n---\n\nBuildKit has built-in support for analyzing your build configuration based on a\nset of pre-defined rules for enforcing Dockerfile and building best practices.\nAdhering to these rules helps avoid errors and ensures good readability of your\nDockerfile.\n\nChecks run as a build invocation, but instead of producing a build output, it\nperforms a series of checks to validate that your build doesn't violate any of\nthe rules. To run a check, use the `--check` flag:\n\n```console\n$ docker build --check .\n```\n\nTo learn more about how to use build checks, see\n[Checking your build configuration](https://docs.docker.com/build/checks/).\n\n<table>\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><a href=\"./stage-name-casing/\">StageNameCasing</a></td>\n      <td>Stage names should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-as-casing/\">FromAsCasing</a></td>\n      <td>The 'as' keyword should match the case of the 'from' keyword</td>\n    </tr>\n    <tr>\n      <td><a href=\"./no-empty-continuation/\">NoEmptyContinuation</a></td>\n      <td>Empty continuation lines will become errors in a future release</td>\n    </tr>\n    <tr>\n      <td><a href=\"./consistent-instruction-casing/\">ConsistentInstructionCasing</a></td>\n      <td>All commands within the Dockerfile should use the same casing (either upper or lower)</td>\n    </tr>\n    <tr>\n      <td><a href=\"./duplicate-stage-name/\">DuplicateStageName</a></td>\n      <td>Stage names should be unique</td>\n    </tr>\n    <tr>\n      <td><a href=\"./reserved-stage-name/\">ReservedStageName</a></td>\n      <td>Reserved words should not be used as stage names</td>\n    </tr>\n    <tr>\n      <td><a href=\"./json-args-recommended/\">JSONArgsRecommended</a></td>\n      <td>JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals</td>\n    </tr>\n    <tr>\n      <td><a href=\"./maintainer-deprecated/\">MaintainerDeprecated</a></td>\n      <td>The MAINTAINER instruction is deprecated, use a label instead to define an image author</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-arg-in-from/\">UndefinedArgInFrom</a></td>\n      <td>FROM command must use declared ARGs</td>\n    </tr>\n    <tr>\n      <td><a href=\"./workdir-relative-path/\">WorkdirRelativePath</a></td>\n      <td>Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-var/\">UndefinedVar</a></td>\n      <td>Variables should be defined before their use</td>\n    </tr>\n    <tr>\n      <td><a href=\"./multiple-instructions-disallowed/\">MultipleInstructionsDisallowed</a></td>\n      <td>Multiple instructions of the same type should not be used in the same stage</td>\n    </tr>\n    <tr>\n      <td><a href=\"./legacy-key-value-format/\">LegacyKeyValueFormat</a></td>\n      <td>Legacy key/value format with whitespace separator should not be used</td>\n    </tr>\n    <tr>\n      <td><a href=\"./redundant-target-platform/\">RedundantTargetPlatform</a></td>\n      <td>Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior</td>\n    </tr>\n    <tr>\n      <td><a href=\"./secrets-used-in-arg-or-env/\">SecretsUsedInArgOrEnv</a></td>\n      <td>Sensitive data should not be used in the ARG or ENV commands</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-default-arg-in-from/\">InvalidDefaultArgInFrom</a></td>\n      <td>Default value for global ARG results in an empty or invalid base image name</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-platform-flag-const-disallowed/\">FromPlatformFlagConstDisallowed</a></td>\n      <td>FROM --platform flag should not use a constant value</td>\n    </tr>\n    <tr>\n      <td><a href=\"./copy-ignored-file/\">CopyIgnoredFile</a></td>\n      <td>Attempting to Copy file that is excluded by .dockerignore</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-definition-description/\">InvalidDefinitionDescription (experimental)</a></td>\n      <td>Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-proto-casing/\">ExposeProtoCasing</a></td>\n      <td>Protocol in EXPOSE instruction should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-invalid-format/\">ExposeInvalidFormat</a></td>\n      <td>IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release</td>\n    </tr>\n  </tbody>\n</table>\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md":"---\ntitle: ConsistentInstructionCasing\ndescription: >-\n  All commands within the Dockerfile should use the same casing (either upper or lower)\naliases:\n  - /go/dockerfile/rule/consistent-instruction-casing/\n---\n\n## Output\n\n```text\nCommand 'EntryPoint' should be consistently cased\n```\n\n## Description\n\nInstruction keywords should use consistent casing (all lowercase or all\nuppercase). Using a case that mixes uppercase and lowercase, such as\n`PascalCase` or `snakeCase`, letters result in poor readability.\n\n## Examples\n\n❌ Bad: don't mix uppercase and lowercase.\n\n```dockerfile\nFrom alpine\nRun echo hello > /greeting.txt\nEntRYpOiNT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all uppercase.\n\n```dockerfile\nFROM alpine\nRUN echo hello > /greeting.txt\nENTRYPOINT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all lowercase.\n\n```dockerfile\nfrom alpine\nrun echo hello > /greeting.txt\nentrypoint [\"cat\", \"/greeting.txt\"]\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md":"---\ntitle: CopyIgnoredFile\ndescription: >-\n  Attempting to Copy file that is excluded by .dockerignore\naliases:\n  - /go/dockerfile/rule/copy-ignored-file/\n---\n\n## Output\n\n```text\nAttempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n```\n\n## Description\n\nWhen you use the Add or Copy instructions from within a Dockerfile, you should\nensure that the files to be copied into the image do not match a pattern\npresent in `.dockerignore`.\n\nFiles which match the patterns in a `.dockerignore` file are not present in the\ncontext of the image when it is built. Trying to copy or add a file which is\nmissing from the context will result in a build error.\n\n## Examples\n\nWith the given `.dockerignore` file:\n\n```text\n*/tmp/*\n```\n\n❌ Bad: Attempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./tmp/helloworld.txt /helloworld.txt\n```\n\n✅ Good: Copying a file which is not excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./forever/helloworld.txt /helloworld.txt\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md":"---\ntitle: DuplicateStageName\ndescription: >-\n  Stage names should be unique\naliases:\n  - /go/dockerfile/rule/duplicate-stage-name/\n---\n\n## Output\n\n```text\nDuplicate stage name 'foo-base', stage names should be unique\n```\n\n## Description\n\nDefining multiple stages with the same name results in an error because the\nbuilder is unable to uniquely resolve the stage name reference.\n\n## Examples\n\n❌ Bad: `builder` is declared as a stage name twice.\n\n```dockerfile\nFROM debian:latest AS builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS builder\n```\n\n✅ Good: stages have unique names.\n\n```dockerfile\nFROM debian:latest AS deb-builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS go-builder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md":"---\ntitle: ExposeInvalidFormat\ndescription: >-\n  IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release\naliases:\n  - /go/dockerfile/rule/expose-invalid-format/\n---\n\n## Output\n\n```text\nEXPOSE instruction should not define an IP address or host-port mapping, found '127.0.0.1:80:80'\n```\n\n## Description\n\nThe [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose) instruction\nin a Dockerfile is used to indicate which ports the container listens on at\nruntime. It should not include an IP address or host-port mapping, as this is\nnot the intended use of the `EXPOSE` instruction. Instead, it should only\nspecify the port number and optionally the protocol (TCP or UDP).\n\n> [!IMPORTANT]\n> This will become an error in a future release.\n\n## Examples\n\n❌ Bad: IP address and host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 127.0.0.1:80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n❌ Bad: Host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md":"---\ntitle: ExposeProtoCasing\ndescription: >-\n  Protocol in EXPOSE instruction should be lowercase\naliases:\n  - /go/dockerfile/rule/expose-proto-casing/\n---\n\n## Output\n\n```text\nDefined protocol '80/TcP' in EXPOSE instruction should be lowercase\n```\n\n## Description\n\nProtocol names in the [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose)\ninstruction should be specified in lowercase to maintain consistency and\nreadability. This rule checks for protocols that are not in lowercase and\nreports them.\n\n## Examples\n\n❌ Bad: protocol is not in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/TcP\n```\n\n✅ Good: protocol is in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/tcp\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md":"---\ntitle: FromAsCasing\ndescription: >-\n  The 'as' keyword should match the case of the 'from' keyword\naliases:\n  - /go/dockerfile/rule/from-as-casing/\n---\n\n## Output\n\n```text\n'as' and 'FROM' keywords' casing do not match\n```\n\n## Description\n\nWhile Dockerfile keywords can be either uppercase or lowercase, mixing case\nstyles is not recommended for readability. This rule reports violations where\nmixed case style occurs for a `FROM` instruction with an `AS` keyword declaring\na stage name.\n\n## Examples\n\n❌ Bad: `FROM` is uppercase, `AS` is lowercase.\n\n```dockerfile\nFROM debian:latest as builder\n```\n\n✅ Good: `FROM` and `AS` are both uppercase\n\n```dockerfile\nFROM debian:latest AS deb-builder\n```\n\n✅ Good: `FROM` and `AS` are both lowercase.\n\n```dockerfile\nfrom debian:latest as deb-builder\n```\n\n## Related errors\n\n- [`FileConsistentCommandCasing`](./consistent-instruction-casing.md)\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md":"---\ntitle: FromPlatformFlagConstDisallowed\ndescription: >-\n  FROM --platform flag should not use a constant value\naliases:\n  - /go/dockerfile/rule/from-platform-flag-const-disallowed/\n---\n\n## Output\n\n```text\nFROM --platform flag should not use constant value \"linux/amd64\"\n```\n\n## Description\n\nSpecifying `--platform` in the Dockerfile `FROM` instruction forces the image to build on only one target platform. This prevents building a multi-platform image from this Dockerfile and you must build on the same platform as specified in `--platform`.\n\nThe recommended approach is to:\n\n* Omit `FROM --platform` in the Dockerfile and use the `--platform` argument on the command line.\n* Use `$BUILDPLATFORM` or some other combination of variables for the `--platform` argument.\n* Stage name should include the platform, OS, or architecture name to indicate that it only contains platform-specific instructions.\n\n## Examples\n\n❌ Bad: using a constant argument for `--platform`\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using the default platform\n\n```dockerfile\nFROM alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using a meta variable\n\n```dockerfile\nFROM --platform=${BUILDPLATFORM} alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: used in a multi-stage build with a target architecture\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS build_amd64\n...\n\nFROM --platform=linux/arm64 alpine AS build_arm64\n...\n\nFROM build_${TARGETARCH} AS build\n...\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md":"---\ntitle: InvalidDefaultArgInFrom\ndescription: >-\n  Default value for global ARG results in an empty or invalid base image name\naliases:\n  - /go/dockerfile/rule/invalid-default-arg-in-from/\n---\n\n## Output\n\n```text\nUsing the global ARGs with default values should produce a valid build.\n```\n\n## Description\n\nAn `ARG` used in an image reference should be valid when no build arguments are used. An image build should not require `--build-arg` to be used to produce a valid build.\n\n## Examples\n\n❌ Bad: don't rely on an ARG being set for an image reference to be valid\n\n```dockerfile\nARG TAG\nFROM busybox:${TAG}\n```\n\n✅ Good: include a default for the ARG\n\n```dockerfile\nARG TAG=latest\nFROM busybox:${TAG}\n```\n\n✅ Good: ARG can be empty if the image would be valid with it empty\n\n```dockerfile\nARG VARIANT\nFROM busybox:stable${VARIANT}\n```\n\n✅ Good: Use a default value if the build arg is not present\n\n```dockerfile\nARG TAG\nFROM alpine:${TAG:-3.14}\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md":"---\ntitle: InvalidDefinitionDescription\ndescription: >-\n  Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\naliases:\n  - /go/dockerfile/rule/invalid-definition-description/\n---\n\n> [!NOTE]\n> This check is experimental and is not enabled by default. To enable it, see\n> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).\n\n## Output\n\n```text\nComment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\n```\n\n## Description\n\nThe [`--call=outline`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nand [`--call=targets`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nflags for the `docker build` command print descriptions for build targets and arguments.\nThe descriptions are generated from [Dockerfile comments](https://docs.docker.com/reference/cli/docker/buildx/build/#descriptions)\nthat immediately precede the `FROM` or `ARG` instruction\nand that begin with the name of the build stage or argument.\nFor example:\n\n```dockerfile\n# build-cli builds the CLI binary\nFROM alpine AS build-cli\n# VERSION controls the version of the program\nARG VERSION=1\n```\n\nIn cases where preceding comments are not meant to be descriptions,\nadd an empty line or comment between the instruction and the preceding comment.\n\n## Examples\n\n❌ Bad: A non-descriptive comment on the line preceding the `FROM` command.\n\n```dockerfile\n# a non-descriptive comment\nFROM scratch AS base\n\n# another non-descriptive comment\nARG VERSION=1\n```\n\n✅ Good: An empty line separating non-descriptive comments.\n\n```dockerfile\n# a non-descriptive comment\n\nFROM scratch AS base\n\n# another non-descriptive comment\n\nARG VERSION=1\n```\n\n✅ Good: Comments describing `ARG` keys and stages immediately proceeding the command.\n\n```dockerfile\n# base is a stage for compiling source\nFROM scratch AS base\n# VERSION This is the version number.\nARG VERSION=1\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md":"---\ntitle: JSONArgsRecommended\ndescription: >-\n  JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\naliases:\n  - /go/dockerfile/rule/json-args-recommended/\n---\n\n## Output\n\n```text\nJSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\n```\n\n## Description\n\n`ENTRYPOINT` and `CMD` instructions both support two different syntaxes for\narguments:\n\n- Shell form: `CMD my-cmd start`\n- Exec form: `CMD [\"my-cmd\", \"start\"]`\n\nWhen you use shell form, the executable runs as a child process to a shell,\nwhich doesn't pass signals. This means that the program running in the\ncontainer can't detect OS signals like `SIGTERM` and `SIGKILL` and respond to\nthem correctly.\n\n## Examples\n\n❌ Bad: the `ENTRYPOINT` command doesn't receive OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT my-program start\n# entrypoint becomes: /bin/sh -c my-program start\n```\n\nTo make sure the executable can receive OS signals, use the exec form for `CMD`\nand `ENTRYPOINT`, which lets you run the executable as the main process (`PID\n1`) in the container, avoiding a shell parent process.\n\n✅ Good: the `ENTRYPOINT` receives OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"my-program\", \"start\"]\n# entrypoint becomes: my-program start\n```\n\nNote that running programs as PID 1 means the program now has the special\nresponsibilities and behaviors associated with PID 1 in Linux, such as reaping\nchild processes.\n\n### Workarounds\n\nThere might still be cases when you want to run your containers under a shell.\nWhen using exec form, shell features such as variable expansion, piping (`|`)\nand command chaining (`&&`, `||`, `;`), are not available. To use such\nfeatures, you need to use shell form.\n\nHere are some ways you can achieve that. Note that this still means that\nexecutables run as child-processes of a shell.\n\n#### Create a wrapper script\n\nYou can create an entrypoint script that wraps your startup commands, and\nexecute that script with a JSON-formatted `ENTRYPOINT` command.\n\n✅ Good: the `ENTRYPOINT` uses JSON format.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nCOPY --chmod=755 <<EOT /entrypoint.sh\n#!/usr/bin/env bash\nset -e\nmy-background-process &\nmy-program start\nEOT\nENTRYPOINT [\"/entrypoint.sh\"]\n```\n\n#### Explicitly specify the shell\n\nYou can use the [`SHELL`](https://docs.docker.com/reference/dockerfile/#shell)\nDockerfile instruction to explicitly specify a shell to use. This will suppress\nthe warning since setting the `SHELL` instruction indicates that using shell\nform is a conscious decision.\n\n✅ Good: shell is explicitly defined.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nSHELL [\"/bin/bash\", \"-c\"]\nENTRYPOINT echo \"hello world\"\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md":"---\ntitle: LegacyKeyValueFormat\ndescription: >-\n  Legacy key/value format with whitespace separator should not be used\naliases:\n  - /go/dockerfile/rule/legacy-key-value-format/\n---\n\n## Output\n\n```text\n\"ENV key=value\" should be used instead of legacy \"ENV key value\" format\n```\n\n## Description\n\nThe correct format for declaring environment variables and build arguments in a\nDockerfile is `ENV key=value` and `ARG key=value`, where the variable name\n(`key`) and value (`value`) are separated by an equals sign (`=`).\nHistorically, Dockerfiles have also supported a space separator between the key\nand the value (for example, `ARG key value`). This legacy format is deprecated,\nand you should only use the format with the equals sign.\n\n## Examples\n\n❌ Bad: using a space separator for variable key and value.\n\n```dockerfile\nFROM alpine\nARG foo bar\n```\n\n✅ Good: use an equals sign to separate key and value.\n\n```dockerfile\nFROM alpine\nARG foo=bar\n```\n\n❌ Bad: multi-line variable declaration with a space separator.\n\n```dockerfile\nENV DEPS \\\n    curl \\\n    git \\\n    make\n```\n\n✅ Good: use an equals sign and wrap the value in quotes.\n\n```dockerfile\nENV DEPS=\"\\\n    curl \\\n    git \\\n    make\"\n```\n\n> [!NOTE]\n> Be aware of leading whitespace when converting multi-line legacy syntax to\n> the modern `key=value` format. In the legacy format, leading whitespace on\n> continuation lines is included in the value. In the modern format with\n> quoted values, leading whitespace inside the quotes is also preserved. If\n> you don't want leading whitespace in the value, make sure to remove it when\n> rewriting to the new format:\n>\n> ```dockerfile\n> ENV DEPS=\"\\\n> curl \\\n> git \\\n> make\"\n> ```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md":"---\ntitle: MaintainerDeprecated\ndescription: >-\n  The MAINTAINER instruction is deprecated, use a label instead to define an image author\naliases:\n  - /go/dockerfile/rule/maintainer-deprecated/\n---\n\n## Output\n\n```text\nMAINTAINER instruction is deprecated in favor of using label\n```\n\n## Description\n\nThe `MAINTAINER` instruction, used historically for specifying the author of\nthe Dockerfile, is deprecated. To set author metadata for an image, use the\n`org.opencontainers.image.authors` [OCI label](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys).\n\n## Examples\n\n❌ Bad: don't use the `MAINTAINER` instruction\n\n```dockerfile\nMAINTAINER moby@example.com\n```\n\n✅ Good: specify the author using the `org.opencontainers.image.authors` label\n\n```dockerfile\nLABEL org.opencontainers.image.authors=\"moby@example.com\"\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md":"---\ntitle: MultipleInstructionsDisallowed\ndescription: >-\n  Multiple instructions of the same type should not be used in the same stage\naliases:\n  - /go/dockerfile/rule/multiple-instructions-disallowed/\n---\n\n## Output\n\n```text\nMultiple CMD instructions should not be used in the same stage because only the last one will be used\n```\n\n## Description\n\nIf you have multiple `CMD`, `HEALTHCHECK`, or `ENTRYPOINT` instructions in your\nDockerfile, only the last occurrence is used. An image can only ever have one\n`CMD`, `HEALTHCHECK`, and `ENTRYPOINT`.\n\n## Examples\n\n❌ Bad: Duplicate instructions.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\"]\nENTRYPOINT [\"echo\", \"Hello, Sweden!\"]\n# Only \"Hello, Sweden!\" will be printed\n```\n\n✅ Good: only one `ENTRYPOINT` instruction.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\\nHello, Sweden!\"]\n```\n\nYou can have both a regular, top-level `CMD`\nand a separate `CMD` for a `HEALTHCHECK` instruction.\n\n✅ Good: only one top-level `CMD` instruction.\n\n```dockerfile\nFROM python:alpine\nRUN apk add curl\nHEALTHCHECK --interval=1s --timeout=3s \\\n  CMD [\"curl\", \"-f\", \"http://localhost:8080\"]\nCMD [\"python\", \"-m\", \"http.server\", \"8080\"]\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md":"---\ntitle: NoEmptyContinuation\ndescription: >-\n  Empty continuation lines will become errors in a future release\naliases:\n  - /go/dockerfile/rule/no-empty-continuation/\n---\n\n## Output\n\n```text\nEmpty continuation line found in: RUN apk add     gnupg     curl\n```\n\n## Description\n\nSupport for empty continuation (`/`) lines have been deprecated and will\ngenerate errors in future versions of the Dockerfile syntax.\n\nEmpty continuation lines are empty lines following a newline escape:\n\n```dockerfile\nFROM alpine\nRUN apk add \\\n\n    gnupg \\\n\n    curl\n```\n\nSupport for such empty lines is deprecated, and a future BuildKit release will\nremove support for this syntax entirely, causing builds to break. To avoid\nfuture errors, remove the empty lines, or add comments, since lines with\ncomments aren't considered empty.\n\n## Examples\n\n❌ Bad: empty continuation line between `EXPOSE` and 80.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n\n80\n```\n\n✅ Good: comments do not count as empty lines.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n# Port\n80\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md":"---\ntitle: RedundantTargetPlatform\ndescription: >-\n  Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\naliases:\n  - /go/dockerfile/rule/redundant-target-platform/\n---\n\n## Output\n\n```text\nSetting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\n```\n\n## Description\n\nA custom platform can be used for a base image. The default platform is the\nsame platform as the target output so setting the platform to `$TARGETPLATFORM`\nis redundant and unnecessary.\n\n## Examples\n\n❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.\n\n```dockerfile\nFROM --platform=$TARGETPLATFORM alpine AS builder\nRUN apk add --no-cache git\n```\n\n✅ Good: omit the `--platform` argument.\n\n```dockerfile\nFROM alpine AS builder\nRUN apk add --no-cache git\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md":"---\ntitle: ReservedStageName\ndescription: >-\n  Reserved words should not be used as stage names\naliases:\n  - /go/dockerfile/rule/reserved-stage-name/\n---\n\n## Output\n\n```text\n'scratch' is reserved and should not be used as a stage name\n```\n\n## Description\n\nReserved words should not be used as names for stages in multi-stage builds.\nThe reserved words are:\n\n- `context`\n- `scratch`\n\n## Examples\n\n❌ Bad: `scratch` and `context` are reserved names.\n\n```dockerfile\nFROM alpine AS scratch\nFROM alpine AS context\n```\n\n✅ Good: the stage name `builder` is not reserved.\n\n```dockerfile\nFROM alpine AS builder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md":"---\ntitle: SecretsUsedInArgOrEnv\ndescription: >-\n  Sensitive data should not be used in the ARG or ENV commands\naliases:\n  - /go/dockerfile/rule/secrets-used-in-arg-or-env/\n---\n\n## Output\n\n```text\nPotentially sensitive data should not be used in the ARG or ENV commands\n```\n\n## Description\n\nWhile it is common to pass secrets to running processes\nthrough environment variables during local development,\nsetting secrets in a Dockerfile using `ENV` or `ARG`\nis insecure because they persist in the final image.\nThis rule reports violations where `ENV` and `ARG` keys\nindicate that they contain sensitive data.\n\nInstead of `ARG` or `ENV`, you should use secret mounts,\nwhich expose secrets to your builds in a secure manner,\nand do not persist in the final image or its metadata.\nSee [Build secrets](https://docs.docker.com/build/building/secrets/).\n\n## Examples\n\n❌ Bad: using ARG to pass AWS credentials.\n\n```dockerfile\nARG AWS_ACCESS_KEY_ID\nARG AWS_SECRET_ACCESS_KEY\nRUN aws s3 cp s3://my-bucket/file .\n```\n\n✅ Good: using secret mounts with environment variables.\n\n```dockerfile\nRUN --mount=type=secret,id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --mount=type=secret,id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY \\\n    aws s3 cp s3://my-bucket/file .\n```\n\nTo build with these secrets:\n\n```console\n$ docker buildx build \\\n    --secret id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --secret id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY .\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md":"---\ntitle: StageNameCasing\ndescription: >-\n  Stage names should be lowercase\naliases:\n  - /go/dockerfile/rule/stage-name-casing/\n---\n\n## Output\n\n```text\nStage name 'BuilderBase' should be lowercase\n```\n\n## Description\n\nTo help distinguish Dockerfile instruction keywords from identifiers, this rule\nforces names of stages in a multi-stage Dockerfile to be all lowercase.\n\n## Examples\n\n❌ Bad: mixing uppercase and lowercase characters in the stage name.\n\n```dockerfile\nFROM alpine AS BuilderBase\n```\n\n✅ Good: stage name is all in lowercase.\n\n```dockerfile\nFROM alpine AS builder-base\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md":"---\ntitle: UndefinedArgInFrom\ndescription: >-\n  FROM command must use declared ARGs\naliases:\n  - /go/dockerfile/rule/undefined-arg-in-from/\n---\n\n## Output\n\n```text\nFROM argument 'VARIANT' is not declared\n```\n\n## Description\n\nThis rule warns for cases where you're consuming an undefined build argument in\n`FROM` instructions.\n\nInterpolating build arguments in `FROM` instructions can be a good way to add\nflexibility to your build, and lets you pass arguments that overriding the base\nimage of a stage. For example, you might use a build argument to specify the\nimage tag:\n\n```dockerfile\nARG ALPINE_VERSION=3.20\n\nFROM alpine:${ALPINE_VERSION}\n```\n\nThis makes it possible to run the build with a different `alpine` version by\nspecifying a build argument:\n\n```console\n$ docker buildx build --build-arg ALPINE_VERSION=edge .\n```\n\nThis check also tries to detect and warn when a `FROM` instruction reference\nmiss-spelled built-in build arguments, like `BUILDPLATFORM`.\n\n## Examples\n\n❌ Bad: the `VARIANT` build argument is undefined.\n\n```dockerfile\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n✅ Good: the `VARIANT` build argument is defined.\n\n```dockerfile\nARG VARIANT=\"-alpine3.20\"\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md":"---\ntitle: UndefinedVar\ndescription: >-\n  Variables should be defined before their use\naliases:\n  - /go/dockerfile/rule/undefined-var/\n---\n\n## Output\n\n```text\nUsage of undefined variable '$foo'\n```\n\n## Description\n\nThis check ensures that environment variables and build arguments are correctly\ndeclared before being used. While undeclared variables might not cause an\nimmediate build failure, they can lead to unexpected behavior or errors later\nin the build process.\n\nThis check does not evaluate undefined variables for `RUN`, `CMD`, and\n`ENTRYPOINT` instructions where you use the [shell form](https://docs.docker.com/reference/dockerfile/#shell-form).\nThat's because when you use shell form, variables are resolved by the command\nshell.\n\nIt also detects common mistakes like typos in variable names. For example, in\nthe following Dockerfile:\n\n```dockerfile\nFROM alpine\nENV PATH=$PAHT:/app/bin\n```\n\nThe check identifies that `$PAHT` is undefined and likely a typo for `$PATH`:\n\n```text\nUsage of undefined variable '$PAHT' (did you mean $PATH?)\n```\n\n## Examples\n\n❌ Bad: `$foo` is an undefined build argument.\n\n```dockerfile\nFROM alpine AS base\nCOPY $foo .\n```\n\n✅ Good: declaring `foo` as a build argument before attempting to access it.\n\n```dockerfile\nFROM alpine AS base\nARG foo\nCOPY $foo .\n```\n\n❌ Bad: `$foo` is undefined.\n\n```dockerfile\nFROM alpine AS base\nARG VERSION=$foo\n```\n\n✅ Good: the base image defines `$PYTHON_VERSION`\n\n```dockerfile\nFROM python AS base\nARG VERSION=$PYTHON_VERSION\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md":"---\ntitle: WorkdirRelativePath\ndescription: >-\n  Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes\naliases:\n  - /go/dockerfile/rule/workdir-relative-path/\n---\n\n## Output\n\n```text\nRelative workdir 'app/src' can have unexpected results if the base image changes\n```\n\n## Description\n\nWhen specifying `WORKDIR` in a build stage, you can use an absolute path, like\n`/build`, or a relative path, like `./build`. Using a relative path means that\nthe working directory is relative to whatever the previous working directory\nwas. So if your base image uses `/usr/local/foo` as a working directory, and\nyou specify a relative directory like `WORKDIR build`, the effective working\ndirectory becomes `/usr/local/foo/build`.\n\nThe `WorkdirRelativePath` build rule warns you if you use a `WORKDIR` with a\nrelative path without first specifying an absolute path in the same Dockerfile.\nThe rationale for this rule is that using a relative working directory for base\nimage built externally is prone to breaking, since working directory may change\nupstream without warning, resulting in a completely different directory\nhierarchy for your build.\n\n> [!NOTE]\n>\n> `WORKDIR` does not perform shell expansion. Paths beginning with `~` or\n> `~username` are treated as literal directory names and are not resolved to a\n> user's home directory.\n\n## Examples\n\n❌ Bad: this assumes that `WORKDIR` in the base image is `/`\n(if that changes upstream, the `web` stage is broken).\n\n```dockerfile\nFROM nginx AS web\nWORKDIR usr/share/nginx/html\nCOPY public .\n```\n\n✅ Good: a leading slash ensures that `WORKDIR` always ends up at the desired path.\n\n```dockerfile\nFROM nginx AS web\nWORKDIR /usr/share/nginx/html\nCOPY public .\n```\n\n","content/manuals/ai/sandboxes/agents/_index.md":"---\ntitle: Supported agents\nlinkTitle: Agents\nweight: 40\ndescription: AI coding agents supported by Docker Sandboxes.\nkeywords: docker sandboxes, ai agents, claude code, codex, cursor, gemini\n---\n\nDocker Sandboxes runs the following agents out of the box:\n\n- [Claude Code](claude-code/)\n- [Codex](codex/)\n- [Copilot](copilot/)\n- [Cursor](cursor/)\n- [Docker Agent](docker-agent/)\n- [Droid](droid/)\n- [Gemini](gemini/)\n- [Kiro](kiro/)\n- [OpenCode](opencode/)\n- [Shell](shell/) — agent-less sandbox for manual setup or testing\n\nWant to pre-install tools or customize an agent's environment?\nSee [Customize](../customize/).\n","content/manuals/ai/sandboxes/agents/claude-code.md":"---\ntitle: Claude Code\nweight: 10\ndescription: |\n  Use Claude Code in Docker Sandboxes with authentication, local models,\n  configuration, and YOLO mode for AI-assisted development.\nkeywords: docker sandboxes, claude code, anthropic, ai agent, sbx, local models, llmman, ollama\n---\n\nOfficial documentation: [Claude Code](https://code.claude.com/docs)\n\n## Quick start\n\nLaunch Claude Code in a sandbox by pointing it at a project directory:\n\n```console\n$ sbx run claude ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so `sbx run claude`\nfrom inside your project works too. To start Claude with a specific prompt:\n\n```console\n$ sbx run claude --name my-sandbox -- \"Add error handling to the login function\"\n```\n\nEverything after `--` is passed directly to Claude Code. You can also pipe in a\nprompt from a file with `-- \"$(cat prompt.txt)\"`.\n\n## Authentication\n\nClaude Code requires either an Anthropic API key or a Claude subscription.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set anthropic\n```\n\n**Claude subscription**: If no API key is set, use the `/login` command inside\nClaude Code to authenticate via OAuth.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.claude`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Remote control\n\nTo use Claude Code's `/remote-control` command inside a sandbox, turn on remote\ncontrol:\n\n```console\n$ sbx settings set claude.remoteControl true\n```\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nclaude --dangerously-skip-permissions\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--dangerously-skip-permissions` is\npreserved:\n\n```console\n$ sbx run claude -- -c   # runs claude --dangerously-skip-permissions -c\n```\n\nWhen the first argument is a bare word, such as the `agents` subcommand, it\nreplaces the defaults instead.\n\nSee the [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference)\nfor available options.\n\n## Agents view\n\nClaude Code's [agents view](https://code.claude.com/docs/en/agent-view)\nstarts background sessions that run tasks in parallel. Pair it with\n[clone mode](../workflows/git.md#clone-mode) to keep their changes inside the\nsandbox:\n\n```console\n$ sbx run --clone claude -- agents\n```\n\nThis invocation replaces the\n[default startup command](#default-startup-command), so it doesn't\ninclude `--dangerously-skip-permissions` and you can't switch to\nbypass-permissions mode inside the sandbox. To work around this, either\nuse Claude Code's auto mode or pass the flag explicitly:\n\n```console\n$ sbx run --clone claude -- --dangerously-skip-permissions agents\n```\n\nClaude Code may use branches or worktrees to keep changes from its background\nsessions separate. This depends on the task, Claude Code configuration, and\nproject instructions. The `--clone` flag doesn't control this behavior. Claude\nCode creates any branches and worktrees inside the sandbox, not in your host\ncheckout.\n\nTo review a branch created by a session, fetch the\n`sandbox-<sandbox-name>` remote from the host:\n\n```console\n$ git fetch sandbox-<sandbox-name>\n$ git diff main..sandbox-<sandbox-name>/<branch>\n```\n\nSee [Git workflows](../workflows/git.md) for clone-mode details.\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:claude-code`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n\n## Use a local model\n\nThe `--model` flag routes Claude Code's Anthropic API requests to a model\nserved on your host. This feature is experimental and isn't supported on\nWindows.\n\nEnable the feature:\n\n```console\n$ sbx settings set platform.allowExperimentalFeatures true\n$ sbx settings set feature.model true\n```\n\nTo use the bundled `llmman` model server, pass a GGUF model reference or short\nname:\n\n```console\n$ sbx run --model gemma4 claude\n```\n\nOn first use, `sbx` starts `llmman`, pulls the model, and leaves the server\nrunning on your host. Later sandboxes reuse the server and its model store.\n\nTo use an existing Ollama installation instead, set the provider to `ollama`:\n\n```console\n$ sbx run --model gemma4 --provider ollama claude\n```\n\nOllama must already be installed and running. `sbx` connects to it but doesn't\nstart or manage the Ollama process.\n\nYou can also change the model for an existing sandbox:\n\n```console\n$ sbx run --name <sandbox-name> --model <model-name>\n```\n\nChanging the model recreates the sandbox container. The workspace and\nkit-owned volumes persist.\n\nTo use Docker Model Runner instead, see\n[Run Claude Code in a Docker Sandbox with Docker Model Runner](/guides/claude-code-sandbox-model-runner/).\n","content/manuals/ai/sandboxes/agents/codex.md":"---\ntitle: Codex\nweight: 20\ndescription: |\n  Use OpenAI Codex in Docker Sandboxes with API key authentication and YOLO\n  mode configuration.\nkeywords: docker sandboxes, codex, openai, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Codex in a\nsandboxed environment.\n\nOfficial documentation: [Codex CLI](https://developers.openai.com/codex/cli)\n\n## Quick start\n\nCreate a sandbox and run Codex for a project directory:\n\n```console\n$ sbx run codex ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run codex\n```\n\n## Authentication\n\nIf you haven't stored an OpenAI credential, `sbx run codex` prompts you to\nauthenticate on your host before launching the sandbox. The flow runs on the\nhost, so credentials are never exposed inside the sandbox.\n\nTo set up authentication ahead of time, choose one of the following methods.\n\n**OAuth**: Start the OAuth flow on your host with:\n\n```console\n$ sbx secret set openai --oauth\n```\n\nThis opens a browser window for authentication and stores the resulting tokens\nin your OS keychain. The OAuth flow runs on the host, not inside the sandbox,\nso browser-based authentication works without any extra setup.\n\n**API key**: Store your OpenAI API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n```\n\nSee [Credentials](../configuration/credentials.md) for more details.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.codex`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncodex --dangerously-bypass-approvals-and-sandbox\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). A bare word — such as a prompt — replaces the\ndefaults instead, so lead with the flag to keep bypass mode:\n\n```console\n$ sbx run codex -- --dangerously-bypass-approvals-and-sandbox \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:codex`\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/copilot.md":"---\ntitle: Copilot\nweight: 30\ndescription: |\n  Use GitHub Copilot in Docker Sandboxes with GitHub token authentication and\n  trusted folder configuration.\nkeywords: docker sandboxes, github copilot, ai agent, github token, sbx\n---\n\nThis guide covers authentication, configuration, and usage of GitHub Copilot\nin a sandboxed environment.\n\nOfficial documentation: [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli)\n\n## Quick start\n\nCreate a sandbox and run Copilot for a project directory:\n\n```console\n$ sbx run copilot ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run copilot\n```\n\n## Authentication\n\nCopilot requires a GitHub token with Copilot access. Store your token using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set github --command 'gh auth token'\n```\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCopilot is configured to trust the workspace directory by default, so it\noperates without repeated confirmations for workspace files.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncopilot --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run copilot -- -p \"review this PR\"   # runs copilot --yolo -p \"review this PR\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:copilot`\n\nPreconfigured to trust the workspace directory.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/cursor.md":"---\ntitle: Cursor\nweight: 40\ndescription: |\n  Use Cursor in Docker Sandboxes with API key or proxy-managed OAuth\n  authentication.\nkeywords: docker sandboxes, cursor, cursor agent, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Cursor in a\nsandboxed environment.\n\nOfficial documentation: [Cursor CLI](https://cursor.com/cli)\n\n## Quick start\n\nCreate a sandbox and run Cursor for a project directory:\n\n```console\n$ sbx run cursor ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run cursor\n```\n\n## Authentication\n\nCursor supports two authentication methods: an API key or OAuth.\n\n**API key**: Store your Cursor API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set cursor\n```\n\n**OAuth**: If no API key is set, Cursor prompts you to sign in interactively\non first run. The proxy intercepts the token exchange with\n`api2.cursor.sh/auth/poll`, so credentials are managed by the host and aren't\nstored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.cursor`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCursor reads `AGENTS.md` from the workspace for agent-specific instructions.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncursor-agent --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run cursor -- -p \"refactor this\"   # runs cursor-agent --yolo -p \"refactor this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:cursor-agent-docker`\n\nPreconfigured with HTTP/1.1 and server-sent events for agent traffic so\nrequests flow through the host proxy. Authentication state is persisted across\nsandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/docker-agent.md":"---\ntitle: Docker Agent\nweight: 50\ndescription: |\n  Use Docker Agent in Docker Sandboxes with multi-provider authentication\n  supporting OpenAI, Anthropic, and more.\nkeywords: docker sandboxes, docker agent, openai, anthropic, sbx\n---\n\nOfficial documentation: [Docker Agent](/manuals/ai/docker-agent/_index.md)\n\n## Quick start\n\nCreate a sandbox and run Docker Agent for a project directory:\n\n```console\n$ sbx run docker-agent ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so\n`sbx run docker-agent` from inside your project works too.\n\n## Authentication\n\nDocker Agent supports multiple providers. Store keys for the providers you want\nto use with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set nebius\n$ sbx secret set mistral\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. Docker Agent detects\navailable credentials and routes requests to the appropriate provider.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ndocker-agent run --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). When the first argument is a bare word — such\nas the `run` subcommand or a config file — it replaces the defaults, so include\n`run --yolo` yourself:\n\n```console\n$ sbx run docker-agent -- run --yolo agent.yml\n```\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:docker-agent`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n","content/manuals/ai/sandboxes/agents/droid.md":"---\ntitle: Droid\nweight: 60\ndescription: |\n  Use Droid in Docker Sandboxes with API key or OAuth authentication.\nkeywords: docker sandboxes, droid, factory, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Droid, an AI\ncoding agent by Factory, in a sandboxed environment.\n\nOfficial documentation: [Droid](https://docs.factory.ai/)\n\n## Quick start\n\nCreate a sandbox and run Droid for a project directory:\n\n```console\n$ sbx run droid ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run droid\n```\n\n## Authentication\n\nDroid requires a [Factory account](https://factory.ai). Both authentication\nmethods authenticate you to Factory's service directly — unlike other agents\nwhere you supply a model provider key, Factory manages model access through\nyour Factory account.\n\n**API key**: Store your Factory API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set droid\n```\n\n**OAuth**: If no API key is set, Droid prompts you to authenticate\ninteractively on first run. The proxy handles the OAuth flow, so credentials\naren't stored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nThe sandbox runs `droid` with no implicit flags. Args after `--` are passed\nstraight through:\n\n```console\n$ sbx run droid -- exec \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:droid-docker`\n\nPreconfigured to run without approval prompts. Authentication state is\npersisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/gemini.md":"---\ntitle: Gemini\nweight: 70\ndescription: |\n  Use Google Gemini in Docker Sandboxes with proxy-managed authentication and\n  API key configuration.\nkeywords: docker sandboxes, gemini, google, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Google Gemini in\na sandboxed environment.\n\nOfficial documentation: [Gemini CLI](https://geminicli.com/docs/)\n\n## Quick start\n\nCreate a sandbox and run Gemini for a project directory:\n\n```console\n$ sbx run gemini ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run gemini\n```\n\n## Authentication\n\nGemini requires either a Google API key or a Google account with Gemini access.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set google\n```\n\n**Google account**: If no API key is set, Gemini prompts you to sign in\ninteractively when it starts. Interactive authentication is scoped to the\nsandbox and doesn't persist if you remove and recreate it.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.gemini`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nThe sandbox disables Gemini's built-in sandbox tool (since the sandbox itself\nprovides isolation).\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ngemini --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run gemini -- -p \"explain this\"   # runs gemini --yolo -p \"explain this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:gemini`\n\nGemini is configured to disable its built-in OAuth flow. Authentication is\nmanaged through the proxy with API keys.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/kiro.md":"---\ntitle: Kiro\nweight: 80\ndescription: |\n  Use Kiro in Docker Sandboxes with device flow authentication for interactive\n  AI-assisted development.\nkeywords: docker sandboxes, kiro, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Kiro in a\nsandboxed environment.\n\nOfficial documentation: [Kiro CLI](https://kiro.dev/docs/cli/)\n\n## Quick start\n\nCreate a sandbox and run Kiro for a project directory:\n\n```console\n$ sbx run kiro ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run kiro\n```\n\nOn first run, Kiro prompts you to authenticate using device flow.\n\n## Authentication\n\nKiro uses device flow authentication, which requires interactive login through\na web browser. This method provides secure authentication without storing API\nkeys directly.\n\n### Device flow login\n\nWhen you first run Kiro, it prompts you to authenticate:\n\n1. Kiro displays a URL and a verification code\n2. Open the URL in your web browser\n3. Enter the verification code\n4. Complete the authentication flow in your browser\n5. Return to the terminal - Kiro proceeds automatically\n\nThe authentication session is persisted in the sandbox and doesn't require\nrepeated login unless you destroy and recreate the sandbox.\n\n### Manual login\n\nYou can trigger the login flow manually:\n\n```console\n$ sbx run kiro --name <sandbox-name> -- login --use-device-flow\n```\n\nThis command initiates device flow authentication without starting a coding\nsession.\n\n### Authentication persistence\n\nKiro stores authentication state in `~/.local/share/kiro-cli/data.sqlite3`\ninside the sandbox. This database persists as long as the sandbox exists. If\nyou destroy the sandbox, you'll need to authenticate again when you recreate\nit.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nKiro requires minimal configuration. The agent runs with trust-all-tools mode\nby default, which lets it execute commands without repeated approval prompts.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nkiro chat --trust-all-tools\n```\n\nWhen the first argument after `--` is a flag (begins with `-`), it's added\nafter the defaults — for example, `sbx run kiro -- --resume` runs\n`kiro chat --trust-all-tools --resume`. When the first argument is a bare word,\nit replaces the defaults, which is why `sbx run kiro -- login --use-device-flow`\nruns the login subcommand on its own. To run `chat` with extra arguments of\nyour own, include the subcommand:\n\n```console\n$ sbx run kiro -- chat --trust-all-tools --resume\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:kiro`\n\nAuthentication state is persisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/opencode.md":"---\ntitle: OpenCode\nweight: 90\ndescription: |\n  Use OpenCode in Docker Sandboxes with multi-provider authentication and TUI\n  interface for AI development.\nkeywords: docker sandboxes, opencode, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of OpenCode in a\nsandboxed environment.\n\nOfficial documentation: [OpenCode](https://opencode.ai/docs)\n\n## Quick start\n\nCreate a sandbox and run OpenCode for a project directory:\n\n```console\n$ sbx run opencode ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run opencode\n```\n\nOpenCode launches a TUI (text user interface) where you can select your\npreferred LLM provider and interact with the agent.\n\n## Authentication\n\nOpenCode supports multiple providers. Store keys for the providers you want to\nuse with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set groq\n$ sbx secret set aws\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. OpenCode detects\navailable credentials and offers those providers in the TUI.\n\n### OpenCode Zen API keys\n\nOpenCode Zen API keys aren't part of the built-in OpenCode credentials that\n`sbx secret set` supports. To use an OpenCode Zen API key, store it as a\n[custom secret](../configuration/credentials.md#custom-secrets):\n\nSet the `OPENCODE_API_KEY` environment variable on the host, then store it:\n\n```console\n$ sbx secret set-custom \\\n    --host opencode.ai \\\n    --env OPENCODE_API_KEY \\\n    --value \"$OPENCODE_API_KEY\"\n```\n\nCustom secrets keep the real key in the host secret store. The sandbox receives\n`OPENCODE_API_KEY` as a placeholder, and the host-side proxy replaces that\nplaceholder with the real key on requests to `opencode.ai`.\n\nOpenCode Zen also requires network access to `opencode.ai`:\n\n```console\n$ sbx policy allow network opencode.ai:443\n```\n\nIf you add a global custom secret, recreate existing OpenCode sandboxes so the\nnew environment variable is available inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nOpenCode uses a TUI interface and doesn't require extensive configuration\nfiles. The agent prompts you to select a provider when it starts, and you can\nswitch providers during a session.\n\n### Default startup command\n\nThe sandbox runs `opencode` with no implicit flags. Args after `--` are passed\nstraight through. For example, to resume an existing session:\n\n```console\n$ sbx run opencode -- -s <session-id>\n```\n\n### TUI mode\n\nOpenCode launches in TUI mode by default. The interface shows:\n\n- Available LLM providers (based on configured credentials)\n- Current conversation history\n- File operations and tool usage\n- Real-time agent responses\n\nUse keyboard shortcuts to navigate the interface and interact with the agent.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:opencode`\n\nOpenCode supports multiple LLM providers with automatic credential injection\nthrough the sandbox proxy.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/shell.md":"---\ntitle: Shell\nweight: 100\ndescription: Run an agent-less sandbox with a Bash login shell for manual setup, testing custom agent implementations, or inspecting a running environment.\nkeywords: sandboxes, sbx, shell, agent, manual setup, testing\n---\n\n`sbx run shell` drops you into a Bash login shell inside a sandbox with no\npre-installed agent binary. It's useful for installing and configuring\nagents manually, testing custom implementations, or inspecting a running\nenvironment.\n\n```console\n$ sbx run shell ~/my-project\n```\n\nThe workspace path defaults to the current directory. To run a one-off\ncommand instead of an interactive shell, pass it after `--`:\n\n```console\n$ sbx run shell -- -c \"echo 'Hello from sandbox'\"\n```\n\n## Default startup command\n\nWithout extra args, the sandbox runs `bash -l`. When the first argument after\n`--` is a flag (begins with `-`), it's added after `-l`, so login-shell\nbehavior is preserved:\n\n```console\n$ sbx run shell -- -c \"echo hi\"   # runs bash -l -c \"echo hi\"\n```\n\nWhen the first argument is a bare word, it replaces `-l` instead.\n\nStore credentials using [stored secrets](../configuration/credentials.md#stored-secrets)\nbefore running the sandbox. The proxy injects them into outbound API requests;\ncredentials are never stored inside the VM:\n\n```console\n$ sbx secret set anthropic\n$ sbx secret set openai\n```\n\nOnce inside the shell, you can install agents using their standard methods,\nfor example `npm install -g @continuedev/cli`. For complex setups, build a\n[custom template](../customize/templates.md) instead of installing\ninteractively each time.\n\n## Base image\n\nThe shell sandbox uses the `shell` base image — the common base environment\nwithout a pre-installed agent.\n","content/manuals/dhi/tools/_index.md":"---\ntitle: Tools\ndescription: Interfaces and tools for browsing, managing, and automating Docker Hardened Images.\nweight: 25\nparams:\n  grid_tools:\n    - title: Use Docker Hub\n      description: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\n      icon: squares-2x2\n      link: /dhi/tools/hub/\n    - title: CLI\n      description: Install and use the `docker dhi` command-line interface to browse the catalog, inspect images, and manage mirrors from your terminal.\n      icon: command-line\n      link: /dhi/tools/cli/\n    - title: MCP server\n      description: Connect an AI assistant to the DHI catalog to search repositories, inspect images, retrieve SBOMs, and check CVEs using plain language.\n      icon: cpu-chip\n      link: /dhi/tools/mcp/\n    - title: Use the DHI Terraform provider\n      description: Use the DHI Terraform provider to manage mirrors and automate DHI configuration as infrastructure as code.\n      icon: wrench-screwdriver\n      link: /dhi/tools/terraform/\n    - title: Use the DHI API\n      description: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\n      icon: code-bracket\n      link: /dhi/tools/api/\n---\n\nDocker Hardened Images can be accessed and managed through several interfaces.\nChoose the tool that fits your workflow.\n\n{{< grid items=\"grid_tools\" >}}\n","content/manuals/dhi/tools/api.md":"---\ntitle: Use the DHI API\nlinktitle: API\ndescription: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\nweight: 50\nkeywords: dhi api, docker hardened images api, graphql api, dhi endpoint, dhi authentication\n---\n\nThe DHI API is a GraphQL API for querying Docker Hardened Images data\nprogrammatically, for use cases like building automation or dashboards on\ntop of DHI data.\n\n## Endpoint\n\nSend requests as `POST` requests to:\n\n```text\nhttps://api.dso.docker.com/v1/graphql\n```\n\n## Request format\n\nThe API accepts standard GraphQL requests: a JSON body with a `query` and,\noptionally, `variables`.\n\n```console\n$ curl https://api.dso.docker.com/v1/graphql \\\n  -H \"Authorization: Bearer <token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"...\", \"variables\": { ... }}'\n```\n\nEvery query takes a `Context` argument (conventionally named `ctx` in the\n`variables` object) alongside its query-specific arguments:\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `ctx` | `Context` | Yes | Scopes the request to an organization. |\n| `ctx.organization` | `String` | Yes | The Docker organization the token belongs to. |\n\n## Authentication\n\nAn [organization access token](/manuals/enterprise/security/access-tokens.md)\n(OAT) or personal access token (PAT) isn't used directly as the bearer\ntoken. Exchange it first for an access token:\n\n```console\n$ curl -X POST https://hub.docker.com/v2/auth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"identifier\": \"<identifier>\", \"secret\": \"<token>\"}'\n```\n\nFor `identifier`, use your Docker Hub username with a PAT, or the\norganization name with an OAT. The response contains the access token:\n\n```json\n{ \"access_token\": \"...\" }\n```\n\nPass that `access_token` as `Authorization: Bearer <access_token>`. Also set\n`ctx.organization` in `variables` to the organization the token belongs to\n(see [Request format](#request-format)).\n\n## Response format\n\nResponses follow the standard GraphQL envelope:\n\n| Key | Description |\n|---|---|\n| `data` | The requested fields. A field is `null` if it couldn't be resolved, for example due to an authorization failure. |\n| `errors` | Present when a field failed to resolve. Includes a `message` and a `path` identifying which field failed. |\n| `extensions` | Metadata such as a `correlation_id`, useful when reporting an issue. |\n\nFor example, an unauthenticated request, or a request for data your token\ncan't access, returns a `null` result under `data` alongside an authorization\nerror in `errors`, rather than an HTTP-level failure:\n\n```json\n{\n  \"errors\": [\n    {\n      \"message\": \"You are not allowed to read data for this team\",\n      \"path\": [\"someQuery\"],\n      \"extensions\": { \"code\": \"DOWNSTREAM_SERVICE_ERROR\", \"status\": 403 }\n    }\n  ],\n  \"data\": { \"someQuery\": null },\n  \"extensions\": { \"correlation_id\": \"...\" }\n}\n```\n\n## Queries\n\n### `imagePackagesForImageCoords`\n\nFetches every package in an image, every CVE reported against it, and\nwhether Docker suppresses that CVE, by digest. See [Query VEX for a Docker\nHardened Image](/manuals/dhi/how-to/vex-api.md) for a guided example.\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `digest` | `String` | Yes | The image's platform manifest digest, not the multi-arch index digest. |\n| `hostName` | `String` | Yes | `hub.docker.com` or `docker.io`. |\n| `repoName` | `String` | Yes | Repository name, with or without the namespace prefix. |\n| `includeExcepted` | `Boolean` | No | Include suppressed CVEs in the response alongside the reason for suppression. Without it, the response only shows the netted list, with no visibility into what was suppressed. |\n| `includeNodsa` | `Boolean` | No | Include Debian NODSA exclusions, which make up most suppressions on a Debian-based image. |\n| `includePublic` | `Boolean` | No | Also include public images when `ctx.organization` scopes the request to an organization. Not needed for a typical lookup. |\n\nKeep the requested response fields limited to what you plan to render.\nFields such as `locations`, `description`, `vulnerableRange`, and `epss`\nincrease response size substantially and aren't needed for a CVE-count or\nsuppressed-CVE view.\n\n#### Response fields\n\n`vulnerabilityExceptions` only contains records that actually suppress a\nCVE, so it always lines up with `isExcepted`: an empty array means the CVE\nis live. Use `isExcepted` as your filter for \"is this CVE suppressed.\"\n\n| Field | Meaning |\n|---|---|\n| `isExcepted` | Docker suppresses this CVE for this image. Use this to filter. |\n| `sourceType` | `EXTERNAL` (Debian NODSA), `MANUAL_EXCEPTION` (Docker analyst exception), or `VEX_STATEMENT` (an ingested VEX document). |\n| `type` | `FALSE_POSITIVE` and `ACCEPTED_RISK` suppress the CVE. `UNDER_INVESTIGATION` and `AFFECTED` don't. |\n| `justification` | The OpenVEX justification value. Always `null` for NODSA exclusions. |\n| `additionalDetails` | Free-text rationale for the suppression. |\n| `isDhiStatement` | Whether the statement is inherited from the DHI base image. |\n| `id` | Stable identifier for the statement. |\n\n#### Mapping to OpenVEX\n\nIf your pipeline consumes OpenVEX documents (for example, Trivy's `--vex`\nflag), each suppressed record maps as follows:\n\n| OpenVEX field | Source |\n|---|---|\n| `vulnerability.name` | `sourceId` |\n| `products[].@id` | The parent package's `purl` |\n| `status` | `not_affected` (from `type: FALSE_POSITIVE`) |\n| `justification` | `justification`, defaulting to `vulnerable_code_cannot_be_controlled_by_adversary` for NODSA exclusions |\n| `status_notes` | `additionalDetails` |\n| `@id` | `id` |\n","content/manuals/dhi/tools/cli.md":"---\ntitle: Use the DHI CLI\nlinkTitle: CLI\nweight: 20\nkeywords: docker dhi, CLI, command line, docker hardened images\ndescription: Learn how to install and use docker dhi, the command-line interface for managing Docker Hardened Images.\naliases:\n  - /dhi/how-to/cli/\n---\n\nThe `docker dhi` command-line interface (CLI) is a tool for managing Docker Hardened Images:\n- Browse the catalog of available DHI images and their metadata\n- View attestations for DHI images, including SBOMs and provenance\n- Mirror DHI images to your Docker Hub organization\n- Create and manage customizations of DHI images\n- Generate authentication for enterprise package repositories\n- Monitor customization builds\n\n## Installation\n\nThe `docker dhi` CLI is available in [Docker Desktop](https://docs.docker.com/desktop/) version 4.65 and later.\nYou can also install the standalone `dhictl` binary.\n\n### Docker Desktop\n\nThe `docker dhi` command is included in Docker Desktop 4.65 and later. No additional installation is required.\n\n### Standalone binary\n\n1. Download the `dhictl` binary for your platform from the\n   [releases](https://github.com/docker-hardened-images/dhictl/releases) page.\n2. Move it to a directory in your `PATH`:\n    - `mv dhictl /usr/local/bin/` on _Linux_ and _macOS_\n    - Move `dhictl.exe` to a directory in your `PATH` on _Windows_\n\n## Usage\n\nEvery command has built-in help accessible with the `--help` flag:\n\n```console\n$ docker dhi --help\n$ docker dhi catalog list --help\n```\n\n### Browse the DHI catalog\n\nList all available DHI images:\n\n```console\n$ docker dhi catalog list\n```\n\nFilter by type, name, or compliance:\n\n```console\n$ docker dhi catalog list --type image\n$ docker dhi catalog list --filter golang\n$ docker dhi catalog list --fips\n$ docker dhi catalog list --stig\n```\n\nGet details of a specific image, including available tags and CVE counts:\n\n```console\n$ docker dhi catalog get <image-name>\n```\n\n### View attestations\n\nList all attestations attached to a DHI image:\n\n```console\n$ docker dhi attestation list dhi/nginx:1.27\n$ docker dhi attestation list dhi/nginx:1.27 --platform linux/amd64\n$ docker dhi attestation list dhi/nginx:1.27 --predicate-type https://slsa.dev/provenance/v1\n$ docker dhi attestation list dhi/nginx:1.27 --json\n```\n\nGet a specific attestation by its referrer digest:\n\n```console\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest>\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest> -o provenance.json\n```\n\nDisplay the SPDX SBOM for an image:\n\n```console\n$ docker dhi attestation sbom dhi/nginx:1.27\n$ docker dhi attestation sbom dhi/nginx:1.27 --platform linux/amd64\n```\n\n### Mirror DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nStart mirroring one or more DHI images to your Docker Hub organization:\n\n```console\n$ docker dhi mirror start --org my-org \\\n  dhi/golang,my-org/dhi-golang \\\n  dhi/nginx,my-org/dhi-nginx \\\n  dhi/prometheus-chart,my-org/dhi-prometheus-chart\n```\n\nMirror with dependencies:\n\n```console\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --dependencies\n```\n\nList mirrored images in your organization:\n\n```console\n$ docker dhi mirror list --org my-org\n```\n\nFilter mirrored images by name or type:\n\n```console\n$ docker dhi mirror list --org my-org --filter python\n$ docker dhi mirror list --org my-org --type image\n$ docker dhi mirror list --org my-org --type helm-chart\n```\n\nStop mirroring one or more images:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org\n$ docker dhi mirror stop dhi-python dhi-golang --org my-org\n```\n\nStop mirroring and delete the repositories:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org --delete\n$ docker dhi mirror stop dhi-golang --org my-org --delete --force\n```\n\n### Customize DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nThe CLI can be used to create and manage DHI image customizations. For detailed\ninstructions on creating customizations using the GUI, see [Customize a Docker\nHardened Image](../how-to/customize.md).\n\nThe following is a quick reference for CLI commands. For complete details on all\noptions and flags, see the\n[CLI reference](/reference/cli/docker/dhi/).\n\n```console\n# Prepare a single customization scaffold\n$ docker dhi customization prepare golang 1.25 \\\n  --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\" \\\n  > my-customization.yaml\n\n# Prepare a bulk customization scaffold (pipe JSON array via stdin)\n$ echo '[{\"destination\":\"my-org/dhi-golang\",\"tag-definition-id\":\"golang/alpine-3.23/1.24-dev\"}]' \\\n  | docker dhi customization prepare --name \"golang with git\" --org my-org \\\n  > my-customization.yaml\n\n# Create a customization\n$ docker dhi customization create my-customization.yaml --org my-org\n\n# Create with flag overrides (flags take precedence over the YAML file)\n$ docker dhi customization create my-customization.yaml --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\"\n\n# List customizations\n$ docker dhi customization list --org my-org\n\n# Filter customizations by name, repository, or source\n$ docker dhi customization list --org my-org --filter git\n$ docker dhi customization list --org my-org --repo dhi-golang\n$ docker dhi customization list --org my-org --source golang\n\n# Get a customization by ID\n$ docker dhi customization get <id> --org my-org\n\n# Update a customization\n# The YAML file must include the 'id' field to identify the customization to update\n$ docker dhi customization edit my-customization.yaml --org my-org\n\n# Delete a customization by ID\n$ docker dhi customization delete <id> --org my-org\n\n# Delete multiple customizations\n$ docker dhi customization delete <id1> <id2> --org my-org\n\n# Delete without confirmation prompt\n$ docker dhi customization delete <id> --org my-org --force\n```\n\nFor a complete reference of all YAML fields, see\n[Image customization YAML file](/dhi/how-to/customize/#image-customization-yaml-file).\n\n### Enterprise package authentication\n\n{{< summary-bar feature_name=\"Docker Hardened Images Enterprise\" >}}\n\nGenerate authentication credentials for accessing the enterprise hardened\npackage repository. These credentials are used when configuring your package\nmanager to install compliance and security-patched packages in your own images. For detailed\ninstructions, see [Enterprise\nrepository](../how-to/hardened-packages.md#enterprise-repository).\n\nFor Alpine-based images:\n\n```console\n$ docker dhi auth apk\n```\n\nFor Debian-based images:\n\n```console\n$ docker dhi auth deb\n```\n\n### Monitor customization builds\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nList builds for a customization:\n\n```console\n$ docker dhi customization build list <customization-id> --org my-org\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\nGet details of a specific build:\n\n```console\n$ docker dhi customization build get <customization-id> <build-id> --org my-org\n$ docker dhi customization build get <customization-id> <build-id> --org my-org --json\n```\n\nView build logs:\n\n```console\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org --json\n```\n\n### JSON output\n\nMost list and get commands support a `--json` flag for machine-readable output:\n\n```console\n$ docker dhi catalog list --json\n$ docker dhi catalog get golang --json\n$ docker dhi attestation list dhi/nginx:1.27 --json\n$ docker dhi mirror list --org my-org --json\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --json\n$ docker dhi customization list --org my-org --json\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\n## Configuration\n\nThe `docker dhi` CLI can be configured with a YAML file located at:\n- `$HOME/.config/dhictl/config.yaml` on _Linux_ and _macOS_\n- `%USERPROFILE%\\.config\\dhictl\\config.yaml` on _Windows_\n\nIf `$XDG_CONFIG_HOME` is set, the configuration file is located at `$XDG_CONFIG_HOME/dhictl/config.yaml`.\n\nAvailable configuration options:\n\n| Option      | Environment Variable | Description                                                                                                               |\n|-------------|----------------------|---------------------------------------------------------------------------------------------------------------------------|\n| `org`       | `DHI_ORG`            | Default Docker Hub organization for mirror and customization commands.                                                    |\n| `api_token` | `DHI_API_TOKEN`      | Docker token for authentication. You can generate a token in your [Docker Hub account settings](https://hub.docker.com/). |\n\nEnvironment variables take precedence over configuration file values.\n","content/manuals/dhi/tools/hub.md":"---\ntitle: Use Docker Hub\nlinktitle: Docker Hub\ndescription: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\nweight: 10\nkeywords: docker hub dhi catalog, hardened images hub, dhi repository details, image variants hub\n---\n\nThe [Docker Hardened Images catalog](https://hub.docker.com/hardened-images/catalog)\non Docker Hub is the primary web interface for browsing, searching, and inspecting\nDHI repositories and their metadata.\n\n## Catalog page\n\nThe catalog lists all available DHI repositories. You can filter by name,\nimage type, or compliance requirements (FIPS, STIG) to find the image you need.\n\n## Repository details page\n\nWhen you select a repository from the catalog, the repository details page\nprovides the following:\n\n- Overview: A brief explanation of the image.\n- Guides: Several guides on how to use the image and migrate your existing application.\n- Images: Select this option to [view image variants](#images-page).\n- Security summary: Select a tag name to view a quick security summary,\n  including package count and total known vulnerabilities.\n- Recently pushed tags: A list of recently updated image variants and when they\n  were last updated.\n- Use this image: After selecting an image variant, you can select this option to\n  view instructions on how to pull and use the image variant, or select **Mirror\n  repository** to mirror it to your organization.\n\n## Images page\n\nFrom the repository details page, select **Images** to see all available image\nvariants for that repository. The table includes:\n\n- Image version: The image name with its base distribution (for example, `debian\n  13`) and associated tags.\n- Type: The support lifecycle status of the variant.\n- Compliance: Relevant compliance designations, for example `CIS`, `FIPS`, or\n  `STIG (100%)`.\n- Package manager: Whether a package manager is available. A checkmark indicates\n  a package manager is present (for example, `apt` or `apk`), a dash indicates\n  none.\n- Shell: Whether a shell is available. A checkmark indicates a shell is present\n  (for example, `bash` or `busybox`), a dash indicates none.\n- User: The user that the container runs as, for example `root` or `nonroot\n  (65532)`.\n- Last pushed: When the image variant was last updated.\n- Vulnerabilities: Vulnerability counts by severity level.\n\n## Image variant details page\n\nSelect an image version from the Images table to view detailed information about\nthat specific variant:\n\n- Packages: A list of all packages included in the image variant, with each\n  package's name, version, distribution, and licensing information.\n- Specifications:\n  - Source and build information: The Dockerfile and Git commit used to build the image.\n  - Build parameters, entrypoint, CMD, user, working directory, environment\n    variables, labels, and platform.\n- Vulnerabilities: A list of known CVEs for the image variant, including CVE ID,\n  severity, affected package, fix version, last detected date, status, and\n  suppressed CVEs.\n- Attestations: Signed security attestations covering the image's build process,\n  contents, and security posture. For the full list, see\n  [Attestations](/dhi/explore/security-concepts/attestations/).\n\n## Manage page\n\nThe Manage page (**My Hub** > **Hardened Images** > **Manage**) is the central\nplace for administering your organization's mirrored DHI repositories. It has\ntwo tabs:\n\n- Mirrored Images: Lists all image repositories currently mirrored to your\n  organization, with their source DHI repository, destination repository name,\n  and mirroring status. From here you can stop mirroring or open a repository's\n  settings.\n- Mirrored Helm charts: The same view for Helm chart repositories.\n\nSelecting a mirrored repository opens its settings, where you can enable or\ndisable Extended Lifecycle Support (ELS) and access customizations.\n\nFor step-by-step instructions, see [Mirror a Docker Hardened Image\nrepository](/dhi/how-to/mirror/).\n\n## Customizations\n\nCustomizations are accessible from **My Hub** > **Hardened Images** > **Manage** > **Mirrored Images**.\nSelect the menu icon next to a mirrored repository and\nthen **Customize**. Each customization defines\nadditional packages, OCI artifacts, environment variables, or labels to layer\nonto the base DHI during a rebuild.\n\nThe customizations view shows each customization's name, status, and last build\ntime. Selecting a customization opens its configuration, where you can edit the\ndefinition, trigger a rebuild, or delete it.\n\nFor step-by-step instructions, see [Customize a Docker Hardened\nImage](/dhi/how-to/customize/).\n","content/manuals/dhi/tools/mcp.md":"---\ntitle: Use the DHI MCP server\nlinktitle: MCP server\ndescription: Connect an AI assistant to the Docker Hardened Images catalog using the DHI MCP server to search repositories, inspect images, view SBOMs, and check CVEs.\nweight: 30\nkeywords: docker hardened images mcp, ai assistant dhi, mcp server docker, dhi catalog ai, claude cursor docker images, sbom mcp, cve mcp\naliases:\n  - /dhi/how-to/mcp/\n---\n\nThe Docker Hardened Images (DHI) MCP server exposes the DHI catalog through the\nModel Context Protocol (MCP), letting you query repositories, inspect image\nmetadata, retrieve SBOMs, and check CVEs directly from your AI assistant in\nplain language.\n\nThe MCP server is:\n\n- Remote. No local binary to install. Your AI assistant connects directly to\n  `https://dhi.io/mcp`.\n- Compatible with any MCP-capable AI assistant, including Claude,\n  Cursor, and others.\n\nMost tools are public and require no credentials. The mirror management tools\n(`dhi_list_mirrors`, `dhi_create_mirror`, `dhi_remove_mirror`) require a Docker\nHub username and personal access token (PAT) with owner access to the target\norganization. Credentials are passed as an HTTP Basic auth header in the MCP\nclient configuration — they are never passed as tool arguments.\n\n## Connect your AI assistant\n\nConfiguration varies by client. Select the tab for your AI assistant.\n\n{{< tabs >}}\n{{< tab name=\"Claude Desktop\" >}}\n\nAdd the following to your Claude Desktop configuration file:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\nThe configuration file is located at:\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n{{< /tab >}}\n{{< tab name=\"Cursor\" >}}\n\nAdd the following to `.cursor/mcp.json` in your project, or\n`~/.cursor/mcp.json` globally:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Claude Code\" >}}\n\nRun the following command to add the DHI MCP server:\n\n```console\n$ claude mcp add dhi --url https://dhi.io/mcp\n```\n\nOr add it manually to `.claude/mcp.json` in your project:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Docker Agent\" >}}\n\nIn your [Docker Agent](/manuals/ai/docker-agent/_index.md) YAML configuration, add the\nDHI MCP server as a remote toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://dhi.io/mcp\"\n      transport_type: streamable\n```\n\nFor example, to create an agent that can answer questions about the DHI catalog:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: DHI catalog assistant\n    instruction: |\n      Help me find and evaluate Docker Hardened Images.\n      Search the DHI catalog, inspect image details, check CVEs,\n      and retrieve SBOMs and attestations as needed.\n    toolsets:\n      - type: mcp\n        remote:\n          url: \"https://dhi.io/mcp\"\n          transport_type: streamable\n```\n\nRun the agent with:\n\n```console\n$ docker agent run dhi-agent.yaml\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Available tools\n\nThe DHI MCP server provides ten tools that your AI assistant calls automatically\nbased on what you ask:\n\n| Tool | What it does |\n|------|-------------|\n| `dhi_list_repositories` | Search and filter the DHI catalog by name, type, category, FIPS, or STIG compliance |\n| `dhi_get_repository` | Get full details for a repository: tag definitions, build config, platforms, and per-manifest vulnerability counts |\n| `dhi_get_tag_definition` | Get the deep view of a single tag definition |\n| `dhi_get_image_details` | Get per-digest details: tags, platform, size, layer and package counts, vulnerability severity counts, and attestation types |\n| `dhi_get_image_packages` | Retrieve the full software bill of materials (SBOM): package name, version, type, purl, licenses, and file locations |\n| `dhi_get_image_cves` | List CVEs with severity, CVSS score, fix version, EPSS score, and CISA-exploited flag; filter by minimum severity or fixable-only |\n| `dhi_get_image_attestations` | List SBOM, provenance, signature, and other attestations for a specific image digest |\n| `dhi_list_mirrors` | List mirrored DHI repositories for a Docker Hub organization — requires authentication |\n| `dhi_create_mirror` | Start mirroring a DHI repository into a Docker Hub organization — requires authentication |\n| `dhi_remove_mirror` | Stop mirroring a repository by its mirror ID — requires authentication |\n\n## Authenticate for mirror tools\n\nThe mirror tools require a Docker Hub username and [personal access token\n(PAT)](/security/access-tokens/) with owner access to the target organization,\npassed as an HTTP Basic auth header. Generate the value with:\n\n```console\n$ printf 'USERNAME:dckr_pat_...' | base64 | tr -d '\\n'\n```\n\nThen add it to your MCP client configuration:\n\n> [!WARNING]\n> Base64 encoding is not encryption. The value in your configuration file\n> is effectively a plaintext password. Do not commit this file to version\n> control or share it.\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Basic <base64-value>\"\n      }\n    }\n  }\n}\n```\n\nWithout credentials, the read-only catalog tools work normally and the mirror\ntools return an authentication error.\n\n## What the tools return\n\nEach tool returns structured data that your AI assistant can summarize,\ncompare, or act on:\n\n- `dhi_list_repositories` returns a list of repositories with display\n  name, distributions, platforms, FIPS/STIG flags, included tools, and category.\n- `dhi_get_repository` returns the full repository record, including all tag\n  definitions with their tags, build configuration, image indexes, and\n  per-platform manifest digests with vulnerability counts.\n- `dhi_get_tag_definition` returns tags, build parameters, entrypoint,\n  environment variables, run-as user, and per-platform manifests for a single\n  tag definition.\n- `dhi_get_image_details` returns the image platform, compressed size, layer\n  count, package count, vulnerability severity counts by level, labels, and\n  a list of attestation predicate types.\n- `dhi_get_image_packages` returns each package in the image with its name,\n  version, type (`deb`, `rpm`, `apk`, etc.), purl, licenses, and the file paths where\n  it was found.\n- `dhi_get_image_cves` returns each CVE affecting the image with its\n  severity, CVSS score and vector, affected package, fix version (if any), EPSS\n  probability score, and a flag indicating whether CISA lists it as\n  actively exploited.\n- `dhi_get_image_attestations` returns the predicate type and OCI reference\n  for each attestation attached to the image digest.\n- `dhi_list_mirrors` returns each mirror's ID, source DHI repository,\n  destination repository, and mirroring status for the given organization.\n- `dhi_create_mirror` starts mirroring a DHI source repository into the\n  specified organization and destination repository name.\n- `dhi_remove_mirror` stops mirroring for the given mirror ID. It does not\n  delete the destination repository — only stops new images from being synced.\n","content/manuals/dhi/tools/terraform.md":"---\ntitle: Use the DHI Terraform provider\nlinktitle: Terraform\ndescription: Use the DHI Terraform provider to manage mirrors and customizations as infrastructure as code.\nweight: 40\nkeywords: dhi terraform, docker hardened images terraform, infrastructure as code, dhi mirror terraform, dhi provider\n---\n\nThe [DHI Terraform provider](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs)\nlets you manage Docker Hardened Image mirrors and customizations as\ninfrastructure as code.\n\n## Install and configure the provider\n\nAdd the provider to your Terraform configuration:\n\n```hcl\nterraform {\n  required_providers {\n    dhi = {\n      source = \"docker-hardened-images/dhi\"\n    }\n  }\n}\n\nprovider \"dhi\" {\n  docker_hub_username = var.docker_username\n  docker_hub_password = var.docker_password\n  organization        = var.org_name\n}\n```\n\nInstead of specifying credentials in the provider block, you can set environment\nvariables:\n\n| Variable | Description |\n|----------|-------------|\n| `DOCKER_USERNAME` | Docker Hub username or organization namespace |\n| `DOCKER_PASSWORD` | Docker Hub password or personal/organization access token |\n| `DHI_ORG` | Target organization namespace |\n\nYou can authenticate using a personal access token (PAT) or an organization\naccess token (OAT) in place of a password. When using an OAT, permission scopes\napply:\n\n- Read (pull) access is required to list mirrors.\n- Push access is required to create or delete mirrors.\n\n## Resources\n\n### `dhi_mirror`\n\nManages a mirrored DHI repository in your organization. See [Mirror a Docker\nHardened Image repository](/dhi/how-to/mirror/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/mirror).\n\n### `dhi_customization`\n\nManages image customizations applied to a mirrored repository. See [Customize a\nDocker Hardened Image](/dhi/how-to/customize/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/customization).\n","content/manuals/extensions/_index.md":"---\ntitle: Docker Extensions\nweight: 60\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows\nparams:\n  sidebar:\n    group: Application development\naliases:\n - /desktop/extensions/\n---\n\nDocker Extensions let you use third-party tools within Docker Desktop to extend its functionality.\n\nYou can seamlessly connect your favorite development tools to your application development and deployment workflows. Augment Docker Desktop with debugging, testing, security, and networking functionalities, and create custom add-ons using the Extensions [SDK](extensions-sdk/_index.md).\n\nAnyone can use Docker Extensions and there is no limit to the number of extensions you can install.\n\n![Extensions Marketplace](/assets/images/extensions.webp)\n\n## What extensions are available?\n\nThere is a mix of partner and community-built extensions and Docker-built extensions.\nYou can explore the list of available extensions in [Docker Hub](https://hub.docker.com/search?q=&type=extension) or in the Extensions Marketplace within Docker Desktop.\n\n## Security and trust\n\nDocker Extensions run with elevated privileges on your host machine. They have direct access to the Docker Engine, can read and write files on your filesystem, and can install and run native binaries. \n\nDocker reviews extensions submitted to the Marketplace, but does not guarantee the security of any extension. Extensions installed outside the Marketplace have not been reviewed at all. Only install extensions from publishers you trust. \n\nIf you're an organization admin, see [Configure a private marketplace](private-marketplace.md) to control which extensions your team can install.","content/manuals/extensions/extensions-sdk/_index.md":"---\ntitle: Overview of the Extensions SDK\nlinkTitle: Extensions SDK\ndescription: Overall index for Docker Extensions SDK documentation\nkeywords: Docker, Extensions, sdk\naliases:\n - /desktop/extensions-sdk/dev/overview/\n - /desktop/extensions-sdk/\ngrid:\n  - title: \"The build and publish process\"\n    description: Understand the process for building and publishing an extension.\n    icon: clipboard-document-check\n    link: \"/extensions/extensions-sdk/process/\"\n  - title: \"Quickstart guide\"\n    description: Follow the quickstart guide to create a basic Docker extension quickly.\n    icon: magnifying-glass-plus\n    link: \"/extensions/extensions-sdk/quickstart/\"\n  - title: \"View the design guidelines\"\n    description: Ensure your extension aligns to Docker's design guidelines and principles.\n    icon: paint-brush\n    link: \"/extensions/extensions-sdk/design/design-guidelines/\"\n  - title: \"Publish your extension\"\n    description: Understand how to publish your extension to the Marketplace.\n    icon: arrow-up-tray\n    link: \"/extensions/extensions-sdk/extensions/\"\n  - title: \"Interacting with Kubernetes\"\n    description: Find information on how to interact indirectly with a Kubernetes cluster from your Docker extension.\n    icon: arrows-right-left\n    link: \"/extensions/extensions-sdk/guides/kubernetes/\"\n  - title: \"Multi-arch extensions\"\n    description: Build your extension for multiple architectures.\n    icon: document-duplicate\n    link: \"/extensions/extensions-sdk/extensions/multi-arch/\"\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nThe resources in this section help you create your own Docker extension.\n\nThe Docker CLI tool provides a set of commands to help you build and publish your extension, packaged as a \nspecially formatted Docker image.\n\nAt the root of the image filesystem is a `metadata.json` file which describes the content of the extension. \nIt's a fundamental element of a Docker extension.\n\nAn extension can contain a UI part and backend parts that run either on the host or in the Desktop virtual machine.\nFor further information, see [Architecture](architecture/_index.md).\n\nYou distribute extensions through Docker Hub. However, you can develop them locally without the need to push \nthe extension to Docker Hub. See [Extensions distribution](extensions/DISTRIBUTION.md) for further details.\n\n{{% include \"extensions-form.md\" %}}\n\n{{< grid >}}\n","content/manuals/extensions/extensions-sdk/architecture/_index.md":"---\ntitle: Extension architecture\nlinkTitle: Architecture\ndescription: Docker extension architecture\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/architecture/\nweight: 50\n---\n\nExtensions are applications that run inside the Docker Desktop. They're packaged as Docker images, distributed\nthrough Docker Hub, and installed by users either through the Marketplace within the Docker Desktop Dashboard or the\nDocker Extensions CLI.\n\nExtensions can be composed of three (optional) components:\n- A frontend (or User Interface): A web application displayed in a tab of the dashboard in Docker Desktop\n- A backend: One or many containerized services running in the Docker Desktop VM\n- Executables: Shell scripts or binaries that Docker Desktop copies on the host when installing the extension\n\n![Overview of the three components of an extension](images/extensions-architecture.png?w=600h=400)\n\nAn extension doesn't necessarily need to have all these components, but at least one of them depending on the extension features. \nTo configure and run those components, Docker Desktop uses a `metadata.json` file. See the\n[metadata](metadata) section for more details.\n\n## The frontend\n\nThe frontend is basically a web application made from HTML, Javascript, and CSS. It can be built with a simple HTML\nfile, some vanilla Javascript or any frontend framework, such as React or Vue.js.\n\nWhen Docker Desktop installs the extension, it extracts the UI folder from the extension image, as defined by the \n`ui` section in the `metadata.json`. See the [ui metadata section](metadata.md#ui-section) for more details.\n\nEvery time users click on the **Extensions** tab, Docker Desktop initializes the extension's UI as if it was the first time. When they navigate away from the tab, both the UI itself and all the sub-processes started by it (if any) are terminated.\n\nThe frontend can invoke `docker` commands, communicate with the extension backend, or invoke extension executables\ndeployed on the host, through the [Extensions SDK](https://www.npmjs.com/package/@docker/extension-api-client).\n\n> [!TIP]\n>\n> The `docker extension init` generates a React based extension. But you can still use it as a starting point for\n> your own extension and use any other frontend framework, like Vue, Angular, Svelte, etc. or event stay with\n> vanilla Javascript.\n\nLearn more about [building a frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) for your extension.\n\n## The backend\n\nAlongside a frontend application, extensions can also contain one or many backend services. In most cases, the Extension does not need a backend, and features can be implemented just by invoking docker commands through the SDK. However, there are some cases when an extension requires a backend\n\tservice, for example:\n- To run long-running processes that must outlive the frontend\n- To store data in a local database and serve them back with a REST API\n- To store the extension state, like when a button starts a long-running process, so that if you navigate away\n  from the extension and come back, the frontend can pick up where it left off\n- To access specific resources in the Docker Desktop VM, for example by mounting folders in the compose\nfile\n\n> [!TIP]\n>\n> The `docker extension init` generates a Go backend. But you can still use it as a starting point for\n> your own extension and use any other language like Node.js, Python, Java, .Net, or any other language and framework.\n\nUsually, the backend is made of one container that runs within the Docker Desktop VM. Internally, Docker Desktop creates\na Docker Compose project, creates the container from the `image` option of the `vm` section of the `metadata.json`, and\nattaches it to the Compose project. See the [`vm` metadata section](metadata.md#vm-section) for more details.\n\nIn some cases, a `compose.yaml` file can be used instead of an `image`. This is useful when the backend container\nneeds more specific options, such as mounting volumes or requesting [capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)\nthat can't be expressed just with a Docker image. The `compose.yaml` file can also be used to add multiple containers\nneeded by the extension, like a database or a message broker. \nNote that, if the Compose file defines many services, the SDK can only contact the first of them.\n\n> [!NOTE]\n>\n> In some cases, it is useful to also interact with the Docker engine from the backend.\n> See [How to use the Docker socket](../guides/use-docker-socket-from-backend.md) from the backend.\n\nTo communicate with the backend, the Extension SDK provides [functions](../dev/api/backend.md#get) to make `GET`,\n`POST`, `PUT`, `HEAD`, and `DELETE` requests from the frontend. Under the hood, the communication is done through a socket\nor named pipe, depending on the operating system. If the backend was listening to a port, it would be difficult to\nprevent collision with other applications running on the host or in a container already. Also, some users are\nrunning Docker Desktop in constrained environments where they can't open ports on their machines.\n\n![Backend and frontend communication](images/extensions-arch-2.png?w=500h=300)\n\nFinally, the backend can be built with any technology, as long as it can run in a container and listen on a socket.\n\nLearn more about [adding a backend](/manuals/extensions/extensions-sdk/build/backend-extension-tutorial.md) to your extension.\n\n## Executables\n\nIn addition to the frontend and the backend, extensions can also contain executables. Executables are binaries or shell scripts\nthat are installed on the host when the extension is installed. The frontend can invoke them with [the extension SDK](../dev/api/backend.md#invoke-an-extension-binary-on-the-host).\n\nThese executables are useful when the extension needs to interact with a third-party CLI tool, like AWS, `kubectl`, etc.\nShipping those executables with the extension ensure that the CLI tool is always available, at the right version, on\nthe users' machine.\n\nWhen Docker Desktop installs the extension, it copies the executables on the host as defined by the `host` section in\nthe `metadata.json`. See the [`host` metadata section](metadata.md#host-section) for more details.\n\n![Executable and frontend communication](images/extensions-arch-3.png?w=250h=300)\n\nHowever, since they're executed on the users' machine, they have to be available to the platform they're running on.\nFor example, if you want to ship the `kubectl` executable, you need to provide a different version for Windows, Mac,\nand Linux. Multi arch images will also need to include binaries built for the right arch (AMD / ARM)\n\n\nSee the [host metadata section](metadata.md#host-section) for more details.\n\nLearn how to [invoke host binaries](../guides/invoke-host-binaries.md).\n","content/manuals/extensions/extensions-sdk/architecture/metadata.md":"---\ntitle: Extension metadata\nlinkTitle: Metadata\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases:\n - /desktop/extensions-sdk/extensions/METADATA\n - /desktop/extensions-sdk/architecture/metadata/\n---\n\n## The metadata.json file\n\nThe `metadata.json` file is the entry point for your extension. It contains the metadata for your extension, such as the\nname, version, and description. It also contains the information needed to build and run your extension. The image for\na Docker extension must include a `metadata.json` file at the root of its filesystem.\n\nThe format of the `metadata.json` file must be:\n\n```json\n{\n    \"icon\": \"extension-icon.svg\",\n    \"ui\": ...\n    \"vm\": ...\n    \"host\": ...\n}\n```\n\nThe `ui`, `vm`, and `host` sections are optional and depend on what a given extension provides. They describe the extension content to be installed.\n\n### UI section\n\nThe `ui` section defines a new tab that's added to the dashboard in Docker Desktop. It follows the form:\n\n```json\n\"ui\":{\n    \"dashboard-tab\":\n    {\n        \"title\":\"MyTitle\",\n        \"root\":\"/ui\",\n        \"src\":\"index.html\"\n    }\n}\n```\n\n`root` specifies the folder where the UI code is within the extension image filesystem.\n`src` specifies the entrypoint that should be loaded in the extension tab.\n\nOther UI extension points will be available in the future.\n\n### VM section\n\nThe `vm` section defines a backend service that runs inside the Desktop VM. It must define either an `image` or a\n`compose.yaml` file that specifies what service to run in the Desktop VM.\n\n```json\n\"vm\": {\n    \"image\":\"${DESKTOP_PLUGIN_IMAGE}\"\n},\n```\n\nWhen you use `image`, a default compose file is generated for the extension.\n\n> `${DESKTOP_PLUGIN_IMAGE}` is a specific keyword that allows an easy way to refer to the image packaging the extension.\n> It is also possible to specify any other full image name here. However, in many cases using the same image makes\n> things easier for extension development.\n\n```json\n\"vm\": {\n    \"composefile\": \"compose.yaml\"\n},\n```\n\nThe Compose file, with a volume definition for example, would look like:\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /host/path:/container/path\n```\n\n### Host section\n\nThe `host` section defines executables that Docker Desktop copies on the host.\n\n```json\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/myBinary\"\n          },\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/myBinary.exe\"\n          },\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/myBinary\"\n          },\n        ]\n      }\n    ]\n  }\n```\n\n`binaries` defines a list of binaries Docker Desktop copies from the extension image to the host.\n\n`path` specifies the binary path in the image filesystem. Docker Desktop is responsible for copying these files in its own location, and the JavaScript API allows invokes these binaries.\n\nLearn how to [invoke executables](../guides/invoke-host-binaries.md).\n","content/manuals/extensions/extensions-sdk/architecture/security.md":"---\ntitle: Extension security\nlinkTitle: Security\ndescription: Aspects of the security model of extensions\nkeywords: Docker, extensions, sdk, security\naliases:\n - /desktop/extensions-sdk/guides/security/\n - /desktop/extensions-sdk/architecture/security/\n---\n\n## Extension capabilities\n\nAn extension can have the following optional parts: \n* A user interface in HTML or JavaScript, displayed in Docker Desktop Dashboard\n* A backend part that runs as a container\n* Executables deployed on the host machine.\n\nExtensions are executed with the same permissions as the Docker Desktop user. Extension capabilities include running any Docker commands (including running containers and mounting folders), running extension binaries, and accessing files on your machine that are accessible by the user running Docker Desktop.\nNote that extensions are not restricted to execute binaries that they list in the [host section](../architecture/metadata.md#host-section) of the extension metadata: since these binaries can contain any code running as user, they can in turn execute any other commands as long as the user has rights to execute them.\n\nThe Extensions SDK provides a set of JavaScript APIs to invoke commands or invoke these binaries from the extension UI code. Extensions can also provide a backend part that starts a long-lived running container in the background.\n\n> [!IMPORTANT]\n>\n> Make sure you trust the publisher or author of the extension when you install it, as the extension has the same access rights as the user running Docker Desktop.\n","content/manuals/extensions/extensions-sdk/design/_index.md":"---\ntitle: UI styling overview for Docker extensions\nlinkTitle: Design and UI styling\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases:\n - /desktop/extensions-sdk/design/design-overview/\n - /desktop/extensions-sdk/design/overview/\n - /desktop/extensions-sdk/design/\nweight: 60\n---\n\nOur Design System is a constantly evolving set of specifications that aim to ensure visual consistency across Docker products, and meet [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance). We've opened parts of it to extension authors, documenting basic styles (color, typography) and components. See: [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771).\n\nWe require extensions to match the wider Docker Desktop UI to a certain degree, and reserve the right to make this stricter in the future.\n\nTo get started on your UI, follow the steps below.\n\n## Step one: Choose your framework\n\n### Recommended: React+MUI, using our theme\n\nDocker Desktop's UI is written in React and [MUI](https://mui.com/) (using Material UI specifically). This is the only officially supported framework for building extensions, and the one that the `init` command automatically configures for you. Using it brings significant benefits to authors:\n\n- You can use our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme) to automatically replicate Docker Desktop's look and feel.\n- In future, we'll release utilities and components specifically targeting this combination (e.g. custom MUI components, or React hooks for interacting with Docker).\n\nRead our [MUI best practices](mui-best-practices.md) guide to learn future-proof ways to use MUI with Docker Desktop.\n\n### Not recommended: Some other framework\n\nYou may prefer to use another framework, perhaps because you or your team are more familiar with it or because you have existing assets you want to reuse. This is possible, but highly discouraged. It means that:\n\n- You'll need to manually replicate the look and feel of Docker Desktop. This takes a lot of effort, and if you don't match our theme closely enough, users will find your extension jarring and we may ask you to make changes during a review process.\n- You'll have a higher maintenance burden. Whenever Docker Desktop's theme changes (which could happen in any release), you'll need to manually change your extension to match it.\n- If your extension is open-source, deliberately avoiding common conventions will make it harder for the community to contribute to it.\n\n## Step two: Follow the below recommendations\n\n### Follow our MUI best practices (if applicable)\n\nSee our [MUI best practices](mui-best-practices.md) article.\n\n### Only use colors from our palette\n\nWith minor exceptions, displaying your logo for example, you should only use colors from our palette. These can be found in our [style guide document](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771), and will also soon be available in our MUI theme and via CSS variables.\n\n### Use counterpart colors in light/dark mode\n\nOur colors have been chosen so that the counterpart colors in each variant of the palette should have the same essential characteristics. Anywhere you use `red-300` in light mode, you should use `red-300` in dark mode too.\n\n## What's next?\n\n- Take a look at our [MUI best practices](mui-best-practices.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/design-guidelines.md":"---\ntitle: Design guidelines for Docker extensions\nlinkTitle: Guidelines\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-guidelines/\nweight: 10\n---\n\nAt Docker, we aim to build tools that integrate into a user's existing workflows rather than requiring them to adopt new ones. We strongly recommend that you follow these guidelines when creating extensions. We review and approve your Marketplace publication based on these requirements.\n\nHere is a simple checklist to go through when creating your extension:\n- Is it easy to get started?\n- Is it easy to use?\n- Is it easy to get help when needed?\n\n\n## Create a consistent experience with Docker Desktop\n\nUse the [Docker Material UI Theme](https://www.npmjs.com/package/@docker/docker-mui-theme) and the [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771) to ensure that your extension feels like it is part of Docker Desktop to create a seamless experience for users.\n\n- Ensure the extension has both a light and dark theme. Using the components and styles as per the Docker style guide ensures that your extension meets the [level AA accessibility standard.](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n  ![Light and dark mode](images/light_dark_mode.webp)\n\n- Ensure that your extension icon is visible both in light and dark mode.\n\n  ![Icon colors in light and dark mode](images/icon_colors.webp)\n\n- Ensure that the navigational behavior is consistent with the rest of Docker Desktop. Add a header to set the context for the extension.\n\n  ![Header that sets the context](images/header.webp)\n\n- Avoid embedding terminal windows. The advantage we have with Docker Desktop over the CLI is that we have the opportunity to provide rich information to users. Make use of this interface as much as possible. \n\n  ![Terminal window used incorrectly](images/terminal_window_dont.webp)\n\n  ![Terminal window used correctly](images/terminal_window_do.webp)\n\n## Build features natively\n\n- In order not to disrupt the flow of users, avoid scenarios where the user has to navigate outside Docker Desktop, to the CLI or a webpage for example, in order to carry out certain functionalities. Instead, build features that are native to Docker Desktop.\n\n  ![Incorrect way to switch context](images/switch_context_dont.webp)\n\n  ![Correct way to switch context](images/switch_context_do.webp)\n\n## Break down complicated user flows\n\n- If a flow is too complicated or the concept is abstract, break down the flow into multiple steps with one simple call-to-action in each step. This helps when onboarding novice users to your extension\n\n  ![A complicated flow](images/complicated_flows.webp)\n\n- Where there are multiple call-to-actions, ensure you use the primary (filled button style) and secondary buttons (outline button style) to convey the importance of each action.\n\n  ![Call to action](images/cta.webp)\n\n## Onboarding new users\n\nWhen creating your extension, ensure that first time users of the extension and your product can understand its value-add and adopt it easily. Ensure you include contextual help within the extension.\n\n- Ensure that all necessary information is added to the extensions Marketplace as well as the extensions detail page. This should include:\n  - Screenshots of the extension. Note that the recommended size for screenshots is 2400x1600 pixels. \n  - A detailed description that covers what the purpose of the extension is, who would find it useful and how it works.\n  - Link to necessary resources such as documentation.\n- If your extension has particularly complex functionality, add a demo or video to the start page. This helps onboard a first time user quickly.\n\n  ![start page](images/start_page.webp)\n\n## What's next?\n\n- Explore our [design principles](design-principles.md).\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/design-principles.md":"---\ntitle: Docker design principles\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-principles/\nweight: 20\n---\n\n## Provide actionable guidance\n\nWe anticipate needs and provide simple explanations with clear actions so people are never lost and always know what to do next. Recommendations lead users to functionality that enhances the experience and extends their knowledge.\n\n## Create value through confidence\n\nPeople from all levels of experience should feel they know how to use our product. Experiences are familiar, unified, and easy to use so all users feel like experts.\n\n## Infuse productivity with delight\n\nWe seek out moments of purposeful delight that elevate rather than distract, making work easier and more gratifying. Simple tasks are automated and users are left with more time for innovation.\n\n## Build trust through transparency\n\nWe always provide clarity on what is happening and why. No amount of detail is withheld; the right information is shown at the right time and is always accessible.\n\n## Scale with intention\n\nOur products focus on inclusive growth and are continuously useful and adapt to match changing individual needs. We support all levels of expertise by meeting users where they are with conscious personalization.\n\n## What's next?\n\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/mui-best-practices.md":"---\ntitle: MUI best practices\ndescription: Guidelines for using MUI to maximize compatibility with Docker Desktop\nkeywords: Docker, extensions, mui, theme, theming, material-ui, material\naliases: \n - /desktop/extensions-sdk/design/mui-best-practices/\n---\n\nThis article assumes you're following our recommended practice by using our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme).\nFollowing the steps below maximizes compatibility with Docker Desktop and minimizes the work you need to do as an\nextension author. They should be considered supplementary to the non-MUI-specific guidelines found in the\n[UI Styling overview](index.md).\n\n## Assume the theme can change at any time\n\nResist the temptation to fine-tune your UI with precise colors, offsets and font sizings to make it look as attractive as possible. Any specializations you make today will be relative to the current MUI theme, and may look worse when the theme changes. Any part of the theme might change without warning, including (but not limited to):\n\n-  The font, or font sizes\n-  Border thicknesses or styles\n-  Colors:\n   -  Our palette members (e.g. `red-100`) could change their RGB values\n   -  The semantic colors (e.g. `error`, `primary`, `textPrimary`, etc) could be changed to use a different member of our palette\n   -  Background colors (e.g. those of the page, or of dialogs) could change\n-  Spacings:\n   -  The size of the basic unit of spacing,(exposed via `theme.spacing`. For instance, we may allow users to customize the density of the UI\n   -  The default spacing between paragraphs or grid items\n\nThe best way to build your UI, so that it’s robust against future theming changes, is to:\n\n-  Override the default styling as little as possible.\n-  Use semantic typography. e.g. use `Typography`s or `Link`s with appropriate `variant`s instead of using typographical HTML elements (`<a>`, `<p>`, `<h1>`, etc) directly.\n-  Use canned sizes. e.g. use `size=\"small\"` on buttons, or `fontSize=\"small\"` on icons, instead of specifying sizes in pixels.\n-  Prefer semantic colors. e.g. use `error` or `primary` over explicit color codes.\n-  Write as little CSS as possible. Write semantic markup instead. For example, if you want to space out paragraphs of text, use the `paragraph` prop on your `Typography` instances. If you want to space out something else, use a `Stack` or `Grid` with the default spacing.\n-  Use visual idioms you’ve seen in the Docker Desktop UI, since these are the main ones we’ll test any theme changes against.\n\n## When you go custom, centralize it\n\nSometimes you’ll need a piece of UI that doesn’t exist in our design system. If so, we recommend that you first reach out to us. We may already have something in our internal design system, or we may be able to expand our design system to accommodate your use case.\n\nIf you still decide to build it yourself after contacting us, try and define the new UI in a reusable fashion. If you define your custom UI in just one place, it’ll make it easier to change in the future if our core theme changes. You could use:\n\n-  A new `variant` of an existing component - see [MUI docs](https://mui.com/material-ui/customization/theme-components/#creating-new-component-variants)\n-  A MUI mixin (a freeform bundle of reusable styling rules defined inside a theme)\n-  A new [reusable component](https://mui.com/material-ui/customization/how-to-customize/#2-reusable-component)\n\nSome of the above options require you to extend our MUI theme. See the MUI documentation on [theme composition](https://mui.com/material-ui/customization/theming/#nesting-the-theme).\n\n## What's next?\n\n- Take a look at our [UI styling guide](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/_index.md":"---\nbuild:\n  render: never\ntitle: Developer SDK tools\n---\n","content/manuals/extensions/extensions-sdk/dev/api/_index.md":"---\nbuild:\n  render: never\ntitle: Extension APIs\n---\n","content/manuals/extensions/extensions-sdk/dev/api/backend.md":"---\ntitle: Extension Backend\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/backend/\n---\n\nThe `ddClient.extension.vm` object can be used to communicate with the backend defined in the [vm section](../../architecture/metadata.md#vm-section) of the extension metadata.\n\n## get\n\n▸ **get**(`url`): `Promise`<`unknown`\\>\n\nPerforms an HTTP GET request to a backend service.\n\n```typescript\nddClient.extension.vm.service\n .get(\"/some/service\")\n .then((value: any) => console.log(value)\n```\n\nSee [Service API Reference](/reference/api/extensions-sdk/HttpService.md) for other HTTP methods.\n\n> Deprecated extension backend communication\n>\n> The methods below that use `window.ddClient.backend` are deprecated and will be removed in a future version. Use the methods specified above.\n\nThe `window.ddClient.backend` object can be used to communicate with the backend\ndefined in the [vm section](../../architecture/metadata.md#vm-section) of the\nextension metadata. The client is already connected to the backend.\n\nExample usages:\n\n```typescript\nwindow.ddClient.backend\n  .get(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .post(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .put(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .patch(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .delete(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .head(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .request({ url: \"/url\", method: \"GET\", headers: { 'header-key': 'header-value' }, data: { ... }})\n  .then((value: any) => console.log(value));\n```\n\n## Run a command in the extension backend container\n\nFor example, execute the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"]);\n```\n\nStream the output of the command executed in the backend container. For example, spawn the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nFor more details, refer to the [Extension VM API Reference](/reference/api/extensions-sdk/ExtensionVM.md)\n\n> Deprecated extension backend command execution\n>\n> This method is deprecated and will be removed in a future version. Use the specified method above.\n\nIf your extension ships with additional binaries that should be run inside the\nbackend container, you can use the `execInVMExtension` function:\n\n```typescript\nconst output = await window.ddClient.backend.execInVMExtension(\n  `cliShippedInTheVm xxx`\n);\nconsole.log(output);\n```\n\n## Invoke an extension binary on the host\n\nInvoke a binary on the host. The binary is typically shipped with your extension using the [host section](../../architecture/metadata.md#host-section) in the extension metadata. Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nFor example, execute the shipped binary `kubectl -h` command in the host:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"]);\n```\n\nAs long as the `kubectl` binary is shipped as part of your extension, you can spawn the `kubectl -h` command in the host and get the output stream:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"], {\n  stream: {\n    onOutput(data: { stdout: string } | { stderr: string }): void {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error: any): void {\n      console.error(error);\n    },\n    onClose(exitCode: number): void {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nYou can stream the output of the command executed in the backend container or in the host.\n\nFor more details, refer to the [Extension Host API Reference](/reference/api/extensions-sdk/ExtensionHost.md)\n\n> Deprecated invocation of extension binary\n>\n> This method is deprecated and will be removed in a future version. Use the method specified above.\n\nTo execute a command in the host:\n\n```typescript\nwindow.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((cmdResult: any) => {\n  console.log(cmdResult);\n});\n```\n\nTo stream the output of the command executed in the backend container or in the host:\n\n```typescript\nwindow.ddClient.spawnHostCmd(\n  `cliShippedOnHost`,\n  [`arg1`, `arg2`],\n  (data: any, err: any) => {\n    console.log(data.stdout, data.stderr);\n    // Once the command exits we get the status code\n    if (data.code) {\n      console.log(data.code);\n    }\n  }\n);\n```\n\n> [!NOTE]\n> \n>You cannot use this to chain commands in a single `exec()` invocation (like `cmd1 $(cmd2)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n","content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md":"---\ntitle: Navigation\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard-routes-navigation/\n---\n\n`ddClient.desktopUI.navigate` enables navigation to specific screens of Docker Desktop such as the containers tab, the images tab, or a specific container's logs.\n\nFor example, navigate to a given container logs:\n\n```typescript\nconst id = '8c7881e6a107';\ntry {\n  await ddClient.desktopUI.navigate.viewContainerLogs(id);\n} catch (e) {\n  console.error(e);\n  ddClient.desktopUI.toast.error(\n    `Failed to navigate to logs for container \"${id}\".`\n  );\n}\n```\n\n#### Parameters\n\n| Name | Type     | Description                                                                                                                                                                                            |\n| :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id` | `string` | The full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`. You can use the `--no-trunc` flag as part of the `docker ps` command to display the full container id. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\nA promise that fails if the container doesn't exist.\n\nFor more details about all navigation methods, see the [Navigation API reference](/reference/api/extensions-sdk/NavigationIntents.md).\n\n> Deprecated navigation methods\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.navigateToContainers();\n// id - the full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`\nwindow.ddClient.navigateToContainer(id);\nwindow.ddClient.navigateToContainerLogs(id);\nwindow.ddClient.navigateToContainerInspect(id);\nwindow.ddClient.navigateToContainerStats(id);\n\nwindow.ddClient.navigateToImages();\nwindow.ddClient.navigateToImage(id, tag);\n\nwindow.ddClient.navigateToVolumes();\nwindow.ddClient.navigateToVolume(volume);\n\nwindow.ddClient.navigateToDevEnvironments();\n```\n","content/manuals/extensions/extensions-sdk/dev/api/dashboard.md":"---\ntitle: Dashboard\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard/\n---\n\n## User notifications\n\nToasts provide a brief notification to the user. They appear temporarily and\nshouldn't interrupt the user experience. They also don't require user input to disappear.\n\n### success\n\n▸ **success**(`msg`): `void`\n\nUse to display a toast message of type success.\n\n```typescript\nddClient.desktopUI.toast.success(\"message\");\n```\n\n### warning\n\n▸ **warning**(`msg`): `void`\n\nUse to display a toast message of type warning.\n\n```typescript\nddClient.desktopUI.toast.warning(\"message\");\n```\n\n### error\n\n▸ **error**(`msg`): `void`\n\nUse to display a toast message of type error.\n\n```typescript\nddClient.desktopUI.toast.error(\"message\");\n```\n\nFor more details about method parameters and the return types available, see [Toast API reference](/reference/api/extensions-sdk/Toast.md).\n\n> Deprecated user notifications\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.toastSuccess(\"message\");\nwindow.ddClient.toastWarning(\"message\");\nwindow.ddClient.toastError(\"message\");\n```\n\n## Open a file selection dialog\n\nThis function opens a file selector dialog that asks the user to select a file or folder.\n\n▸ **showOpenDialog**(`dialogProperties`): `Promise`<[`OpenDialogResult`](/reference/api/extensions-sdk/OpenDialogResult.md)\\>:\n\nThe `dialogProperties` parameter is a list of flags passed to Electron to customize the dialog's behaviour. For example, you can pass `multiSelections` to allow a user to select multiple files. See [Electron's documentation](https://www.electronjs.org/docs/latest/api/dialog) for a full list.\n\n```typescript\nconst result = await ddClient.desktopUI.dialog.showOpenDialog({\n  properties: [\"openDirectory\"],\n});\nif (!result.canceled) {\n  console.log(result.paths);\n}\n```\n\n## Open a URL\n\nThis function opens an external URL with the system default browser.\n\n▸ **openExternal**(`url`): `void`\n\n```typescript\nddClient.host.openExternal(\"https://docker.com\");\n```\n\n> The URL must have the protocol `http` or `https`.\n\nFor more details about method parameters and the return types available, see [Desktop host API reference](/reference/api/extensions-sdk/Host.md).\n\n> Deprecated external URL opening\n>\n> This method is deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.openExternal(\"https://docker.com\");\n```\n\n## Navigation to Dashboard routes\n\nFrom your extension, you can also [navigate](dashboard-routes-navigation.md) to other parts of the Docker Desktop Dashboard.\n","content/manuals/extensions/extensions-sdk/dev/api/docker.md":"---\ntitle: Docker\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/docker/\n---\n\n## Docker objects\n\n▸ **listContainers**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of containers:\n\n```typescript\nconst containers = await ddClient.docker.listContainers();\n```\n\n▸ **listImages**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of local container images:\n\n```typescript\nconst images = await ddClient.docker.listImages();\n```\n\nSee the [Docker API reference](/reference/api/extensions-sdk/Docker.md) for details about these methods.\n\n> Deprecated access to Docker objects\n>\n> The methods below are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nconst containers = await window.ddClient.listContainers();\n\nconst images = await window.ddClient.listImages();\n```\n\n## Docker commands\n\nExtensions can also directly execute the `docker` command line.\n\n▸ **exec**(`cmd`, `args`): `Promise`<[`ExecResult`](/reference/api/extensions-sdk/ExecResult.md)\\>\n\n```typescript\nconst result = await ddClient.docker.cli.exec(\"info\", [\n  \"--format\",\n  '\"{{ json . }}\"',\n]);\n```\n\nThe result contains both the standard output and the standard error of the executed command:\n\n```json\n{\n  \"stderr\": \"...\",\n  \"stdout\": \"...\"\n}\n```\n\nIn this example, the command output is JSON.\nFor convenience, the command result object also has methods to easily parse it:\n\n- `result.lines(): string[]` splits output lines.\n- `result.parseJsonObject(): any` parses a well-formed json output.\n- `result.parseJsonLines(): any[]` parses each output line as a json object.\n\n▸ **exec**(`cmd`, `args`, `options`): `void`\n\nThe command above streams the output as a result of the execution of a Docker command.\nThis is useful if you need to get the output as a stream or the output of the command is too long.\n\n```typescript\nawait ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n    splitOutputLines: true,\n  },\n});\n```\n\nThe child process created by the extension is killed (`SIGTERM`) automatically when you close the dashboard in Docker Desktop or when you exit the extension UI.\nIf needed, you can also use the result of the `exec(streamOptions)` call in order to kill (`SIGTERM`) the process.\n\n```typescript\nconst logListener = await ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    // ...\n  },\n});\n\n// when done listening to logs or before starting a new one, kill the process\nlogListener.close();\n```\n\nThis `exec(streamOptions)` API can also be used to listen to docker events:\n\n```typescript\nawait ddClient.docker.cli.exec(\n  \"events\",\n  [\"--format\", \"{{ json . }}\", \"--filter\", \"container=my-container\"],\n  {\n    stream: {\n      onOutput(data) {\n        if (data.stdout) {\n          const event = JSON.parse(data.stdout);\n          console.log(event);\n        } else {\n          console.log(data.stderr);\n        }\n      },\n      onClose(exitCode) {\n        console.log(\"onClose with exit code \" + exitCode);\n      },\n      splitOutputLines: true,\n    },\n  }\n);\n```\n\n> [!NOTE]\n>\n>You cannot use this to chain commands in a single `exec()` invocation (like `docker kill $(docker ps -q)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n\nSee the [Exec API reference](/reference/api/extensions-sdk/Exec.md) for details about these methods.\n\n> Deprecated execution of Docker commands\n>\n> This method is deprecated and will be removed in a future version. Use the one specified just below.\n\n```typescript\nconst output = await window.ddClient.execDockerCmd(\n  \"info\",\n  \"--format\",\n  '\"{{ json . }}\"'\n);\n\nwindow.ddClient.spawnDockerCmd(\"logs\", [\"-f\", \"...\"], (data, error) => {\n  console.log(data.stdout);\n});\n```\n","content/manuals/extensions/extensions-sdk/dev/api/overview.md":"---\ntitle: Extension UI API\ndescription: Docker extension development overview\nkeywords: Docker, extensions, sdk, development\naliases:\n - /desktop/extensions-sdk/dev/api/overview/\n---\n\nThe extensions UI runs in a sandboxed environment and doesn't have access to any\nelectron or nodejs APIs.\n\nThe extension UI API provides a way for the frontend to perform different actions\nand communicate with the Docker Desktop dashboard or the underlying system.\n\nJavaScript API libraries, with Typescript support, are available in order to get all the API definitions in to your extension code.\n\n- [@docker/extension-api-client](https://www.npmjs.com/package/@docker/extension-api-client) gives access to the extension API entrypoint `DockerDesktopClient`.\n- [@docker/extension-api-client-types](https://www.npmjs.com/package/@docker/extension-api-client-types) can be added as a dev dependency in order to get types auto-completion in your IDE.\n\n```Typescript\nimport { createDockerDesktopClient } from '@docker/extension-api-client';\n\nexport function App() {\n  // obtain Docker Desktop client\n  const ddClient = createDockerDesktopClient();\n  // use ddClient to perform extension actions\n}\n```\n\nThe `ddClient` object gives access to various APIs:\n\n- [Extension Backend](backend.md)\n- [Docker](docker.md)\n- [Dashboard](dashboard.md)\n- [Navigation](dashboard-routes-navigation.md)\n\nSee also the [Extensions API reference](/reference/api/extensions-sdk/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/continuous-integration.md":"---\ntitle: Continuous Integration (CI)\ndescription: Automatically test and validate your extension.\nkeywords: Docker, Extensions, sdk, CI, test, regression\naliases: \n - /desktop/extensions-sdk/dev/continuous-integration/\nweight: 20\n---\n\nIn order to help validate your extension and ensure it's functional, the Extension SDK provides tools to help you setup continuous integration for your extension.\n\n> [!IMPORTANT]\n>\n> The [Docker Desktop Action](https://github.com/docker/desktop-action) and the [extension-test-helper library](https://www.npmjs.com/package/@docker/extension-test-helper) are both [experimental](https://docs.docker.com/release-lifecycle/#experimental).\n\n## Setup CI environment with GitHub Actions\n\nYou need Docker Desktop to be able to install and validate your extension.\nYou can start Docker Desktop in GitHub Actions using the [Docker Desktop Action](https://github.com/docker/desktop-action), by adding the following to a workflow file:\n\n```yaml\nsteps:\n  - id: start_desktop\n    uses: docker/desktop-action/start@v0.1.0\n```\n\n> [!NOTE]\n>\n> This action supports only GitHub Actions macOS runners at the moment. You need to specify `runs-on: macOS-latest` for your end to end tests.\n\nOnce the step has executed, the next steps use Docker Desktop and the Docker CLI to install and test the extension.\n\n## Validating your extension with Puppeteer\n\nOnce Docker Desktop starts in CI, you can build, install, and validate your extension with Jest and Puppeteer.\n\nFirst, build and install the extension from your test:\n\n```ts\nimport { DesktopUI } from \"@docker/extension-test-helper\";\nimport { exec as originalExec } from \"child_process\";\nimport * as util from \"util\";\n\nexport const exec = util.promisify(originalExec);\n\n// keep a handle on the app to stop it at the end of tests\nlet dashboard: DesktopUI;\n\nbeforeAll(async () => {\n  await exec(`docker build -t my/extension:latest .`, {\n    cwd: \"my-extension-src-root\",\n  });\n\n  await exec(`docker extension install -f my/extension:latest`);\n});\n```\n\nThen open the Docker Desktop Dashboard and run some tests in your extension's UI:\n\n```ts\ndescribe(\"Test my extension\", () => {\n  test(\"should be functional\", async () => {\n    dashboard = await DesktopUI.start();\n\n    const eFrame = await dashboard.navigateToExtension(\"my/extension\");\n\n    // use puppeteer APIs to manipulate the UI, click on buttons, expect visual display and validate your extension\n    await eFrame.waitForSelector(\"#someElementId\");\n  });\n});\n```\n\nFinally, close the Docker Desktop Dashboard and uninstall your extension:\n\n```ts\nafterAll(async () => {\n  dashboard?.stop();\n  await exec(`docker extension uninstall my/extension`);\n});\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/test-debug.md":"---\ntitle: Test and debug\ndescription: Test and debug your extension.\nkeywords: Docker, Extensions, sdk, preview, update, Chrome DevTools\naliases:\n - /desktop/extensions-sdk/build/test-debug/\n - /desktop/extensions-sdk/dev/test-debug/\nweight: 10\n---\n\nIn order to improve the developer experience, Docker Desktop provides a set of tools to help you test and debug your extension.\n\n### Open Chrome DevTools\n\nIn order to open the Chrome DevTools for your extension when you select the **Extensions** tab, run:\n\n```console\n$ docker extension dev debug <name-of-your-extensions>\n```\n\nEach subsequent click on the extension tab also opens Chrome DevTools. To stop this behaviour, run:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\nAfter an extension is deployed, it is also possible to open Chrome DevTools from the UI extension part using a variation of the [Konami Code](https://en.wikipedia.org/wiki/Konami_Code). Select the **Extensions** tab, and then hit the key sequence `up, up, down, down, left, right, left, right, p, d, t`.\n\n### Hot reloading whilst developing the UI\n\nDuring UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\nextension. To do this, you can configure Docker Desktop to load your UI from a development server, such as the one\n[Vite](https://vitejs.dev/) starts when invoked with `npm start`.\n\nAssuming your app runs on the default port, start your UI app and then run:\n\n```console\n$ cd ui\n$ npm run dev\n```\n\nThis starts a development server that listens on port 3000.\n\nYou can now tell Docker Desktop to use this as the frontend source. In another terminal run:\n\n```console\n$ docker extension dev ui-source <name-of-your-extensions> http://localhost:3000\n```\n\nClose and reopen the Docker Desktop dashboard and go to your extension. All the changes to the frontend code are immediately visible.\n\nOnce finished, you can reset the extension configuration to the original settings. This will also reset opening Chrome DevTools if you used `docker extension dev debug <name-of-your-extensions>`:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\n## Show the extension containers\n\nIf your extension is composed of one or more services running as containers in the Docker Desktop VM, you can access them easily from the dashboard in Docker Desktop.\n\n1. In Docker Desktop, navigate to **Settings**.\n2. Under the **Extensions** tab, select the **Show Docker Desktop Extensions system containers** option. You can now view your extension containers and their logs.\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Explore our [design principles](../design/design-principles.md).\n- Take a look at our [UI styling guidelines](../design/_index.md).\n- Learn how to [setup CI for your extension](continuous-integration.md).\n","content/manuals/extensions/extensions-sdk/dev/usage.md":"---\ntitle: CLI reference\ndescription: Docker extension CLI\nkeywords: Docker, extensions, sdk, CLI\naliases:\n - /desktop/extensions-sdk/dev/cli/usage/\n - /desktop/extensions-sdk/dev/usage/\nweight: 30\n---\n\nThe Extensions CLI is an extension development tool that is used to manage Docker extensions. Actions include install, list, remove, and validate extensions.\n\n- `docker extension enable` turns on Docker extensions.\n- `docker extension dev` commands for extension development.\n- `docker extension disable` turns off Docker extensions.\n- `docker extension init` creates a new Docker extension.\n- `docker extension install` installs a Docker extension with the specified image.\n- `docker extension ls` list installed Docker extensions.\n- `docker extension rm` removes a Docker extension.\n- `docker extension update` removes and re-installs a Docker extension.\n- `docker extension validate` validates the extension metadata file against the JSON schema.\n","content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md":"---\ntitle: Package and release your extension\ndescription: Docker extension distribution\nkeywords: Docker, extensions, sdk, distribution\naliases: \n - /desktop/extensions-sdk/extensions/DISTRIBUTION/\nweight: 30\n---\n\nThis page contains additional information on how to package and distribute extensions.\n\n## Package your extension\n\nDocker extensions are packaged as Docker images. The entire extension runtime including the UI, backend services (host or VM), and any necessary binary must be included in the extension image.\nEvery extension image must contain a `metadata.json` file at the root of its filesystem that defines the [contents of the extension](../architecture/metadata.md).\n\nThe Docker image must have several [image labels](labels.md), providing information about the extension. See how to use [extension labels](labels.md) to provide extension overview information.\n\nTo package and release an extension, you must build a Docker image (`docker build`), and push the image to [Docker Hub](https://hub.docker.com/) (`docker push`) with a specific tag that lets you manage versions of the extension.\n\n## Release your extension\n\nDocker image tags must follow semver conventions in order to allow fetching the latest version of the extension, and to know if there are updates available. See [semver.org](https://semver.org/) to learn more about semantic versioning.\n\nExtension images must be multi-arch images so that users can install extensions on ARM/AMD hardware. These multi-arch images can include ARM/AMD specific binaries. Mac users will automatically use the right image based on their architecture.\nExtensions that install binaries on the host must also provide Windows binaries in the same extension image. See how to [build a multi-arch image](multi-arch.md) for your extension.\n\nYou can implement extensions without any constraints on the code repository. Docker doesn't need access to the code repository in order to use the extension. Also, you can manage new releases of your extension, without any dependency on Docker Desktop releases.\n\n## New releases and updates\n\nYou can release a new version of your Docker extension by pushing a new image with a new tag to Docker Hub.\n\nAny new image pushed to an image repository corresponding to an extension defines a new version of that extension. Image tags are used to identify version numbers. Extension versions must follow semver to make it easy to understand and compare versions.\n\nDocker Desktop scans the list of extensions published in the marketplace for new versions, and provides notifications to users when they can upgrade a specific extension. Extensions that aren't part of the Marketplace don't have automatic update notifications at the moment.\n\nUsers can download and install the newer version of any extension without updating Docker Desktop itself.\n\n## Extension API dependencies\n\nExtensions must specify the Extension API version they rely on. Docker Desktop checks the extension's required version, and only proposes to install extensions that are compatible with the current Docker Desktop version installed. Users might need to update Docker Desktop in order to install the latest extensions available.\n\nExtension image labels must specify the API version that the extension relies upon. This allows Docker Desktop to inspect newer versions of extension images without downloading the full extension image upfront.\n\n## License on extensions and the extension SDK\n\nThe [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use.\n\nThere is no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n","content/manuals/extensions/extensions-sdk/extensions/_index.md":"---\ntitle: \"Part two: Publish\"\ndescription: General steps in how to publish an extension\nkeywords: Docker, Extensions, sdk, publish\naliases: \n - /desktop/extensions-sdk/extensions/\nweight: 40\n---\n\nThis section describes how to make your extension available and more visible, so users can discover it and install it with a single click.\n\n## Release your extension\n\nAfter you have developed your extension and tested it locally, you are ready to release the extension and make it available for others to install and use (either internally with your team, or more publicly).\n\nReleasing your extension consists of:\n\n- Providing information about your extension: description, screenshots, etc. so users can decide to install your extension\n- [Validating](validate.md) that the extension is built in the right format and includes the required information\n- Making the extension image available on [Docker Hub](https://hub.docker.com/)\n\nSee [Package and release your extension](DISTRIBUTION.md) for more details about the release process.\n\n## Promote your extension\n\nOnce your extension is available on Docker Hub, users who have access to the extension image can install it using the Docker CLI.\n\n### Use a share extension link\n\nYou can also [generate a share URL](share.md) in order to share your extension within your team, or promote your extension on the internet. The share link lets users view the extension description and screenshots.\n\n### Publish your extension in the Marketplace\n\nYou can publish your extension in the Extensions Marketplace to make it more discoverable. You must [submit your extension](publish.md) if you want to have it published in the Marketplace.\n\n## What happens next\n\n### New releases\n\nOnce you have released your extension, you can push a new release just by pushing a new version of the extension image, with an incremented tag (still using `semver` conventions).\nExtensions published in the Marketplace benefit from update notifications to all Desktop users that have installed the extension. For more details, see [new releases and updates](DISTRIBUTION.md#new-releases-and-updates).\n\n### Extension support and user feedback\n\nIn addition to providing a description of your extension's features and screenshots, you should also specify additional URLs using [extension labels](labels.md). This direct users to your website for reporting bugs and feedback, and accessing documentation and support.\n\n{{% include \"extensions-form.md\" %}}\n","content/manuals/extensions/extensions-sdk/extensions/labels.md":"---\ntitle: Extension image labels\nlinkTitle: Add labels\ndescription: Docker extension labels\nkeywords: Docker, extensions, sdk, labels\naliases: \n - /desktop/extensions-sdk/extensions/labels/\nweight: 10\n---\n\nExtensions use image labels to provide additional information such as a title, description, screenshots, and more.\n\nThis information is then displayed as an overview of the extension, so users can choose to install it.\n\n![An extension overview, generated from labels](images/marketplace-details.png)\n\nYou can define [image labels](/reference/dockerfile.md#label) in the extension's `Dockerfile`.\n\n> [!IMPORTANT]\n>\n> If any of the **required** labels are missing in the `Dockerfile`, Docker Desktop considers the extension invalid and doesn't list it in the Marketplace.\n\n\nHere is the list of labels you can or need to specify when building your extension:\n\n| Label                                       | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Example                                                                                                                                                                                                                                                         |\n| ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `org.opencontainers.image.title`            | Yes      | Human-readable title of the image (string). This appears in the UI for Docker Desktop.                                                                                                                                                                                                                                                                                                                                                                                                                | my-extension                                                                                                                                                                                                                                                    |\n| `org.opencontainers.image.description`      | Yes      | Human-readable description of the software packaged in the image (string)                                                                                                                                                                                                                                                                                                                                                                                                                             | This extension is cool.                                                                                                                                                                                                                                         |\n| `org.opencontainers.image.vendor`           | Yes      | Name of the distributing entity, organization, or individual.                                                                                                                                                                                                                                                                                                                                                                                                                                         | Acme, Inc.                                                                                                                                                                                                                                                      |\n| `com.docker.desktop.extension.api.version`  | Yes      | Version of the Docker Extension manager that the extension is compatible with. It must follow [semantic versioning](https://semver.org/).                                                                                                                                                                                                                                                                                                                                                             | A specific version like `0.1.0` or, a constraint expression: `>= 0.1.0`, `>= 1.4.7, < 2.0` . For your first extension, you can use `docker extension version` to know the SDK API version and specify `>= <SDK_API_VERSION>`.                                   |\n| `com.docker.desktop.extension.icon`         | Yes      | The extension icon (format: .svg .png .jpg)                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com/assets/image.svg`                                                                                                                                                                                                                          |\n| `com.docker.extension.screenshots`          | Yes      | A JSON array of image URLs and an alternative text displayed to users (in the order they appear in your metadata) in your extension's details page. **Note:** The recommended size for screenshots is 2400x1600 pixels.                                                                                                                                                                                                                                                                               | `[{\"alt\":\"alternative text for image 1\",` `\"url\":\"https://example.com/image1.png\"},` `{\"alt\":\"alternative text for image2\",` `\"url\":\"https://example.com/image2.jpg\"}]`                                                                                         |\n| `com.docker.extension.detailed-description` | Yes      | Additional information in plain text or HTML about the extension to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                    | `My detailed description` or `<h1>My detailed description</h1>`                                                                                                                                                                                                 |\n| `com.docker.extension.publisher-url`        | Yes      | The publisher website URL to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com`                                                                                                                                                                                                                                           |\n| `com.docker.extension.additional-urls`      | No       | A JSON array of titles and additional URLs displayed to users (in the order they appear in your metadata) in your extension's details page. Docker recommends you display the following links if they apply: documentation, support, terms of service, and privacy policy links.                                                                                                                                                                                                                      | `[{\"title\":\"Documentation\",\"url\":\"https://example.com/docs\"},` `{\"title\":\"Support\",\"url\":\"https://example.com/bar/support\"},` `{\"title\":\"Terms of Service\",\"url\":\"https://example.com/tos\"},` `{\"title\":\"Privacy policy\",\"url\":\"https://example.com/privacy\"}]` |\n| `com.docker.extension.changelog`            | Yes      | Changelog in plain text or HTML containing the change for the current version only.                                                                                                                                                                                                                                                                                                                                                                                                                   | `Extension changelog` or `<p>Extension changelog<ul>` `<li>New feature A</li>` `<li>Bug fix on feature B</li></ul></p>`                                                                                                                                         |\n| `com.docker.extension.account-info`         | No       | Whether the user needs to register to a SaaS platform to use some features of the extension.                                                                                                                                                                                                                                                                                                                                                                                                          | `required` in case it does, leave it empty otherwise.                                                                                                                                                                                                           |\n| `com.docker.extension.categories`           | No       | The list of Marketplace categories that your extension belongs to: `ci-cd`, `container-orchestration`, `cloud-deployment`, `cloud-development`, `database`, `kubernetes`, `networking`, `image-registry`, `security`, `testing-tools`, `utility-tools`,`volumes`. If you don't specify this label, users won't be able to find your extension in the Extensions Marketplace when filtering by a category. Extensions published to the Marketplace before the 22nd of September 2022 have been auto-categorized by Docker. | Specified as comma separated values in case of having multiple categories e.g: `kubernetes,security` or a single value e.g. `kubernetes`.                                                                                                   |\n\n> [!TIP]\n>\n> Docker Desktop applies CSS styles to the provided HTML content. You can make sure that it renders correctly \n> [within the Marketplace](#preview-the-extension-in-the-marketplace). It is recommended that you follow the \n> [styling guidelines](../design/_index.md).\n\n## Preview the extension in the Marketplace\n\nYou can validate that the image labels render as you expect.\n\nWhen you create and install your unpublished extension, you can preview the extension in the Marketplace's **Managed** tab. You can see how the extension labels render in the list and in the details page of the extension.\n\n> Preview extensions already listed in Marketplace\n>\n> When you install a local image of an extension already published in the Marketplace, for example with the tag `latest`, your local image is not detected as \"unpublished\".\n>\n> You can re-tag your image in order to have a different image name that's not listed as a published extension.\n> Use `docker tag org/published-extension unpublished-extension` and then `docker extension install unpublished-extension`.\n\n![List preview](images/list-preview.png)\n","content/manuals/extensions/extensions-sdk/extensions/multi-arch.md":"---\ntitle: Build multi-arch extensions\ndescription: Step three in creating an extension.\nkeywords: Docker, Extensions, sdk, build, multi-arch\naliases: \n - /desktop/extensions-sdk/extensions/multi-arch/\n---\n\nIt is highly recommended that, at a minimum, your extension is supported for the following architectures:\n\n- `linux/amd64`\n- `linux/arm64`\n\nDocker Desktop retrieves the extension image according to the user’s system architecture. If the extension does not provide an image that matches the user’s system architecture, Docker Desktop is not able to install the extension. As a result, users can’t run the extension in Docker Desktop.\n\n## Build and push for multiple architectures\n\nIf you created an extension from the `docker extension init` command, the\n`Makefile` at the root of the directory includes a target with name\n`push-extension`.\n\nYou can run `make push-extension` to build your extension against both\n`linux/amd64` and `linux/arm64` platforms, and push them to Docker Hub.\n\nFor example:\n\n```console\n$ make push-extension\n```\n\nAlternatively, if you started from an empty directory, use the command below\nto build your extension for multiple architectures:\n\n```console\n$ docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=username/my-extension:0.0.1 .\n```\n\nYou can then check the image manifest to see if the image is available for both\narchitectures using the [`docker buildx imagetools` command](/reference/cli/docker/buildx/imagetools/):\n\n```console\n$ docker buildx imagetools inspect username/my-extension:0.0.1\nName:      docker.io/username/my-extension:0.0.1\nMediaType: application/vnd.docker.distribution.manifest.list.v2+json\nDigest:    sha256:f3b552e65508d9203b46db507bb121f1b644e53a22f851185d8e53d873417c48\n\nManifests:\n  Name:      docker.io/username/my-extension:0.0.1@sha256:71d7ecf3cd12d9a99e73ef448bf63ae12751fe3a436a007cb0969f0dc4184c8c\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/amd64\n\n  Name:      docker.io/username/my-extension:0.0.1@sha256:5ba4ceea65579fdd1181dfa103cc437d8e19d87239683cf5040e633211387ccf\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/arm64\n```\n\n> [!TIP]\n>\n> If you're having trouble pushing the image, make sure you're signed in to Docker Hub. Otherwise, run `docker login` to authenticate.\n\nFor more information, see [Multi-platform images](/manuals/build/building/multi-platform.md) page.\n\n## Adding multi-arch binaries\n\nIf your extension includes some binaries that deploy to the host, it’s important that they also have the right architecture when building the extension against multiple architectures.\n\nCurrently, Docker does not provide a way to explicitly specify multiple binaries for every architecture in the `metadata.json` file. However, you can add architecture-specific binaries depending on the `TARGETARCH` in the extension’s `Dockerfile`.\n\nThe following example shows an extension that uses a binary as part of its operations. The extension needs to run both in Docker Desktop for Mac and Windows.\n\nIn the `Dockerfile`, download the binary depending on the target architecture:\n\n```Dockerfile\n#syntax=docker/dockerfile:1.3-labs\n\nFROM alpine AS dl\nWORKDIR /tmp\nRUN apk add --no-cache curl tar\nARG TARGETARCH\nRUN <<EOT ash\n    mkdir -p /out/darwin\n    curl -fSsLo /out/darwin/kubectl \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/darwin/${TARGETARCH}/kubectl\"\n    chmod a+x /out/darwin/kubectl\nEOT\nRUN <<EOT ash\n    if [ \"amd64\" = \"$TARGETARCH\" ]; then\n        mkdir -p /out/windows\n        curl -fSsLo /out/windows/kubectl.exe \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe\"\n    fi\nEOT\n\nFROM alpine\nLABEL org.opencontainers.image.title=\"example-extension\" \\\n    org.opencontainers.image.description=\"My Example Extension\" \\\n    org.opencontainers.image.vendor=\"Docker Inc.\" \\\n    com.docker.desktop.extension.api.version=\">= 0.3.3\"\n\nCOPY --from=dl /out /\n```\n\nIn the `metadata.json` file, specify the path for every binary on every platform:\n\n```json\n{\n  \"icon\": \"docker.svg\",\n  \"ui\": {\n    \"dashboard-tab\": {\n      \"title\": \"Example Extension\",\n      \"src\": \"index.html\",\n      \"root\": \"ui\"\n    }\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/kubectl\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/kubectl.exe\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nAs a result, when `TARGETARCH` equals:\n\n- `arm64`, the `kubectl` binary fetched corresponds to the `arm64` architecture, and is copied to `/darwin/kubectl` in the final stage.\n- `amd64`, two `kubectl` binaries are fetched. One for Darwin and another for Windows. They are copied to `/darwin/kubectl` and `/windows/kubectl.exe` respectively, in the final stage.\n\n> [!NOTE]\n>\n> The binary destination path for Darwin is `darwin/kubectl` in both cases. The only change is the architecture-specific binary that is downloaded.\n\nWhen the extension is installed, the extension framework copies the binaries from the extension image at `/darwin/kubectl` for Darwin, or `/windows/kubectl.exe` for Windows, to a specific location in the user’s host filesystem.\n\n## Can I develop extensions that run Windows containers?\n\nAlthough Docker Extensions is supported on Docker Desktop for Windows, Mac, and Linux, the extension framework only supports Linux containers. Therefore, you must target `linux` as the OS when you build your extension image.\n","content/manuals/extensions/extensions-sdk/extensions/publish.md":"---\ntitle: Publish in the Marketplace\ndescription: Docker extension distribution\nkeywords: Docker, extensions, publish\naliases: \n - /desktop/extensions-sdk/extensions/publish/\nweight: 50\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\n## Submit your extension to the Marketplace\n\nDocker Desktop displays published extensions in the Extensions Marketplace on [Docker Desktop](https://open.docker.com/extensions/marketplace) and [Docker Hub](https://hub.docker.com/search?q=&type=extension).\nThe Extensions Marketplace is a space where developers can discover extensions to improve their developer experience and propose their own extension to be available for all Desktop users.\n\nWhenever you are [ready to publish](DISTRIBUTION.md) your extension in the Marketplace, you can [self-publish your extension](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n> [!NOTE]\n>\n> As the Extension Marketplace continues to add new features for both Extension users and publishers, you are expected\n> to maintain your extension over time to ensure it stays available in the Marketplace.\n\n> [!IMPORTANT]\n>\n> The Docker manual review process for extensions is paused at the moment. Submit your extension through the [automated submission process](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n### Before you submit\n\nBefore you submit your extension, it must pass the [validation](validate.md) checks.\n\nIt is highly recommended that your extension follows the guidelines outlined in this section before submitting your\nextension. If you request a review from the Docker Extensions team and have not followed the guidelines, the review process may take longer. \n\nThese guidelines don't replace Docker's terms of service or guarantee approval:\n- Review the [design guidelines](../design/design-guidelines.md)\n- Ensure the [UI styling](../design/_index.md) is in line with Docker Desktop guidelines\n- Ensure your extensions support both light and dark mode\n- Consider the needs of both new and existing users of your extension\n- Test your extension with potential users\n- Test your extension for crashes, bugs, and performance issues\n- Test your extension on various platforms (Mac, Windows, Linux)\n- Read the [Terms of Service](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)\n\n#### Validation process\n\nSubmitted extensions go through an automated validation process. If all the validation checks pass successfully, the extension is\npublished on the Marketplace and accessible to all users within a few hours.\nIt is the fastest way to get developers the tools they need and to get feedback from them as you work to\nevolve/polish your extension.\n\n> [!IMPORTANT]\n>\n> Docker Desktop caches the list of extensions available in the Marketplace for 12 hours. If you don't see your\n> extension in the Marketplace, you can restart Docker Desktop to force the cache to refresh.\n","content/manuals/extensions/extensions-sdk/extensions/share.md":"---\ntitle: Share your extension\ndescription: Share your extension with a share link\nkeywords: Docker, extensions, share\naliases: \n - /desktop/extensions-sdk/extensions/share/\nweight: 40\n---\n\nOnce your extension image is accessible on Docker Hub, anyone with access to the image can install the extension.\n\nPeople can install your extension by typing `docker extension install my/awesome-extension:latest` in to the terminal.\n\nHowever, this option doesn't provide a preview of the extension before it's installed.\n\n## Create a share URL\n\nDocker lets you share your extensions using a URL.\n\nWhen people navigate to this URL, it opens Docker Desktop and displays a preview of your extension in the same way as an extension in the Marketplace. From the preview, users can then select **Install**.\n\n![Navigate to extension link](images/open-share.png)\n\nTo generate this link you can either:\n\n- Run the following command:\n\n  ```console\n  $ docker extension share my/awesome-extension:0.0.1\n  ```\n\n- Once you have installed your extension locally, navigate to the **Manage** tab and select **Share**.\n\n  ![Share button](images/list-preview.png)\n\n> [!NOTE]\n>\n> Previews of the extension description or screenshots, for example, are created using [extension labels](labels.md).\n","content/manuals/extensions/extensions-sdk/extensions/validate.md":"---\ntitle: Validate your extension\nlinkTitle: Validate\ndescription: Step three in the extension creation process\nkeywords: Docker, Extensions, sdk, validate, install\naliases:\n - /desktop/extensions-sdk/extensions/validation/\n - /desktop/extensions-sdk/build/build-install/\n - /desktop/extensions-sdk/dev/cli/build-test-install-extension/\n - /desktop/extensions-sdk/extensions/validate/\nweight: 20\n---\n\nValidate your extension before you share or publish it. Validating the extension ensures that the extension:\n\n- Is built with the [image labels](labels.md) it requires to display correctly in the marketplace\n- Installs and runs without problems\n\nThe Extensions CLI lets you validate your extension before installing and running it locally.\n\nThe validation checks if the extension’s `Dockerfile` specifies all the required labels and if the metadata file is valid against the JSON schema file.\n\nTo validate, run:\n\n```console\n$ docker extension validate <name-of-your-extension>\n```\n\nIf your extension is valid, the following message displays:\n\n```console\nThe extension image \"name-of-your-extension\" is valid\n```\n\nBefore the image is built, it's also possible to validate only the `metadata.json` file:\n\n```console\n$ docker extension validate /path/to/metadata.json\n```\n\nThe JSON schema used to validate the `metadata.json` file against can be found under the [releases page](https://github.com/docker/extensions-sdk/releases/latest).\n","content/manuals/extensions/extensions-sdk/guides/_index.md":"---\nbuild:\n  render: never\ntitle: Developer Guides\n---\n","content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md":"---\ntitle: Invoke host binaries\ndescription: Add invocations to host binaries from the frontend with the extension\n  SDK.\nkeywords: Docker, extensions, sdk, build\naliases:\n - /desktop/extensions-sdk/guides/invoke-host-binaries/\n---\n\nIn some cases, your extension may need to invoke some command from the host. For example, you\nmight want to invoke the CLI of your cloud provider to create a new resource, or the CLI of a tool your extension\nprovides, or even a shell script that you want to run on the host. \n\nYou could do that by executing the CLI from a container with the extension SDK. But this CLI needs to access the host's filesystem, which isn't easy nor fast if it runs in a container.\n\nThis page describes how to run executables on the host (binaries, shell scripts) that are shipped as part of your extension and deployed to the host. As extensions can run on multiple platforms, this\nmeans that you need to ship the executables for all the platforms you want to support.\n\nLearn more about extensions [architecture](../architecture/_index.md).\n\n> [!NOTE]\n>\n>  Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nIn this example, the CLI is a simple `Hello world` script that must be invoked with a parameter and returns a \nstring.\n\n## Add the executables to the extension\n\n{{< tabs >}}\n{{< tab name=\"Mac and Linux\" >}}\n\nCreate a `bash` script for macOS and Linux, in the file `binaries/unix/hello.sh` with the following content:\n\n```bash\n#!/bin/sh\necho \"Hello, $1!\"\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\nCreate a `batch script` for Windows in another file `binaries/windows/hello.cmd` with the following content:\n\n```bash\n@echo off\necho \"Hello, %1!\"\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThen update the `Dockerfile` to copy the `binaries` folder into the extension's container filesystem and make the\nfiles executable.\n\n```dockerfile\n# Copy the binaries into the right folder\nCOPY --chmod=0755 binaries/windows/hello.cmd /windows/hello.cmd\nCOPY --chmod=0755 binaries/unix/hello.sh /linux/hello.sh\nCOPY --chmod=0755 binaries/unix/hello.sh /darwin/hello.sh\n```\n\n## Invoke the executable from the UI\n\nIn your extension, use the Docker Desktop Client object to [invoke the shell script](../dev/api/backend.md#invoke-an-extension-binary-on-the-host)\nprovided by the extension with the `ddClient.extension.host.cli.exec()` function.\nIn this example, the binary returns a string as result, obtained by `result?.stdout`, as soon as the extension view is rendered.\n\n{{< tabs group=\"framework\" >}}\n{{< tab name=\"React\" >}}\n\n```typescript\nexport function App() {\n  const ddClient = createDockerDesktopClient();\n  const [hello, setHello] = useState(\"\");\n\n  useEffect(() => {\n    const run = async () => {\n      let binary = \"hello.sh\";\n      if (ddClient.host.platform === 'win32') {\n        binary = \"hello.cmd\";\n      }\n\n      const result = await ddClient.extension.host?.cli.exec(binary, [\"world\"]);\n      setHello(result?.stdout);\n\n    };\n    run();\n  }, [ddClient]);\n    \n  return (\n    <div>\n      {hello}\n    </div>\n  );\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Vue\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Vue yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Vue)\n> and let us know if you'd like a sample with Vue.\n\n{{< /tab >}}\n{{< tab name=\"Angular\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Angular yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Angular)\n> and let us know if you'd like a sample with Angular.\n\n{{< /tab >}}\n{{< tab name=\"Svelte\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Svelte yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Svelte)\n> and let us know if you'd like a sample with Svelte.\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Configure the metadata file\n\nThe host binaries must be specified in the `metadata.json` file so that Docker Desktop copies them on to the host when installing\nthe extension. Once the extension is uninstalled, the binaries that were copied are removed as well.\n\n```json\n{\n  \"vm\": {\n    ...\n  },\n  \"ui\": {\n    ...\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/hello.sh\"\n          }\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/hello.sh\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/hello.cmd\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nThe `path` must reference the path of the binary inside the container.\n","content/manuals/extensions/extensions-sdk/guides/kubernetes.md":"---\ntitle: Interacting with Kubernetes from an extension\nlinkTitle: Interacting with Kubernetes\ndescription: How to connect to a Kubernetes cluster from an extension\nkeywords: Docker, Extensions, sdk, Kubernetes\naliases:\n - /desktop/extensions-sdk/dev/kubernetes/\n - /desktop/extensions-sdk/guides/kubernetes/\n---\n\nThe Extensions SDK does not provide any API methods to directly interact with the Docker Desktop managed Kubernetes cluster or any other created using other tools such as KinD. However, this page provides a way for you to use other SDK APIs to interact indirectly with a Kubernetes cluster from your extension.\n\nTo request an API that directly interacts with Docker Desktop-managed Kubernetes, you can upvote [this issue](https://github.com/docker/extensions-sdk/issues/181) in the Extensions SDK GitHub repository.\n\n## Prerequisites\n\n### Turn on Kubernetes\n\nYou can use the built-in Kubernetes in Docker Desktop to start a Kubernetes single-node cluster.\nA `kubeconfig` file is used to configure access to Kubernetes when used in conjunction with the `kubectl` command-line tool, or other clients.\nDocker Desktop conveniently provides the user with a local preconfigured `kubeconfig` file and `kubectl` command within the user’s home area. It is a convenient way to fast-tracking access for those looking to leverage Kubernetes from Docker Desktop.\n\n## Ship the `kubectl` as part of the extension\n\nIf your extension needs to interact with Kubernetes clusters, it is recommended that you include the `kubectl` command line tool as part of your extension. By doing this, users who install your extension get `kubectl` installed on their host.\n\nTo find out how to ship the `kubectl` command line tool for multiple platforms as part of your Docker Extension image, see [Build multi-arch extensions](../extensions/multi-arch.md#adding-multi-arch-binaries).\n\n## Examples\n\nThe following code snippets have been put together in the [Kubernetes Sample Extension](https://github.com/docker/extensions-sdk/tree/main/samples/kubernetes-sample-extension). It shows how to interact with a Kubernetes cluster by shipping the `kubectl` command-line tool.\n\n### Check the Kubernetes API server is reachable\n\nOnce the `kubectl` command-line tool is added to the extension image in the `Dockerfile`, and defined in the `metadata.json`, the Extensions framework deploys `kubectl` to the users' host when the extension is installed.\n\nYou can use the JS API `ddClient.extension.host?.cli.exec` to issue `kubectl` commands to, for instance, check whether the Kubernetes API server is reachable given a specific context:\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"cluster-info\",\n  \"--request-timeout\",\n  \"2s\",\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n### List Kubernetes contexts\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"-o\",\n  \"jsonpath='{.contexts}'\",\n]);\n```\n\n### List Kubernetes namespaces\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"get\",\n  \"namespaces\",\n  \"--no-headers\",\n  \"-o\",\n  'custom-columns=\":metadata.name\"',\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n## Persisting the kubeconfig file\n\nBelow there are different ways to persist and read the `kubeconfig` file from the host filesystem. Users can add, edit, or remove Kubernetes context to the `kubeconfig` file at any time.\n\n> Warning\n>\n> The `kubeconfig` file is very sensitive and if found can give an attacker administrative access to the Kubernetes Cluster.\n\n### Extension's backend container\n\nIf you need your extension to persist the `kubeconfig` file after it's been read, you can have a backend container that exposes an HTTP POST endpoint to store the content of the file either in memory or somewhere within the container filesystem. This way, if the user navigates out of the extension to another part of Docker Desktop and then comes back, you don't need to read the `kubeconfig` file again.\n\n```typescript\nexport const updateKubeconfig = async () => {\n  const kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n    \"config\",\n    \"view\",\n    \"--raw\",\n    \"--minify\",\n    \"--context\",\n    \"docker-desktop\",\n  ]);\n  if (kubeConfig?.stderr) {\n    console.log(\"error\", kubeConfig?.stderr);\n    return false;\n  }\n\n  // call backend container to store the kubeconfig retrieved into the container's memory or filesystem\n  try {\n    await ddClient.extension.vm?.service?.post(\"/store-kube-config\", {\n      data: kubeConfig?.stdout,\n    });\n  } catch (err) {\n    console.log(\"error\", JSON.stringify(err));\n  }\n};\n```\n\n### Docker volume\n\nVolumes are the preferred mechanism for persisting data generated by and used by Docker containers. You can make use of them to persist the `kubeconfig` file.\nBy persisting the `kubeconfig` in a volume you won't need to read the `kubeconfig` file again when the extension pane closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nconst kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"--raw\",\n  \"--minify\",\n  \"--context\",\n  \"docker-desktop\",\n]);\nif (kubeConfig?.stderr) {\n  console.log(\"error\", kubeConfig?.stderr);\n  return false;\n}\n\nawait ddClient.docker.cli.exec(\"run\", [\n  \"--rm\",\n  \"-v\",\n  \"my-vol:/tmp\",\n  \"alpine\",\n  \"/bin/sh\",\n  \"-c\",\n  `\"touch /tmp/.kube/config && echo '${kubeConfig?.stdout}' > /tmp/.kube/config\"`,\n]);\n```\n\n### Extension's `localStorage`\n\n`localStorage` is one of the mechanisms of a browser's web storage. It allows users to save data as key-value pairs in the browser for later use.\n`localStorage` does not clear data when the browser (the extension pane) closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nlocalStorage.setItem(\"kubeconfig\", kubeConfig);\n```\n\n```typescript\nlocalStorage.getItem(\"kubeconfig\");\n```\n","content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md":"---\ntitle: Authentication\ndescription: Docker extension OAuth 2.0 flow\nkeywords: Docker, extensions, sdk, OAuth 2.0\naliases:\n - /desktop/extensions-sdk/dev/oauth2-flow/\n - /desktop/extensions-sdk/guides/oauth2-flow/\n---\n\n> [!NOTE]\n>\n> This page assumes that you already have an Identity Provider (IdP), such as Google, Entra ID (formerly Azure AD) or Okta, which handles the authentication process and returns an access token.\n\nLearn how you can let users authenticate from your extension using OAuth 2.0 via a web browser, and return to your extension.\n\nIn OAuth 2.0, the term \"grant type\" refers to the way an application gets an access token. Although OAuth 2.0 defines several grant types, this page only describes how to authorize users from your extension using the Authorization Code grant type.\n\n## Authorization code grant flow\n\nThe Authorization Code grant type is used by confidential and public clients to exchange an authorization code for an access token.\n\nAfter the user returns to the client via the redirect URL, the application gets the authorization code from the URL and uses it to request an access token.\n\n![Flow for OAuth 2.0](images/oauth.png)\n\nThe image above shows that:\n\n- The Docker extension asks the user to authorize access to their data.\n- If the user grants access, the extension then requests an access token from the service provider, passing the access grant from the user and authentication details to identify the client.\n- The service provider then validates these details and returns an access token.\n- The extension uses the access token to request the user data with the service provider.\n\n### OAuth 2.0 terminology\n\n- Auth URL: The endpoint for the API provider authorization server, to retrieve the auth code.\n- Redirect URI: The client application callback URL to redirect to after auth. This must be registered with the API provider.\n\nOnce the user enters the username and password, they're successfully authenticated.\n\n## Open a browser page to authenticate the user\n\nFrom the extension UI, you can provide a button that, when selected, opens a new window in a browser to authenticate the user.\n\nUse the [ddClient.host.openExternal](../dev/api/dashboard.md#open-a-url) API to open a browser to the auth URL. For\nexample:\n\n```typescript\nwindow.ddClient.openExternal(\"https://authorization-server.com/authorize?\n  response_type=code\n  &client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n  &redirect_uri=${REDIRECT_URI});\n```\n\n## Get the authorization code and access token\n\nYou can get the authorization code from the extension UI by listing `docker-desktop://dashboard/extension-tab?extensionId=awesome/my-extension` as the `redirect_uri` in the OAuth app you're using and concatenating the authorization code as a query parameter. The extension UI code will then be able to read the corresponding code query-param.\n\n> [!IMPORTANT]\n>\n> Using this feature requires the extension SDK 0.3.3 in Docker Desktop. You need to ensure that the required SDK version for your extension set with `com.docker.desktop.extension.api.version` in [image labels](../extensions/labels.md) is higher than 0.3.3.\n\n#### Authorization\n\nThis step is where the user enters their credentials in the browser. After the authorization is complete, the user is redirected back to your extension user interface, and the extension UI code can consume the authorization code that's part of the query parameters in the URL.\n\n#### Exchange the Authorization Code\n\nNext, you exchange the authorization code for an access token.\n\nThe extension must send a `POST` request to the 0Auth authorization server with the following parameters:\n\n```text\nPOST https://authorization-server.com/token\n&client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n&client_secret=YABbyHQShPeO1T3NDQZP8q5m3Jpb_UPNmIzqhLDCScSnRyVG\n&redirect_uri=${REDIRECT_URI}\n&code=N949tDLuf9ai_DaOKyuFBXStCNMQzuQbtC1QbvLv-AXqPJ_f\n```\n\n> [!NOTE]\n>\n> The client's credentials are included in the `POST` query params in this example. OAuth authorization servers may require that the credentials are sent as a HTTP Basic Authentication header or might support different formats. See your OAuth provider docs for details.\n\n### Store the access token\n\nThe Docker Extensions SDK doesn't provide a specific mechanism to store secrets.\n\nIt's highly recommended that you use an external source of storage to store the access token.\n\n> [!NOTE]\n>\n> The user interface Local Storage is isolated between extensions (an extension can't access another extension's local storage), and each extension's local storage gets deleted when users uninstall an extension.\n\n## What's next\n\nLearn how to [publish and distribute your extension](../extensions/_index.md)\n","content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md":"---\ntitle: Use the Docker socket from the extension backend\nlinkTitle: Use the Docker socket\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/guides/use-docker-socket-from-backend/\n---\n\nExtensions can invoke Docker commands directly from the frontend with the SDK. \n\nIn some cases, it is useful to also interact with Docker Engine from the backend. \n\nExtension backend containers can mount the Docker socket and use it to\ninteract with Docker Engine from the extension backend logic. Learn more about the [Docker Engine socket](/reference/cli/dockerd/#examples)\n\nHowever, when mounting the Docker socket from an extension container that lives in the Desktop virtual machine, you want\nto mount the Docker socket from inside the VM, and not mount `/var/run/docker.sock` from the host filesystem (using\nthe Docker socket from the host can lead to permission issues in containers).\n\nIn order to do so, you can use `/var/run/docker.sock.raw`. Docker Desktop mounts the socket that lives in the Desktop VM, and not from the host.\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /var/run/docker.sock.raw:/var/run/docker.sock\n```\n","content/manuals/extensions/extensions-sdk/process.md":"---\ndescription: Understand the process of creating an extension.\ntitle: The build and publish process\nkeyword: Docker Extensions, sdk, build, create, publish\naliases:\n - /desktop/extensions-sdk/process/\nweight: 10\n---\n\nThis documentation is structured so that it matches the steps you need to take when creating your extension. \n\nThere are two main parts to creating a Docker extension:\n\n1. Build the foundations\n2. Publish the extension\n\n> [!NOTE]\n>\n> You do not need to pay to create a Docker extension. The [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use. Anyone can create new extensions and share them without constraints.\n> \n> There is also no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n\n## Part one: Build the foundations\n\nThe build process consists of:\n\n- Installing the latest version of Docker Desktop.\n- Setting up the directory with files, including the extension’s source code and the required extension-specific files.\n- Creating the `Dockerfile` to build, publish, and run your extension in Docker Desktop.\n- Configuring the metadata file which is required at the root of the image filesystem.\n- Building and installing the extension.\n\nFor further inspiration, see the other examples in the [samples folder](https://github.com/docker/extensions-sdk/tree/main/samples).\n\n> [!TIP]\n>\n> Whilst creating your extension, make sure you follow the [design](design/design-guidelines.md) and [UI styling](design/_index.md) guidelines to ensure visual consistency and [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n## Part two: Publish and distribute your extension\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nDocker Desktop displays published extensions in the Extensions Marketplace. The Extensions Marketplace is a curated space where developers can discover extensions to improve their developer experience and upload their own extension to share with the world.\n\nIf you want your extension published in the Marketplace, read the [publish documentation](extensions/publish.md).\n\n{{% include \"extensions-form.md\" %}}\n\n## What’s next?\n\nIf you want to get up and running with creating a Docker Extension, see the [Quickstart guide](quickstart.md).\n\nAlternatively, get started with reading the \"Part one: Build\" section for more in-depth information about each step of the extension creation process.\n\nFor an in-depth tutorial of the entire build process, we recommend the following video walkthrough from DockerCon 2022.\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Yv7OG-EGJsg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n","content/manuals/extensions/extensions-sdk/quickstart.md":"---\ntitle: Quickstart\ndescription: Guide on how to build an extension quickly\nkeywords: quickstart, extensions\naliases:\n - desktop/extensions-sdk/tutorials/initialize/\n - /desktop/extensions-sdk/quickstart/\nweight: 20\n---\n\nFollow this guide to get started with creating a basic Docker extension. The Quickstart guide automatically generates boilerplate files for you.\n\n## Prerequisites\n\n- [Docker Desktop](/manuals/desktop/release-notes.md)\n- [NodeJS](https://nodejs.org/)\n- [Go](https://go.dev/dl/)\n\n> [!NOTE]\n>\n> NodeJS and Go are only required when you follow the quickstart guide to create an extension. It uses the `docker extension init` command to automatically generate boilerplate files. This command uses a template based on a ReactJS and Go application.\n\nIn Docker Desktop settings, ensure you can install the extension you're developing. You may need to navigate to the **Extensions** tab in Docker Desktop settings and deselect **Allow only extensions distributed through the Docker Marketplace**.\n\n## Step one: Set up your directory\n\nTo set up your directory, use the `init` subcommand and provide a name for your extension.\n\n```console\n$ docker extension init <my-extension>\n```\n\nThe command asks a series of questions about your extension, such as its name, a description, and the name of your Hub repository. This helps the CLI generate a set of boilerplate files for you to get started. It stores the boilerplate files in the `my-extension` directory.\n\nThe automatically generated extension contains:\n\n- A Go backend service in the `backend` folder that listens on a socket. It has one endpoint `/hello` that returns a JSON payload.\n- A React frontend in the `frontend` folder that can call the backend and output the backend’s response.\n\nFor more information and guidelines on building the UI, see the [Design and UI styling section](design/design-guidelines.md).\n\n## Step two: Build the extension\n\nTo build the extension, move into the newly created directory and run:\n\n```console\n$ docker build -t <name-of-your-extension> .\n```\n\n`docker build` builds the extension and generates an image named the same as the chosen hub repository. For example, if you typed `john/my-extension` as the answer to the following question:\n\n```console\n? Hub repository (eg. namespace/repository on hub): john/my-extension`\n```\n\nThe `docker build` generates an image with name `john/my-extension`.\n\n## Step three: Install and preview the extension\n\nTo install the extension in Docker Desktop, run:\n\n```console\n$ docker extension install <name-of-your-extension>\n```\n\nTo preview the extension in Docker Desktop, once the installation is complete and you should\nsee a **Quickstart** item underneath the **Extensions** menu. Selecting this item opens the extension's frontend.\n\n> [!TIP]\n>\n> During UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\n> extension. See [Preview whilst developing the UI](dev/test-debug.md#hot-reloading-whilst-developing-the-ui) for more information.\n\nYou may also want to inspect the containers that belong to the extension. By default, extension containers are\nhidden from the Docker Dashboard. You can change this in **Settings**, see\n[how to show extension containers](dev/test-debug.md#show-the-extension-containers) for more information.\n\n## Step four: Submit and publish your extension to the Marketplace\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nIf you want to make your extension available to all Docker Desktop users, you can submit it for publication in the Marketplace. For more information, see [Publish](extensions/_index.md).\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build a more [advanced frontend](build/frontend-extension-tutorial.md) for your extension.\n- Learn how to [test and debug](dev/test-debug.md) your extension.\n- Learn how to [setup CI for your extension](dev/continuous-integration.md).\n- Learn more about extensions [architecture](architecture/_index.md).\n- Learn more about [designing the UI](design/design-guidelines.md).\n","content/manuals/extensions/marketplace.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace\ntitle: Marketplace extensions\nweight: 10\naliases:\n - /desktop/extensions/marketplace/\n---\n\nThere are two types of extensions available in the Extensions Marketplace:\n- Docker-reviewed extensions\n- Self-published extensions\n\nDocker-reviewed extensions are manually reviewed by the Docker Extensions team to ensure an extra level of trust\nand quality. They appear as **Reviewed** in the Marketplace.\n\nSelf-published extensions are autonomously published by extension developers and go through an automated validation process. They appear as **Not reviewed** in the Marketplace.\n\n> [!IMPORTANT]\n>\n> Marketplace extensions are reviewed by Docker, but are not subject to a full security audit. Extensions run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Only install extensions from publishers you trust.\n\n## Install an extension\n\n> [!NOTE]\n>\n> For some extensions, a separate account needs to be created before use.\n\nTo install an extension:\n\n1. Open Docker Desktop.\n2. From the Docker Desktop Dashboard, select the **Extensions** tab.\n   The Extensions Marketplace opens on the **Browse** tab.\n3. Browse the available extensions.\n   You can sort the list of extensions by **Recently added**, **Most installed**, or alphabetically. Alternatively, use the **Content** or **Categories** drop-down menu to search for extensions by whether they have been reviewed or not, or by category.\n4. Choose an extension and select **Install**.\n\nFrom here, you can select **Open** to access the extension or install additional extensions. The extension also appears in the left-hand menu and in the **Manage** tab.\n\n## Update an extension\n\nYou can update any extension outside of Docker Desktop releases. To update an extension to the latest version, navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n\nThe **Manage** tab displays with all your installed extensions. If an extension has a new version available, it displays an **Update** button.\n\n\n## Uninstall an extension\n\nYou can uninstall an extension at any time.\n\n> [!NOTE]\n>\n> Any data used by the extension that's stored in a volume must be manually deleted.\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the ellipsis to the right of extension you want to uninstall.\n3. Select **Uninstall**.\n","content/manuals/extensions/non-marketplace.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows,\ntitle: Non-marketplace extensions\nweight: 20\n---\n\n## Install an extension not available in the Marketplace\n\n> [!WARNING]\n>\n> Extensions installed outside the Marketplace have not gone through Docker's review process. Like all Docker extensions, they run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Install only if you trust the publisher and have verified the source.\n\nThe Extensions Marketplace is the trusted and official place to install extensions from within Docker Desktop. These extensions have gone through a review process by Docker. However, other extensions can also be installed in Docker Desktop if you trust the extension author.\n\nGiven the nature of a Docker Extension (i.e. a Docker image) you can find other places where users have their extension's source code published. For example on GitHub, GitLab or even hosted in image registries like DockerHub or GHCR.\nYou can install an extension that has been developed by the community or internally at your company from a teammate. You are not limited to installing extensions just from the Marketplace.\n\n> [!NOTE]\n>\n> Ensure the option **Allow only extensions distributed through the Docker Marketplace** is disabled. Otherwise, this prevents any extension not listed in the Marketplace, via the Extension SDK tools from, being installed.\n> You can change this option in **Settings**. \n\nTo install an extension which is not present in the Marketplace, you can use the Extensions CLI that is bundled with Docker Desktop.\n\nIn a terminal, type `docker extension install IMAGE[:TAG]` to install an extension by its image reference and optionally a tag. Use the `-f` or `--force` flag to avoid interactive confirmation.\n\nGo to the Docker Desktop Dashboard to see the new extension installed.\n\n## List installed extensions\n\nRegardless whether the extension was installed from the Marketplace or manually by using the Extensions CLI, you can use the `docker extension ls` command to display the list of extensions installed.\nAs part of the output you'll see the extension ID, the provider, version, the title and whether it runs a backend container or has deployed binaries to the host, for example:\n\n```console\n$ docker extension ls\nID                  PROVIDER            VERSION             UI                    VM                  HOST\njohn/my-extension   John                latest              1 tab(My-Extension)   Running(1)          -\n```\n\nGo to the Docker Desktop Dashboard, select **Add Extensions** and on the **Managed** tab to see the new extension installed.\nNotice that an `UNPUBLISHED` label displays which indicates that the extension has not been installed from the Marketplace.\n\n## Update an extension \n\nTo update an extension which isn't present in the Marketplace, in a terminal type `docker extension update IMAGE[:TAG]` where the `TAG` should be different from the extension that's already installed.\n\nFor instance, if you installed an extension with `docker extension install john/my-extension:0.0.1`, you can update it by running `docker extension update john/my-extension:0.0.2`.\nGo to the Docker Desktop Dashboard to see the new extension updated.\n\n> [!NOTE]\n>\n> Extensions that aren't installed through the Marketplace don't receive update notifications from Docker Desktop.\n\n## Uninstall an extension\n\nTo uninstall an extension which is not present in the Marketplace, you can either navigate to the **Managed** tab in the Marketplace and select the **Uninstall** button, or from a terminal type `docker extension uninstall IMAGE[:TAG]`.\n","content/manuals/extensions/private-marketplace.md":"---\ndescription: How to configure and use Docker Extensions' private marketplace\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace, private, security, admin\ntitle: Configure a private marketplace for extensions\ntags: [admin]\nlinkTitle: Configure a private marketplace\nweight: 30\n---\n\n{{< summary-bar feature_name=\"Private marketplace\" >}}\n\nLearn how to configure and set up a private marketplace with a curated list of extensions for your Docker Desktop users.\n\nDocker Extensions' private marketplace is designed specifically for organizations who don’t give developers root access to their machines. It makes use of [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) so administrators have complete control over the private marketplace.\n\n## Prerequisites\n\n- [Download and install Docker Desktop](https://docs.docker.com/desktop/release-notes/).\n- You must be an administrator for your organization.\n- You have the ability to push the `extension-marketplace` folder and `admin-settings.json` file to the locations specified below through device management software such as [Jamf](https://www.jamf.com/).\n\n## Step one: Initialize the private marketplace\n\n1. Create a folder locally for the content that will be deployed to your developers’ machines:\n\n   ```console\n   $ mkdir my-marketplace\n   $ cd my-marketplace\n   ```\n\n2. Initialize the configuration files for your marketplace:\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ /Applications/Docker.app/Contents/Resources/bin/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin init\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ /opt/docker-desktop/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\nThis creates 2 files:\n\n- `admin-settings.json`, which activates the private marketplace feature once it’s applied to Docker Desktop on your developers’ machines.\n- `extensions.txt`, which determines which extensions to list in your private marketplace.\n\n> [!IMPORTANT]\n>\n> If your org is using [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will not need the `admin-settings.json` file. Delete the generated file and keep only the `extensions.txt` file.\n\n## Step two: Set the behaviour\n\nThe generated `admin-settings.json` file includes various settings you can modify.\n\n> [!IMPORTANT]\n>\n> If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will define the same settings in Docker Home instead of the `admin-settings.json` file.\n\nEach setting has a `value` that you can set, including a `locked` field that lets you lock the setting and make it unchangeable by your developers.\n\n- `extensionsEnabled` enables Docker Extensions.\n- `extensionsPrivateMarketplace` activates the private marketplace and ensures Docker Desktop connects to content defined and controlled by the administrator instead of the public Docker marketplace.\n- `onlyMarketplaceExtensions` allows or blocks developers from installing other extensions by using the command line. Teams developing new extensions must have this setting unlocked (`\"locked\": false`) to install and test extensions being developed.\n- `extensionsPrivateMarketplaceAdminContactURL` defines a contact link for developers to request new extensions in the private marketplace. If `value` is empty then no link is shown to your developers on Docker Desktop, otherwise this can be either an HTTP link or a “mailto:” link. For example,\n\n  ```json\n  \"extensionsPrivateMarketplaceAdminContactURL\": {\n    \"locked\": true,\n    \"value\": \"mailto:admin@acme.com\"\n  }\n  ```\n\nTo find out more information about the `admin-settings.json` file, see [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md).\n\n## Step three: List allowed extensions\n\nThe generated `extensions.txt` file defines the list of extensions that are available in your private marketplace.\n\nEach line in the file is an allowed extension and follows the format of `org/repo:tag`.\n\nFor example, if you want to permit the Disk Usage extension you would enter the following into your `extensions.txt` file:\n\n```console\ndocker/disk-usage-extension:0.2.8\n```\n\nIf no tag is provided, the latest tag available for the image is used. You can also comment out lines with `#` so the extension is ignored.\n\nThis list can include different types of extension images:\n\n- Extensions from the public marketplace or any public image stored in Docker Hub.\n- Extension images stored in Docker Hub as private images. Developers need to be signed in and have pull access to these images.\n- Extension images stored in a private registry. Developers need to be signed in and have pull access to these images.\n\n> [!IMPORTANT]\n>\n> Your developers can only install the version of the extension that you’ve listed.\n\n## Step four: Generate the private marketplace\n\nOnce the list in `extensions.txt` is ready, you can generate the marketplace:\n\n{{< tabs group=\"os_version\" >}}\n{{< tab name=\"Mac\" >}}\n\n```console\n$ /Applications/Docker.app/Contents/Resources/bin/extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\n```console\n# For all-user installations\n$ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin generate\n\n# For per-user installations\n$ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Linux\" >}}\n\n```console\n$ /opt/docker-desktop/extension-admin generate\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThis creates an `extension-marketplace` directory and downloads the marketplace metadata for all the allowed extensions.\n\nThe marketplace content is generated from extension image information as image labels, which is the [same format as public extensions](extensions-sdk/extensions/labels.md). It includes the extension title, description, screenshots, links, etc.\n\n## Step five: Test the private marketplace setup\n\nIt's recommended that you try the private marketplace on your Docker Desktop installation.\n\n1. Run the following command in your terminal. This command automatically copies the generated files to the location where Docker Desktop reads the configuration files. Depending on your operating system, the location is:\n\n    - Mac: `/Library/Application\\ Support/com.docker.docker`\n    - Windows: `C:\\ProgramData\\DockerDesktop`\n    - Linux: `/usr/share/docker-desktop`\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ sudo /Applications/Docker.app/Contents/Resources/bin/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows (run as admin)\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin apply\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ sudo /opt/docker-desktop/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\n2. Quit and re-open Docker Desktop. \n3. Sign in with a Docker account.\n\n> [!IMPORTANT]\n>\n> > If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), in Docker Desktop 4.59 and earlier, you must manually delete the `admin-settings.json` file created in the target folder by the `apply` command before step 2. In Docker Desktop 4.60 and later, this step is no longer necessary. \n\nWhen you select the **Extensions** tab, you should see the private marketplace listing only the extensions you have allowed in `extensions.txt`.\n\n![Extensions Private Marketplace](/assets/images/extensions-private-marketplace.webp)\n\n## Step six: Distribute the private marketplace\n\nOnce you’ve confirmed that the private marketplace configuration works, the final step is to distribute the files to the developers’ machines with the MDM software your organization uses. For example, [Jamf](https://www.jamf.com/).\n\nThe files to distribute are:\n* `admin-settings.json` (except if your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md))\n* the entire `extension-marketplace` folder and its subfolders\n\nThese files must be placed on developer's machines. Depending on your operating system, the target location is (as mentioned above):\n\n- Mac: `/Library/Application\\ Support/com.docker.docker`\n- Windows: `C:\\ProgramData\\DockerDesktop`\n- Linux: `/usr/share/docker-desktop`\n\nMake sure your developers are signed in to Docker Desktop in order for the private marketplace configuration to take effect. As an administrator, you should [enforce sign-in](/manuals/enterprise/security/enforce-sign-in/_index.md).\n\n## Feedback\n\nGive feedback or report any bugs you may find by emailing `extensions@docker.com`.\n","content/manuals/extensions/settings-feedback.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, feedback\ntitle: Settings and feedback for Docker Extensions\nlinkTitle: Settings and feedback\nweight: 40\n---\n\n## Settings\n\n### Turn on or turn off extensions\n\nDocker Extensions is switched off by default. To change your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Enable Docker Extensions**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> If you are an [organization owner](/manuals/admin/organization/manage/manage-a-team.md#what-is-an-organization-owner), you can turn off extensions for your users. Open the `settings-store.json` file, and set `\"extensionsEnabled\"` to `false`.\n> The `settings-store.json` file is located at:\n>   - `~/Library/Group Containers/group.com.docker/settings-store.json` on Mac\n>   - `C:\\Users\\[USERNAME]\\AppData\\Roaming\\Docker\\settings-store.json` on Windows\n>\n> This can also be done with [Hardened Docker Desktop](/manuals/enterprise/security/hardened-desktop/_index.md)\n\n### Turn on or turn off extensions not available in the Marketplace\n\nYou can install extensions through the Marketplace or through the Extensions SDK tools. You can choose to only allow published extensions. These are extensions that have been reviewed and published in the Extensions Marketplace.\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Allow only extensions distributed through the Docker Marketplace**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n### See containers created by extensions\n\nBy default, containers created by extensions are hidden from the list of containers in the Docker Desktop Dashboard and the Docker CLI. To make them visible\nupdate your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Show Docker Extensions system containers**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> Enabling extensions doesn't use computer resources (CPU / Memory) by itself.\n>\n> Specific extensions might use computer resources, depending on the features and implementation of each extension, but there is no reserved resources or usage cost associated with enabling extensions.\n\n## Submit feedback\n\nFeedback can be given to an extension author through a dedicated Slack channel or GitHub. To submit feedback about a particular extension:\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the extension you want to provide feedback on. \n3. Scroll down to the bottom of the extension's description and, depending on the \nextension, select:\n    - Support\n    - Slack\n    - Issues. You'll be sent to a page outside of Docker Desktop to submit your feedback.\n\nIf an extension doesn't provide a way for you to give feedback, contact us and we'll pass on the feedback for you. To provide feedback, select the **Give feedback** to the right of **Extensions Marketplace**.\n"},"files":{".agents/skills/agent-readiness-audit/SKILL.md":"---\nname: agent-readiness-audit\ndescription: >\n  Audit a documentation site for agent-friendliness: discovery, markdown\n  delivery, crawlability, semantic structure, machine-readable surfaces,\n  and content legibility. Use when asked to assess docs.docker.com or any\n  docs site for AI/agent readiness, produce a scored report, compare with\n  external scanners, or generate a remediation list. Triggers on:\n  \"audit docs for agent readiness\", \"how agent-friendly is docs.docker.com\",\n  \"score our docs for AI agents\", \"review llms.txt / markdown / crawlability\",\n  \"create an agent-readiness remediation plan\".\nargument-hint: \"<base-url>\"\n---\n\n# Agent Readiness Audit\n\nAudit the live site, not the source tree alone. Prefer the same fetch path\nan external agent would use in the wild: direct HTTP requests, sitemap\nsampling, and page-level inspection.\n\nDo not reduce the result to a homepage-only scan or a binary checklist.\n\n## 1. Set scope\n\nUse `$ARGUMENTS` as the base URL when provided. Otherwise infer the base\nURL from context and state the assumption.\n\nDecide whether the host being audited is:\n\n- a docs-only host\n- an app/tool host\n- a mixed host\n\nThis matters for optional checks such as MCP, plugin manifests, or other\ntool discovery files. Do not penalize a docs-only host for missing\ntooling manifests that belong on a separate service.\n\nFor `docs.docker.com`, treat the public docs host as docs-only. Docker's\nMCP server is published separately, so missing MCP files on the docs host\nshould be reported as `N/A`, not as a failure.\n\n## 2. Gather sitewide signals\n\nAlways check these resources first:\n\n- `/llms.txt`\n- `/llms-full.txt`\n- `/robots.txt`\n- `/sitemap.xml`\n\nOnly check host-level tool manifests when the host is an app/tool host,\nmixed host, or explicitly advertises them:\n\n- `/.well-known/ai-plugin.json`\n- `/.well-known/agent.json`\n- `/.well-known/agents.json`\n\nUse the bundled script for a baseline:\n\n```bash\nbash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\nThe script produces baseline evidence only. You still need to interpret\nwhat matters for a docs property and score it with the rubric.\n\nFor docs-only hosts, you may skip tool-manifest probes to reduce noise:\n\n```bash\nCHECK_TOOL_MANIFESTS=0 \\\n  bash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\n## 3. Sample representative pages\n\nUse the sitemap when available. Do not rely on the homepage alone.\n\nIf `llms.txt` exists, sample some URLs from it as well. This helps catch\nstale or misleading discovery surfaces that a sitemap-only sample would miss.\n\nSample at least 12 pages when the site is large enough, and cover multiple\npage types:\n\n- homepage or docs landing page\n- section landing pages\n- task guides\n- product manuals\n- reference or API pages\n- tutorial or learning pages\n\nIf the sitemap is missing or unusable, discover pages through internal\nlinks and note the lower confidence.\n\nIf the site has distinct delivery patterns, sample each one. For example:\n\n- normal content pages\n- generated reference pages\n- versioned docs\n- localized docs\n\n## 4. Run fetch-path checks on each sample\n\nFor each sampled page, verify:\n\n- HTML fetch status, content type, and final URL\n- `Accept: text/markdown` behavior\n- direct markdown route behavior such as `<page>.md` or another stable path\n- page-level markdown alternate links and whether they actually resolve\n- whether page actions such as \"Open Markdown\" agree with the working route\n- whether the HTML title or H1 matches the markdown H1 closely enough for\n  retrieval parity\n- whether main content is present in the initial HTML\n- redirect chain length and canonical URL consistency\n- obvious chrome/noise in the markdown response\n\nDo not assume a `.md` mirror exists just because another site uses one.\nVerify the actual markdown path the site exposes.\n\nTreat these as separate signals:\n\n- negotiated markdown works\n- a stable direct markdown URL works\n- the page advertises the correct markdown URL\n\nIf the page advertises dead markdown alternates but a working markdown route\nexists, do not fail markdown delivery outright. Score it as a discoverability\nand consistency problem instead.\n\nFor API or generated reference pages, also verify whether a machine-readable\nasset such as OpenAPI YAML is directly linked and fetchable.\n\n## 5. Judge structure and legibility\n\nMeasure structural signals:\n\n- exactly one `h1`\n- sane heading hierarchy\n- `main` and `article` presence where appropriate\n- canonical tags\n- JSON-LD or breadcrumb structured data\n- stable anchors and deep-linkable headings\n\nAlso make a qualitative judgment about agent legibility:\n\n- markdown strips site chrome cleanly\n- headings are specific and task-oriented\n- code blocks stay intelligible without client-side JS\n- the page is not dominated by banners, injected chat, or nav noise\n\nMeasure code block labeling explicitly when code samples are common. A page\ntype with many untagged fenced blocks should lose points even if the prose is\notherwise clean.\n\nFor page types that intentionally render interactive UIs with JavaScript,\njudge them separately from normal docs pages. If the HTML shell is thin,\ncheck whether the page still provides:\n\n- a fetchable markdown summary\n- a directly linked machine-readable asset\n- a usable non-JS fallback\n\n## 6. Score with the rubric\n\nUse [references/rubric.md](references/rubric.md).\n\nRules:\n\n- score only what you verified\n- mark non-applicable checks as `N/A`\n- normalize the final score against applicable points only\n- do not let optional manifest checks dominate the grade\n\nApply the foundational caps from the rubric. A site with broken discovery\nor broken markdown delivery should not earn a high grade because it has\nclean metadata.\n\nDo not average away a weak page type. If one major page type, such as API\nreference, is materially worse than the rest of the corpus, call it out as\nthe weakest segment and reflect it in the category notes.\n\n## 7. Compare with external scanners when useful\n\nIf external scanner results are available, compare them to your live\nfindings. Treat them as secondary evidence.\n\nIf a scanner and the live fetch disagree:\n\n- trust the live fetch\n- report the mismatch explicitly\n- explain whether the scanner is testing a different assumption\n\n## 8. Produce a remediation list\n\nTurn findings into a short backlog:\n\n- `P0`: fetchability or discovery blockers\n- `P1`: recurring structural or parity issues\n- `P2`: polish, optional manifests, or low-impact enhancements\n\nFor each remediation, include:\n\n- the failing signal\n- why it matters to agents\n- a concrete fix\n- whether it is sitewide or page-type-specific\n\n## 9. Report in a stable format\n\nUse [references/report-template.md](references/report-template.md).\n\nAlways include:\n\n- overall score and grade\n- confidence level\n- sampled URLs or sample strategy\n- category scores\n- highest-priority findings\n- remediation backlog\n\n## Notes\n\n- Favor docs-delivery checks over marketing-site heuristics.\n- Do not fail a docs host for lacking MCP or plugin manifests unless the\n  host itself is meant to expose tools.\n- Treat raw byte size as supporting evidence, not as a primary scoring input.\n- Prefer short evidence excerpts and commands over long copied page text.\n",".agents/skills/agent-readiness-audit/references/report-template.md":"# Agent Readiness Report Template\n\nUse this structure for final audit output.\n\n```markdown\n## Agent Readiness Audit\n\n**Site:** <base-url>\n**Date:** <YYYY-MM-DD>\n**Overall score:** <score>/100\n**Grade:** <A-F>\n**Confidence:** <High|Medium|Low>\n\n### Summary\n\n<2-4 sentence verdict focused on what an external agent can actually\ndiscover, fetch, and interpret on this site.>\n\n### Category Scores\n\n| Category | Score | Notes |\n| --- | ---: | --- |\n| Discovery and policy | <x>/<y> | <short note> |\n| Retrieval and markdown delivery | <x>/<y> | <short note> |\n| Structure and semantics | <x>/<y> | <short note> |\n| Crawlability and delivery behavior | <x>/<y> | <short note> |\n| Machine-readable surfaces | <x>/<y> | <short note or N/A> |\n| Content legibility | <x>/<y> | <short note> |\n\n### Sample\n\n- Sample strategy: <sitemap / internal links / explicit URLs>\n- Sampled pages: <count>\n- Page types covered: <landing, guide, manual, reference, ...>\n- Weakest page type: <if any>\n\n### Findings\n\n- `P0`: <highest-priority blocker with evidence>\n- `P1`: <important recurring issue with evidence>\n- `P2`: <lower-priority or optional improvement>\n\n### Remediation\n\n- `P0`: <fix>, because <why it matters to agents>\n- `P1`: <fix>, because <why it matters to agents>\n- `P2`: <fix>, because <why it matters to agents>\n\n### Evidence\n\n- Sitewide checks: <llms.txt, robots.txt, sitemap.xml, manifests>\n- Fetch-path checks: <markdown negotiation, direct markdown routes,\n  advertised alternates, parity>\n- Structural checks: <h1/main/article/canonical/json-ld/title-h1 parity>\n- Code block checks: <fence count, language-tag coverage>\n- Scanner comparison: <optional>\n```\n\n## Notes\n\n- Keep the summary short and outcome-oriented.\n- Findings should refer to concrete URLs or page types.\n- If a criterion is `N/A`, say why instead of leaving it blank.\n",".agents/skills/agent-readiness-audit/references/rubric.md":"# Agent Readiness Rubric\n\nScore the site on a 100-point scale before normalization. If a criterion is\nnot applicable, remove its points from the denominator instead of treating\nit as failed.\n\n## Grade bands\n\n- `A`: 90-100\n- `B`: 80-89\n- `C`: 65-79\n- `D`: 50-64\n- `F`: below 50\n\n## Confidence levels\n\n- `High`: sitemap available and at least 12 sampled pages across at least\n  four page types\n- `Medium`: six to 11 sampled pages, or weaker coverage of page types\n- `Low`: fewer than six sampled pages, or homepage-biased sampling\n\n## Foundational caps\n\nApply these after computing the raw score:\n\n- No `sitemap.xml` and no `llms.txt`: maximum grade `C`\n- Markdown delivery fails on most sampled pages and no usable alternate\n  markdown path exists: maximum grade `D`\n- Main content is missing from initial HTML on more than 25% of sampled\n  pages: maximum grade `D`\n- `robots.txt` blocks broad crawl access to the docs site and the block is\n  not clearly intentional: maximum grade `F`\n\nOptional manifest gaps alone must not drop a docs-only host below `B`.\n\n## Categories\n\n### 1. Discovery and policy - 15 points\n\n- `5` `llms.txt` exists, is fetchable, and is useful for agent discovery\n- `4` `sitemap.xml` exists and includes the main docs corpus\n- `4` `robots.txt` is accessible and does not unintentionally block major\n  crawl agents or search agents\n- `2` curated bulk-discovery aid exists, such as `llms-full.txt` or an\n  equivalent machine-readable catalog\n\nWhen `llms.txt` exists, sample some URLs from it. Stale or misleading\ndiscovery links should reduce this category even if the file itself exists.\n\n### 2. Retrieval and markdown delivery - 25 points\n\n- `8` `Accept: text/markdown` works on sampled pages or an equivalent\n  negotiated markdown response exists\n- `5` a stable direct markdown route works on sampled pages\n- `5` page-level markdown hints, alternates, or UI actions point to a\n  working markdown URL\n- `4` markdown responses strip navigation chrome and preserve headings,\n  links, and code blocks cleanly\n- `3` HTML and markdown stay in parity across the sampled set\n\n### 3. Structure and semantics - 20 points\n\n- `6` sampled pages have one `h1` and a mostly consistent heading hierarchy\n- `5` `main` or `article` marks the primary content and the content is\n  present in the initial HTML\n- `4` canonical tags and stable final URLs are correct\n- `3` structured data such as breadcrumbs or article metadata exists where\n  appropriate\n- `2` headings expose stable anchors or deep-link targets, and the HTML title\n  or H1 stays reasonably aligned with the markdown H1\n\n### 4. Crawlability and delivery behavior - 15 points\n\n- `5` crawl directives are sane for a public docs property\n- `4` the site does not depend on client-side rendering to expose core\n  content\n- `3` cache and freshness signals are reasonable for bots, such as\n  `ETag`, `Last-Modified`, or useful cache headers\n- `3` redirect chains are short and predictable\n\n### 5. Machine-readable surfaces - 10 points\n\n- `4` API or reference sections expose OpenAPI, schema, or downloadable\n  machine-readable assets where relevant\n- `3` pages with interactive JavaScript reference UIs still provide a usable\n  non-JS fallback such as markdown, YAML, or another directly linked asset\n- `3` tool manifests such as MCP, plugin, or agent descriptors exist only\n  when the audited host is actually meant to expose tools\n\n### 6. Content legibility - 15 points\n\n- `5` markdown is clean and low-noise rather than a dump of site chrome\n- `4` headings and section intros are specific enough for retrieval and\n  chunking\n- `3` fenced code blocks are mostly language-tagged and remain copyable and\n  interpretable\n- `3` repeated banners, chat chrome, consent overlays, or other boilerplate\n  do not overwhelm the main content\n\n## Scoring guidance\n\nUse the full category only when the signal is consistently good across the\nsample. Partial credit is expected.\n\nExamples:\n\n- A sitewide `llms.txt` that exists but is stale or too shallow may earn\n  partial credit rather than full credit.\n- If markdown works only on some page types, score that criterion based on\n  observed coverage instead of failing or passing it outright.\n- If a working markdown route exists but the page advertises a dead\n  alternate URL, deduct in markdown discoverability rather than in raw\n  markdown availability.\n- If `llms.txt` exists but points to stale, broken, or inconsistent paths,\n  deduct in discovery rather than in core fetchability.\n- If tool manifests are irrelevant to the host, mark them `N/A`.\n- If a major page type is weaker than the rest of the site, note that\n  explicitly instead of letting stronger page types hide it in the average.\n\n## Reporting guidance\n\nFor every category, include one line that explains the score:\n\n- what was tested\n- what passed\n- what limited the score\n\nUse evidence from live fetches. Do not score from assumptions about the\nframework or source repository.\n",".agents/skills/create-lab-guide/SKILL.md":"---\nname: create-lab-guide\ndescription: \"Clone a dockersamples Labspace repo, extract learning objectives and module structure from labspace.yaml, and produce a Hugo guide page under content/guides/ with correct frontmatter, labspace-launch shortcode, and Docker docs style compliance. Use when asked to create a lab guide, write a Labspace page, add a Docker lab tutorial, migrate a lab to docs, or document a hands-on lab.\"\n---\n\n# Create Lab Guide\n\nCreate a guide page for a Docker Labspace: clone the source repo, extract\nstructure from `labspace.yaml`, write the Hugo markdown page, and validate.\n\n## Inputs\n\n- **REPO_NAME**: GitHub repo in the `dockersamples` org (e.g. `labspace-ai-fundamentals`)\n\n## Step 1: Clone the labspace repo\n\n```bash\nTMPDIR=$(mktemp -d)\ngit clone --depth 1 https://github.com/dockersamples/{REPO_NAME}.git \"$TMPDIR/{REPO_NAME}\"\n```\n\n## Step 2: Extract key information\n\nRead these files from the cloned repo:\n\n| File | Purpose |\n|------|---------|\n| `README.md` | Lab purpose and overview |\n| `labspace/labspace.yaml` | Module structure and content paths |\n| `labspace/*.md` | Module content (only files listed in `labspace.yaml`) |\n| `.github/workflows/*.yml` | Published Compose file URL for the launch command |\n| `compose.override.yaml` | Check for top-level `model` specs (triggers `model-download` param) |\n\nExtract:\n1. A short description for the `description` and `summary` frontmatter fields.\n2. Learning objectives from the module content.\n3. Whether a model download is required (`compose.override.yaml` → top-level `model` key).\n\n## Step 3: Write the guide markdown\n\nPlace the file at `content/guides/lab-{GUIDE_ID}.md`.\n\n```markdown\n---\ntitle: \"Lab: { Short title }\"\nlinkTitle: \"Lab: { Short title }\"\ndescription: |\n  A short description of the lab for SEO and social sharing.\nsummary: |\n  A short summary of the lab for the guides listing page. 2-3 lines.\nkeywords: AI, Docker, Model Runner, agentic apps, lab, labspace\naliases: # Include only for AI-related labs\n  - /labs/docker-for-ai/{REPO_NAME_WITHOUT_LABSPACE_PREFIX}/\nparams:\n  tags: [ai, labs]\n  time: 20 minutes\n  resource_links:\n    - title: A resource link pointing to relevant documentation or code\n      url: /ai/model-runner/\n    - title: Labspace repository\n      url: https://github.com/dockersamples/{REPO_NAME}\n---\n\nShort explanation of the lab and what it covers.\n\n## Launch the lab\n\n{{< labspace-launch image=\"dockersamples/{REPO_NAME}\" >}}\n\n## What you'll learn\n\nBy the end of this Labspace, you will have completed the following:\n\n- Objective #1\n- Objective #2\n- Objective #3\n\n## Modules\n\n| # | Module | Description |\n|---|--------|-------------|\n| 1 | Module #1 | Description of module #1 |\n| 2 | Module #2 | Description of module #2 |\n| 3 | Module #3 | Description of module #3 |\n```\n\nConditional rules:\n- All lab guides **must** include `labs` in `params.tags`.\n- AI-related labs: also add `ai` tag and an alias under `/labs/docker-for-ai/`.\n- If a model download is required: add `model-download: true` to the `labspace-launch` shortcode.\n\n## Step 4: Apply Docker docs style rules\n\nFollow STYLE.md and COMPONENTS.md. Key rules:\n\n| Avoid | Use instead |\n|-------|-------------|\n| \"we\", \"let's\" | Imperative voice or \"you\" |\n| \"simply\", \"easily\", \"just\" | Remove the hedge word |\n| \"allows you to\" / \"enables you to\" | \"lets you\" or rephrase |\n| \"click\" | \"select\" |\n| Bold for emphasis / product names | Bold only for UI elements |\n| \"currently\", \"new\", \"recently\" | Remove time-relative language |\n\nUse `console` as the language hint for shell blocks with `$` prompts.\nUse contractions (\"it's\", \"you're\", \"don't\").\n\n## Step 5: Validate\n\n1. Confirm frontmatter has `title`, `description`, `keywords`, and `params.tags` including `labs`.\n2. Run `npx --no-install rumdl fmt <file>` to format.\n3. Run `docker buildx bake lint vale` and fix any errors.\n4. Re-read the file and verify: correct shortcode syntax, objectives match source content, modules match `labspace.yaml`, no vendored paths edited.\n\nDo not proceed to commit until validation passes.\n",".agents/skills/create-pr/SKILL.md":"---\nname: create-pr\ndescription: >\n  Push the current branch and create a pull request against docker/docs.\n  Use after changes are committed and reviewed. \"create a PR\", \"submit the\n  fix\", \"open a pull request for this\".\n---\n\n# Create PR\n\nPush the branch and create a properly structured pull request.\n\n## 1. Verify the branch\n\nConfirm you're on a dedicated branch, not the default branch:\n\n```bash\ngit branch --show-current   # must not be main or master\n```\n\nIf this returns `main` or `master`, stop. Create a branch and move your\ncommits onto it before continuing.\n\nConfirm commits exist and the working tree is clean:\n\n```bash\ngit log --oneline main..HEAD   # confirm commits exist\ngit status --porcelain         # must print nothing\n```\n\nIf `git status --porcelain` prints anything, there are uncommitted or\nunstaged changes. Stop and commit them — or unstage stray files like\n`package-lock.json` — before opening a PR. Don't open a PR mid-edit.\n\n## 2. Push the branch\n\nIdentify the remote that points at your fork. Inspect the remotes:\n\n```bash\ngit remote -v\n```\n\nIf `origin` is your fork, use it. If `origin` points at canonical\n`docker/docs` (the upstream), push to your separate fork remote instead —\nnever push the branch to `docker/docs` directly:\n\n```bash\nFORK_REMOTE=origin   # or the name of your fork remote if origin is upstream\ngit push -u \"$FORK_REMOTE\" <branch-name>\n```\n\n## 3. Create the PR\n\nBefore creating a PR for an issue, check whether that issue already has an open\nlinked PR:\n\n```bash\ngh api repos/docker/docs/issues/<issue-number>/timeline --paginate \\\n  --jq '.[] | select((.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") and .source.issue.pull_request and .source.issue.state==\"open\") | {url: .source.issue.html_url, title: .source.issue.title}'\n```\n\nIf this returns an open PR that addresses the same issue, stop. Don't open a\nduplicate PR; report the existing PR instead. Only proceed if there is no open\nlinked PR, or if the existing PR clearly does not address the issue and you\nexplain why in the new PR body.\n\nDerive the fork owner dynamically from the same fork remote you pushed to:\n\n```bash\nFORK_OWNER=$(git remote get-url \"$FORK_REMOTE\" | sed -E 's|.*[:/]([^/]+)/[^/]+(\\.git)?$|\\1|')\n```\n\n```bash\ngh pr create --repo docker/docs \\\n  --head \"${FORK_OWNER}:<branch-name>\" \\\n  --title \"<concise summary under 70 chars>\" \\\n  --body \"$(cat <<'EOF'\n## Summary\n\n<1-2 sentences: what was wrong and what was changed>\n\nCloses #NNNN\n\nGenerated by <active coding agent name>\nEOF\n)\"\n```\n\nPrefix the title with the change type to match repo convention — `docs:` for\ndocumentation changes (or another scope like `hub:` when appropriate), for\nexample `docs: fix broken link on install page`.\n\nKeep the body short. Reviewers need to know what changed and why — nothing\nelse. Do **not** add a \"Test plan\" section — documentation PRs don't need one.\n\nUse an accurate disclosure footer that names the active coding agent, for\nexample `Generated by Codex` or `Generated by Claude Code`.\n\n### Optional: Netlify preview entry path\n\nIf the PR primarily edits a single page or a focused section of pages, add a\n`@netlify` stanza to the PR body (for example, just below the Summary). This\nsets the entry path for the Netlify deploy preview so reviewers land on the\nedited page instead of the site root:\n\n```markdown\n@netlify /desktop/setup/install/\n```\n\nThe stanza takes a single published URL path. Derive it from the source file\npath: drop the `content/` prefix and `.md` suffix, strip the `/manuals`\nsegment, and add a trailing slash. For example,\n`content/manuals/desktop/setup/install/mac-install.md` becomes\n`/desktop/setup/install/mac-install/`.\n\nOnly add this when the change is focused on one page or section. Skip it for\nPRs that touch many unrelated pages — there is no useful single entry path.\n\n### Optional: Preview links\n\nWhen the change is focused, also add direct links to the deploy preview in the\nPR body so reviewers can jump straight to the affected pages. The preview URL\nembeds the PR number:\n\n```\nhttps://deploy-preview-<pr-number>--docsdocker.netlify.app/path/to/page/\n```\n\nThe PR number isn't known until `gh pr create` returns, so add these links\nafter creating the PR by updating the body:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --body \"...\"\n```\n\nUse the same source-path-to-URL mapping as the `@netlify` stanza above.\n\n## 4. Apply labels and request review\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<pr-number>/labels \\\n  --method POST \\\n  --field 'labels[]=status/review'\n```\n\nRequest review:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --add-reviewer docker/docs-team\n```\n\nVerify the reviewer was assigned:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json reviewRequests \\\n  --jq '.reviewRequests[].slug'\n```\n\nIf the team doesn't appear, use the API directly:\n\n```bash\ngh api repos/docker/docs/pulls/<pr-number>/requested_reviewers \\\n  --method POST --field 'team_reviewers[]=docs-team'\n```\n\n## 5. Report\n\nPrint the PR URL and current CI state:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json url,state\ngh pr checks <pr-number> --repo docker/docs --json name,state\n```\n\n## Notes\n\n- Always use `Closes #NNNN` (not \"Fixes\") for GitHub auto-close linkage\n- One issue, one branch, one PR — never combine\n",".agents/skills/curate-whats-new/SKILL.md":"---\nname: curate-whats-new\ndescription: Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights.\n---\n\n# Curate What's New\n\nTreat merged documentation as evidence that a capability shipped, but do not\ntreat a documentation change as news by itself.\n\nInclude an item only when the merged documentation directly shows all of the\nfollowing:\n\n- A released user-facing feature, material enhancement, or broader availability\n  milestone that was not available before the period\n- A substantial capability or workflow, not new syntax or a small control\n  within an existing workflow\n- Enough Docker-wide editorial significance to merit proactively telling users\n  about it outside product release notes\n- A useful published page and a factual title and description\n\nApply a high bar. The result is a curated launch archive, not a complete\nchangelog. A specialized feature can qualify when its user impact is\nsubstantial. A quiet period can produce few or no items.\n\n## Exclusions\n\nExclude documentation maintenance; fixes; rewrites; guidance for old behavior;\nroutine release or generated-content syncs; limitations, prerequisites, and\nworkarounds; narrow flags, settings, command variants, protocols, and\ncompatibility changes; incremental UI, safety, permissions, or observability\nimprovements; and lower-level Engine, Build, networking, or storage changes.\nThese qualify only when they are part of an independently newsworthy\nproduct-level launch.\n\nJudge the user outcome, not PR size, product popularity, labels, changed lines,\na dedicated page, or the existence of a new API or command.\n\n## Select highlights\n\nInclude every qualifying launch; do not impose a quota. Mark the five most\nimportant as `featured: true`, or all items when fewer than five qualify. Rank\nby the magnitude and distinctness of the user outcome and the value of helping\nits audience discover it. Breadth can matter, but a major capability for a\nspecialized audience can outrank a smaller change for a broad audience. Recency\nand product variety are not ranking goals.\n\nCreate one item per launch and combine PRs that document the same launch.\nPreserve existing copy while it remains accurate and qualifies. Change featured\nstatus only when the relative importance of the candidate set changes.\n\n## Procedure\n\n1. Read `data/whats-new.json`.\n2. Determine the review mode from the request:\n   - For an incremental review, list PRs merged from the day after the supplied\n     checkpoint through the end of the publication window. Retain existing\n     items inside the publication window without re-reviewing their source PRs.\n   - For a full review, inspect every PR merged in the supplied publication\n     window.\n3. List PRs in the range that applies to the review mode:\n\n   ```console\n   $ gh pr list --repo docker/docs --state merged --search 'merged:START..END' --limit 200\n   ```\n\n   If an incremental search returns no PRs, skip candidate inspection and only\n   remove expired items.\n4. Inspect the diff and resulting pages for every plausible new candidate.\n5. Decide what qualifies using only evidence in the merged documentation.\n6. Remove existing items published before the requested publication window.\n   Add newly qualifying launches, combine related PRs, and reconsider featured\n   status across the resulting list. Do not replace or rewrite retained items\n   merely because they were not part of the incremental candidate range.\n7. Replace `period_start`, `period_end`, and `items` in\n   `data/whats-new.json`. Sort items by `published` date, newest first.\n8. Write `.pr-body.md` with the publication period, selected highlights and\n   source PRs, plus concise reasons for plausible exclusions.\n\nEach item must contain `product`, `title`, `description`, `url`, `published`,\n`source_prs`, and `featured`. Use the canonical product name, a published\ninternal URL, the merge date in `YYYY-MM-DD` format, and source PR numbers.\n\nWrite factual, restrained copy. Avoid superlatives, promotional language, and\nclaims about ease or importance. Do not modify tracked files other than\n`data/whats-new.json`.\n",".agents/skills/curate-whats-new/agents/openai.yaml":"interface:\n  display_name: \"Curate What's New\"\n  short_description: \"Curate noteworthy Docker launches from merged docs\"\n  default_prompt: \"Use $curate-whats-new to curate Docker launches published during the requested date range.\"\n",".agents/skills/fix-issue/SKILL.md":"---\nname: fix-issue\ndescription: >\n  Fix a single GitHub issue end-to-end: triage, research, write the fix,\n  review, and create a PR. Use when asked to fix an issue: \"fix issue 1234\",\n  \"resolve #500\", \"create a PR for issue 200\".\nargument-hint: \"<issue-number>\"\n---\n\n# Fix Issue\n\nGiven GitHub issue **$ARGUMENTS**, decide what to do with it and either\nclose it or fix it. This skill orchestrates the composable skills — it owns\nthe decision tree, not the individual steps.\n\n## 1. Triage\n\nInvoke `/triage-issue $ARGUMENTS` to understand the issue and decide what\nto do. This runs in a forked subagent and returns a verdict.\n\n## 2. Act on the triage result\n\nIf triage says **close it** — comment with the reason and close:\n```bash\ngh issue close $ARGUMENTS --repo docker/docs \\\n  --comment \"<one sentence explaining why>\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **escalate upstream** — comment noting the repo and stop:\n```bash\ngh issue comment $ARGUMENTS --repo docker/docs \\\n  --body \"This needs to be fixed in <upstream-repo>.\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **leave it open** — comment explaining what was checked and\nwhat's unclear. Do not close.\nDone.\n\nEnd every issue comment with an accurate agent-disclosure footer that names\nthe active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n\nIf triage says **fix it** — proceed to step 3.\n\n## 3. Research\n\nInvoke `/research` to locate affected files, verify facts, and identify\nthe fix. The issue context carries over from triage. This runs inline —\nfindings stay in conversation context for the write step.\n\nIf research reveals the issue is upstream or cannot be fixed (e.g.\nunverifiable URLs), comment on the issue and stop.\n\n## 4. Write\n\nInvoke `/write` to create a branch, make the change, format, self-review,\nand commit.\n\n## 5. Review\n\nInvoke `/review-changes` to check the diff for correctness, coherence, and\nmechanical compliance. This runs in a forked subagent with fresh context.\n\nIf issues are found, fix them and re-review until clean.\n\n## 6. Create PR\n\nInvoke `/create-pr` to push the branch and open a pull request.\n\n## 7. Return to main\n\n```bash\ngit checkout main\n```\n\n## 8. Report\n\nSummarize what happened: the issue number, what was done (closed, escalated,\nfixed with a PR link), and why — in a sentence or two.\n",".agents/skills/maintain-pr/SKILL.md":"---\nname: maintain-pr\ndescription: >\n  Maintain and follow up on a single Docker documentation pull request that\n  you own or are responsible for updating. Check CI and review feedback, fix\n  actionable failures, push changes, reply to comments, and report status.\n  Use for requests such as \"babysit this PR\", \"check the status of my PR\",\n  \"fix CI on my PR\", or \"address review comments on #500\". Do not use for\n  maintainer review of an incoming contribution; use review-pr for that.\n---\n\n# Maintain PR\n\nDo one maintenance pass over the specified author-owned PR: inspect its\nstate, fix actionable failures or feedback, reply to reviewers, and report\nthe result. This workflow may modify the branch and GitHub because the user\nis asking to maintain the PR. Do not apply it to an incoming PR merely\nbecause the user asks to review or assess it.\n\n## 1. Gather PR state\n\n```bash\ngh pr view <PR> --repo docker/docs --json state,title,url,headRefName,headRepositoryOwner,comments,reviews,reviewDecision\ngh pr checks <PR> --repo docker/docs --json name,state,detailsUrl\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, in_reply_to_id}]'\n```\n\nAlways check both top-level reviews and inline comments. A review with an\nempty body may still contain line-level feedback. Confirm that the PR is one\nthe user owns or is authorized to update before checking out or pushing its\nbranch. If not, stop and use `review-pr`.\n\n## 2. Handle terminal states\n\nIf merged, report the final state and identify unanswered review comments.\nReply only when the user remains responsible for follow-up.\n\nIf closed without merge, read the closing context and report the reason.\nCommon causes include maintainer rejection, supersession, or automation.\n\n## 3. Diagnose CI failures\n\n- Read the failure details.\n- Determine whether the failure comes from the PR or predates it.\n- Fix actionable failures in the PR's changed files.\n- Report pre-existing or upstream failures without changing unrelated files.\n\nFollow repository instructions for formatting, targeted linting, explicit\nstaging, commits, and pushes. Preserve unrelated working-tree changes.\n\n## 4. Address review feedback\n\nTreat every review comment as a claim to verify. Implement it only when the\nevidence supports it; explain any evidence-based disagreement.\n\nAfter each fix:\n\n1. Format and validate the changed files.\n2. Commit and push the focused change.\n3. Reply to every addressed thread with what changed or why no change was\n   made.\n4. End replies with an accurate agent-disclosure footer, such as\n   `Generated by Codex`.\n5. Resolve threads only after replying.\n6. Re-request review when appropriate.\n\nUse the inline comment endpoint to reply:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --method POST \\\n  --field in_reply_to=<COMMENT_ID> \\\n  --field body='<RESPONSE>'\n```\n\nUse GraphQL to retrieve unresolved review-thread IDs and resolve only the\nthreads that were addressed. Do not silently fix feedback without replying.\n\n## 5. Report\n\n```markdown\n## PR #<number>: <title>\n\n**State:** <open, merged, or closed>\n**CI:** <passing, failing, or pending>\n**Review:** <approved, changes requested, or pending>\n**Action taken:** <changes, replies, and thread resolution, or none needed>\n```\n",".agents/skills/maintain-pr/agents/openai.yaml":"interface:\n  display_name: \"Maintain PR\"\n  short_description: \"Maintain an authored PR through review and CI\"\n  default_prompt: \"Use $maintain-pr to maintain this pull request through CI and review follow-up.\"\n",".agents/skills/migrate-content-ia/SKILL.md":"---\nname: migrate-content-ia\ndescription: >\n  Handle Hugo docs information-architecture moves: discover old vs new URLs,\n  add front matter aliases (Phase 1), update in-repo links (Phase 2), interactive\n  List 2 resolution and fragment validation (Phase 3; no guessing). Supports\n  PR-scoped mapping plus whole-content sweeps for inbound links to that mapping,\n  or a full-site follow-up. Triggers on: \"IA migration\", \"redirects for moved\n  pages\", \"fix links after content move\", \"PR-scoped link/anchor pass\",\n  \"aliases for old URLs\". After branch work, chain the review-changes skill\n  (main...HEAD) before a PR. Agents must run the in-file required procedure\n  and definition of done, not the phases alone in isolation.\n---\n\n# Migrate content IA (redirects + links + anchors)\n\nUse this skill when pages **move or rename** under `content/` and you must\npreserve old public URLs and/or fix cross-references. Work in **phases**;\nchoose **PR-scoped** vs **full-site** mode per run.\n\n**Read first:** **CLAUDE.md** / **AGENTS.md** (URL rules, vendored areas, external\nlinks, special cases) and **hugo.yaml** (`permalinks`, `refLinksErrorLevel`,\n`disablePathToLower`). For **prose and link text**, follow **STYLE.md**; for\n**components, front matter, and link examples**, follow **COMPONENTS.md**.\n\n**Related skills:** **research** helps map moves and find inbound links; **write**\ncommits minimal edits. Run this skill’s phases after the move is identified (or\nin parallel with research for large IA work).\n\n## Agent: required procedure (do not skip)\n\n**Common mistake (wrong):** use **`git diff main...HEAD` (or the PR’s file\nlist) as the full set of places to fix links** for a migration. That set shows\n**what *moved***; it is **not** the list of every page that **points *to*** a\nmoved page. Inbound stragglers are often in files the PR **never** touched. You\nmust still **sweep the repo** for every string in the **old path and published-URL set**\nfor this run, not only for “files in the diff.”\n\n**Definition of done (when the migration is *finished*):** **Both** of the\nfollowing (unless the user or **AGENTS.md** **explicitly defers** a **List 2**\nitem in **Phase 3**; document the deferral):\n\n1. **`docker buildx bake validate`** passes for the branch, with no new\n   build/link errors from this work.\n2. A **sweep of the old path and published-URL set for this run** (see\n   [Sweep commands](#sweep-commands) below) finds **no** remaining\n   migration-relevant **inbound** reference—**including**:\n   - links to an old **source** path (plain `.md` and equivalent `ref` forms),\n   - links that use the old path **and** a `#fragment`,\n   - and, where your mapping includes them, old **published-style** `link:` /\n   `url:` / full-site URL strings,  \n   **except** intentional entries to keep: for example `aliases` on the **new**\n   canonical page, or **redirects.yml** *sources* you must not edit per policy.\n   (A hit on a **source** that is only an `alias` line on the new page is\n   **expected**—do not “fix” that away; distinguish alias rows from straggler\n   links in body or nav config.)\n\n**Chaining (policy):** when this branch’s content work is ready for handoff,\n**run the [review-changes](../review-changes/SKILL.md) skill** on\n**`main...HEAD`** (or **`merge-base`…`HEAD`** for a different target branch) so\nthe **whole branch** is re-read for cross-page issues before opening a PR. Do\nnot treat phases 0–3 alone as the final check.\n\n**Run in order (mandatory for agents):**\n\n1. **Scope the moves (mapping input):** set the Git range like **review-changes**\n   (for a PR to `main`: `git diff --name-only main...HEAD`; for another target:\n   `BASE=$(git merge-base <target-branch> HEAD)` then\n   `git diff --name-only $BASE...HEAD`, as in **Phase 0.5**). Include\n   renames; build the **old → new** table (source and published) per **Phase\n   0**.\n2. **Sweep and list:** for every **old** path/URL in that table, run\n   [Sweep commands](#sweep-commands) on the **allowed** trees. Record\n   every hit as **List 1** (no `#`) or **List 2** (old path with `#...`) per\n   **Phase 0.5**.\n3. **Phased edits:** **Phase 1** (`aliases`), then **Phase 2** (List 1), then\n   **Phase 3** (List 2) with **no guessing**—as in the sections below.\n4. **Re-sweep** the same old-path set, then run **`docker buildx bake\n   validate`**. The **Definition of done** above is met or you have **explicit\n   defers** for the remainder.\n5. **review-changes:** run **[review-changes](../review-changes/SKILL.md)**\n   on the branch vs **`main`…`HEAD`** (or the correct base) before a PR.\n\n### Sweep commands\n\nUse a **repository** search (e.g. `rg` / your IDE) so **nothing** in the\nallowed scope is only eyeballed.\n\n**Trees to include** (at minimum): all of `content/`, plus **`data/`** and\n**`layouts/`** when a migration can appear in config, `link:`-like fields,\nshortcodes, or hardcoded path strings. Follow **Vendored / generated** rules in\n**AGENTS.md**; do not edit disallowed files.\n\n**What to search for (repeat per row in the old side of the mapping):**\n\n- **Hugo / source form:** path segments that identify the *old* file, e.g.\n  `manuals/.../old-segment/...` or `../old-segment/.../page.md` as your tree\n  uses; include variants that still appear in the repo.\n- **Published / site form:** e.g. `/admin/.../old-slug/` in front matter, nav\n  `url:`, or `https://docs.docker.com/...` in allowed files—**match the\n  file’s** established pattern, per **Conventions** below.\n- **Anchors:** search for the **old path string**; matches that also include\n  `#...` belong on **List 2** for **Phase 3** unless the whole link is\n  a pure path-only case.\n\n[scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) (if present) prints\n**`PR_SCOPE_FILES` only**—it does **not** replace this sweep. Use it to build\nthe **old → new** table, **not** to list where inbound links were fixed.\n\n## Progressive disclosure (optional)\n\nThe procedure below stays in this file. If a run produces a very large\n**old → new** URL table, store that table in **`reference.md`** in this skill\ndirectory and link it from the task summary, so the agent reads the long\nmapping only when needed.\n\n## Modes\n\n- **PR-scoped (typical for a single PR)**  \n  - **What the PR “owns” (focus):** use `git diff` / `base...HEAD` to know which\n    pages and renames the branch actually moves (`PR_SCOPE_FILES`). The **old →\n    new** mapping and **List 1 / List 2** for this migration are defined from\n    **that** work, not from unrelated areas.\n  - **Where to look for stale references (sweep):** search broadly—typically all\n    of `content/` (and config, shortcodes, layouts, per Conventions)—for **inbound**\n    links and fields whose **target** is an **old** path or URL in **this** PR’s\n    mapping. Inbound stragglers are often in files the PR never touched; finding\n    them is **in scope** for this migration.  \n  - **What to edit:** update **any** file in the allowed trees that contains a\n    **migration-relevant** reference (target ∈ this PR’s old path set) according\n    to the phases below. **Do not** treat `PR_SCOPE_FILES` as a hard limit on\n    *which files you may save* for **inbound** link repairs (unless\n    project policy for a given PR says otherwise; then follow policy and\n    **defer** out-of-PR file fixes).\n  - **Out of scope (defer / ignore in this run):** link and anchor problems that\n    are **not** about this PR’s old→new map—e.g. a different area’s own slug\n    issues, rot unrelated to the remapped path set. *Example:* a PR that only\n    remaps `content/strawberry/...` should not “fix the whole site”; it **should**\n    still fix a link under `mango/…` that **points at** an old `strawberry/…` path\n    in the mapping, and **should not** chase **mango/**-only issues that do\n    not involve those old targets.\n\n- **Full-site (complete migration after the PR)**  \n  - Update stragglers **across the repo** (or all inbound links to moved\n    sections), including config-driven `link:` fields if policy allows.  \n  - Still make **minimal** edits; no drive-by rewrites to **unrelated** targets\n    outside the run’s **declared** mapping and lists.\n\n### No guessing\n\n- The agent must **not** guess **replacement paths, published URLs, or fragment\n  IDs** (including for consolidated pages, renamed headings, or\n  “semantic” remaps of `#anchor` → new `#…`). If the user has not given an\n  explicit new target, **ask**, **defer**, or **stop** per **AGENTS.md**; never\n  infer, autocomplete, or substitute a plausible fragment from the target page’s\n  heading list. That rule applies in **every** phase, including after validation\n  in Phase 3.\n\n---\n\n## Conventions (links, anchors, redirects)\n\n### Front matter `aliases` (redirects)\n\n- Per **COMPONENTS.md**, `aliases` are **URLs that redirect to this page**.\n- Add or **merge** on the **new canonical** page; do not drop unrelated\n  entries. Match local examples: **published-style paths** (leading `/`), and\n  **trailing `/`** when that matches existing pages in the same area.\n- **No** speculative redirects for URLs that were never published.\n- **Collision check** before adding: no other page or redirect may already\n  own the same old path.\n- If the site also uses **`data/redirects.yml`**, only add entries when\n  project policy requires it; avoid duplicating the same old URL in\n  `aliases` **and** `redirects.yml` unless maintainers do.\n\n### Internal links in Markdown (STYLE.md + COMPONENTS.md)\n\n- Use **relative paths to source files** (e.g. `../section/page.md`) with\n  **`.md`**, following **COMPONENTS.md** examples, unless the file already\n  uses an established pattern (e.g. some `link:` or nav fields use **published**\n  paths without `manuals` or `.md` — **match the surrounding file**).\n- Keep **CLAUDE.md** / **AGENTS.md** rules: internal ref targets under\n  `content/manuals/...` often use the full **`/manuals/...`** path; published\n  URLs omit the `manuals` segment—do not confuse the two when fixing links.\n- **Link text (STYLE.md):** descriptive, ~**5 words**; no “click here” or\n  “learn more”; **no** end punctuation **inside** the link text; **no** bold/italic\n  on link text unless normal in the sentence.\n- **Headings (STYLE):** **sentence case**; do not rename headings in passing\n  unless the migration requires it (heading changes break fragments).\n\n### Shortcodes and layouts (links not only in Markdown)\n\n- **Phase 2–3 scope includes** any **shortcode or layout partial** (under\n  **Modes**, search broadly for inbound links to the migration; **edits** follow\n  the same file-level rules as for Markdown) that emits links: e.g. `ref` /\n  `relref`, `link` fields in shortcode args, or hardcoded\n  `docs.docker.com` / path strings. Grep for old paths, slugs, and fragments\n  under `layouts/shortcodes/` (and `layouts/_default/` if partials build nav).\n- Match each file’s existing pattern; do not rewrite working shortcode style\n  just to “clean up.”\n\n### Fragments / anchors (Phase 3)\n\n- List 1 / List 2: fragment-bearing **cross-references to old paths** are tracked\n  on **List 2** in Phase 0.5; do not bulk-rewrite them in the **List 1** pass\n  (Phase 2). See Phase 0.5 and Phase 2.\n- **Valid `#fragment` values:** after the user supplies a new fragment, it should\n  match the **target** page’s **generated** heading ID (Hugo slugification; see\n  **CLAUDE.md** / **AGENTS.md**). The agent still **validates** (see Phase 3) and\n  must **not** “pick” a different id from the page to replace a bad answer—**No\n  guessing**.\n- Same-page: `[Text](#section-id)`.\n- Cross-page: when user-provided, `#fragment` must still be checked against the\n  **target** file. Validate fragments in shortcodes the same way as in body\n  Markdown.\n\n### External URLs (**AGENTS.md**)\n\n- Do not commit **guessed** replacement URLs. If a URL cannot be verified,\n  treat as blocked or drop the fragment per AGENTS guidance. See also **No\n  guessing** above; internal and external link targets are treated the same for\n  inference: **none** without user input or a verified source.\n\n### Special cases (**AGENTS.md**)\n\n- **Engine API version** pages: respect coordinated **`/latest/` `aliases`**\n  rules—never leave two version files both owning `/latest/`.\n- **Vendored / generated** trees: read-only; see CLAUDE.md. Do not “fix” links\n  there if policy forbids.\n\n---\n\n## Phase 0 — Discovery (read-only; may use whole repo)\n\n1. Read **hugo.yaml** (permalinks, `refLinksErrorLevel`, `disablePathToLower`).\n2. From the branch (diff, renames), build a **mapping table**:\n   - old source path → new source path  \n   - old published URL → new published URL (from permalink rules)\n3. **Case:** with `disablePathToLower: true`, filesystem path **case** appears in\n   URLs—**directory and link casing must match** (e.g. `setup` vs `Setup`).\n4. When planning **inbound link** fixes, treat old-path references as two\n   categories: **no fragment** vs **with `#fragment`**. That split feeds\n   **List 1** and **List 2** in Phase 0.5 and drives Phase 2 ordering (see\n   there).\n\n---\n\n## Phase 0.5 — PR-scoped evaluation (required before edits in PR mode)\n\n1. **Set `PR_SCOPE_FILES` (Git scope for PR mode)**  \n   - When the PR **targets `main`**, use the same triple-dot form as\n     **review-changes**:  \n     `git diff --name-only main...HEAD`  \n   - For a **different target branch** or a custom base, use the merge base:  \n     `BASE=$(git merge-base <target-branch> HEAD)`  \n     then:  \n     `git diff --name-only \"$BASE\"...HEAD`  \n   - Those paths define **what moved** in the branch; they are the primary input\n     to the **old → new** path/URL table. They are **not** a hard cap on *where\n     to search* for **inbound** links (see **Modes**): sweeps for links **to** old\n     paths usually cover all of `content/` (and other trees per Conventions).  \n   - If project policy **limits edits** to the diff for a given PR, follow that\n     and **defer** link fixes in files outside the diff; note the exception in\n     the task if the user relaxes that policy.\n\n2. Build checklists (see **Modes** for sweep vs area-of-work):\n   - path/URL mapping this run must honor (old source path → new; old published\n     → new, from the **PR’s** moves in PR-scoped mode, or the **declared** full\n     migration in full-site mode)\n   - **List 1 — old path, no fragment:** every **inbound** reference, found on\n     the **sweep** surface, to a moved **old** path that does **not** include a\n     `#...` fragment (e.g. `…/banana.md` in the repo’s link style for that\n     file).\n   - **List 2 — old path with fragment:** every **inbound** reference, found on\n     the same sweep, to a moved **old** path that **includes** a `#...` fragment\n     (e.g. `…/banana.md#anchor` or the published-style equivalent in context). The\n     **same** old path string may appear on **both** List 1 and List 2 for\n     different links; duplication across the two lists is OK.\n   - **Matching rules:** when recording List 1 / List 2, use **one** consistent\n     path representation for comparison (e.g. relative `../path/banana.md` vs\n     root-anchored) **per the conventions in this doc** and the **surrounding\n     file’s** established pattern. Agents compare and skip List 2 links in the\n     List 1 pass using the **same** representation rules.\n3. **Out of scope** for the lists: only include references whose **old** target\n   is in this run’s **mapping**. Do not build List 1/2 for unrelated **mango/**\n   (or other) problems unless those links also target an **old** path that this\n   migration renames. Defer those issues separately (see **Modes**).\n\n---\n\n## Phase 1 — `aliases` (old published URLs)\n\n1. On each **new** canonical page, add or merge **`aliases`** for every **real**\n   former public URL.\n2. Do not strip existing unrelated aliases.\n3. **PR-scoped:** add aliases only where the canonical file is in scope or the\n   project requires it; otherwise list missing alias targets for follow-up.\n\n---\n\n## Phase 2 — In-repo link reference updates\n\n1. **List 1 first (path only):** update references that belong to **List 1**\n   (old path, **no** fragment). Replace old source paths or old published URLs\n   with the **new** targets; preserve each file’s link pattern (relative vs\n   root-anchored `.md` paths). **Do not** apply the same bulk path replacement to\n   links that appear in **List 2** (old path **with** `#...`) during this\n   sub-step—**leave** every **List 2** link **unchanged** for now.\n2. **After List 1 is complete:** **re-scan** the **same** **sweep** surface as\n   in Phase 0.5 (e.g. all of `content/` plus config) or **print** a clear list of\n   all **remaining** **List 2** entries. Those links should still point at the\n   **old** path and **old** fragment until Phase 3.  \n3. **Full-site (extra sweep):** after steps 1–2, still use **AGENTS “Page\n   deletion checklist”**-style thoroughness for **config / front matter**\n   `link:` and similar so nav and grids are not left on old slugs. Apply the\n   **List 1 / List 2** rules there too: path-only old references first; defer\n   fragment-bearing rewrites in line with **List 2** until Phase 3.\n4. **PR-scoped (which files to change):** apply List 1 and later Phase 3 updates\n   to **every** file the **sweep** finds with a **migration-relevant** reference\n   (inbound to an **old** path in the mapping), including files **not** in\n   `PR_SCOPE_FILES`, per **Modes**. **Log** and **defer** (do not “fix”)\n   unrelated stragglers. If policy forbids out-of-PR file edits, defer per step 1\n   of Phase 0.5.  \n5. Include **shortcodes and layout partials** (see Conventions and **Modes** for\n   sweep vs focus).\n\n---\n\n## Phase 3 — List 2: interactive path and fragment resolution\n\n**Prerequisites:** Phase 2 has updated **List 1**; **List 2** still lists **old\npath + `#...`** (unchanged) for this migration. See **Modes** for which files\nmay be edited; **No guessing** applies.\n\n1. **Print List 2** to the user: every remaining **old path** + `#anchor` (in the\n   agreed representation), so nothing is hidden before the loop.\n2. **For each distinct** `old-path#oldAnchor` (or process in the order the user\n   prefers, one at a time):  \n   - Ask: **What is the new path (and fragment, if any) for this content?** The\n     user may give a new source path, published URL, and/or `#newAnchor` per\n     project conventions.  \n   - **Validate** the user’s answer: open the **target** page (or resolve the\n     target) and check that `#newAnchor` (if any) **exists** as a real heading\n     / generated id on that page, per **CLAUDE.md** / **AGENTS.md** (same rules\n     as the rest of the site). **Do not** replace the user’s fragment with a\n     “better” one from the file.  \n   - If validation **fails** (unknown target file, or `#newAnchor` not found on\n     the page): **warn** clearly (what failed: path vs missing fragment), then\n     **ask again** for a corrected path and/or fragment. **Repeat** until\n     validation passes or the user **defers** / **drops** the fragment (per\n     **AGENTS.md**). **Never** guess a new fragment to fix the problem.  \n   - When validation **passes:** update **all** in-repo references that match\n     that **same** `old-path#oldAnchor` to the user-approved `new-path#newAnchor`\n     (respect each file’s link style; include shortcodes/layouts on the same\n     **sweep** surface as Phase 2).  \n3. **Repeat** from step 1: **re-print** or **re-scan** for **List 2** until it is\n   **empty** or the user defers the remainder.  \n4. **PR-scoped / full-site:** the **loop** is the same. **Edits** follow **Modes**:\n   migration-relevant **inbound** links may live in any file on the sweep; do\n   not expand into **unrelated** link debt from other areas. Defer as in **Modes**\n   and Phase 0.5.\n\n---\n\n## Optional: scripts helper\n\nThis skill includes a small **scope helper** so agents do not re-derive Git\nrecipes. See [scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) — it prints\npaths in PR scope for a given target branch (default `main`).\n\n---\n\n## Verification\n\n```bash\ndocker buildx bake validate\n```\n\nUse the **Definition of done** in **Agent: required procedure (do not skip)**\nas the final bar: **validate** must pass, and the **sweep** must be clean for\n**plain** and **`#fragment`** old-path references, **or** the remainder must be\n**explicitly deferred** in **Phase 3** per **AGENTS.md** / the user. Mid-run,\n**Phase 2** may still leave **List 2** links unchanged **until** Phase 3; that\nintermediate state is **not** the finished migration.\n",".agents/skills/research/SKILL.md":"---\nname: research\ndescription: >\n  Research a documentation topic — locate affected files, understand the\n  problem, identify what to change. Use when investigating an issue, a\n  question, or a topic before writing a fix. Triggers on: \"research issue\n  1234\", \"investigate what needs changing for #500\", \"what files are\n  affected by #200\", \"where is X documented\", \"is our docs page about Y\n  accurate\", \"look into how we document Z\".\n---\n\n# Research\n\nThoroughly investigate the topic at hand and produce a clear plan for\nthe fix. The goal is to identify exact files, named targets within those\nfiles, and the verified content needed for the fix.\n\n## 1. Gather context\n\nIf the input is a GitHub issue number, fetch it:\n\n```bash\ngh issue view <number> --repo docker/docs \\\n  --json number,title,body,labels,comments\n```\n\nOtherwise, work from what was provided — a description, a URL, a question,\nor prior conversation context. Identify the topic, affected feature, or\npage to investigate.\n\n## 2. Locate affected files\n\nSearch `content/` using the URL or topic from the issue. Remember the\n`/manuals` prefix mapping when converting URLs to file paths.\n\nFor each candidate file, read the relevant section to confirm it contains\nthe reported problem.\n\n## 3. Check vendored ownership\n\nBefore planning any edit, verify the file is editable locally:\n\n- `_vendor/` — read-only, vendored via Hugo modules\n- `data/cli/` — read-only, generated from upstream YAML\n- `content/reference/cli/` — read-only, generated from `data/cli/`\n- Everything else in `content/` — editable\n\nIf the fix requires upstream changes, identify the upstream repo and note\nit as out of scope. See the vendored content table in CLAUDE.md.\n\n## 4. Find related content\n\nLook for pages that may need updating alongside the primary fix:\n\n- Pages that link to the affected content\n- Include files (`content/includes/`) referenced by the page\n- Related pages in the same section describing the same feature\n\n## 5. Verify facts\n\nIf the issue makes a factual claim about how a feature behaves, verify it.\nFollow external links, read upstream source, check release notes. Do not\nplan a fix based on an unverified claim.\n\nIf the fix requires a replacement URL and that URL cannot be verified (e.g.\nnetwork restrictions), report it as a blocker rather than guessing.\n\n## 6. Check the live site (if needed)\n\nFor URL or rendering issues, fetch the live page:\n\n```\nhttps://docs.docker.com/<path>/\n```\n\n## 7. Report findings\n\nSummarize what you found — files to change, the specific problem in each,\nwhat the fix should be, and any constraints. This context feeds directly\ninto the write step.\n\nBe specific: name the file, the section or element within it, and the\nverified content needed. \"Fix the broken link in networking.md\" is not\nspecific enough. \"In `compose/networking.md`, the 'Custom networks' section,\nremove the note about `driver_opts` being ignored — this was fixed in\nCompose 2.24\" is.\n\n## Notes\n\n- Research quality bounds write quality. Vague research produces broad\n  changes; precise research produces minimal ones.\n- Do not create standalone research files — findings stay in conversation\n  context for the write step.\n",".agents/skills/review-changes/SKILL.md":"---\nname: review-changes\ndescription: >\n  Review uncommitted or recently committed documentation changes for\n  correctness, coherence, and style compliance. Use before creating a PR\n  to catch issues. \"review my changes\", \"review the diff\", \"check the fix\n  before submitting\", \"does this look right\".\ncontext: fork\nmodel: opus\n---\n\n# Review Changes\n\nEvaluate whether the changes correctly and completely solve the stated\nproblem, without introducing new issues. Start with no assumptions — the\nchange may contain mistakes. Your job is to catch what the writer missed,\nnot to rubber-stamp the diff.\n\n## 1. Identify what changed\n\nDetermine the scope of changes to review:\n\n```bash\n# Uncommitted changes\ngit diff --name-only\n\n# Last commit\ngit diff --name-only HEAD~1\n\n# Entire branch vs main\ngit diff --name-only main...HEAD\n```\n\nPick the right comparison for what's being reviewed. If reviewing a branch,\nuse `main...HEAD` to see all changes since the branch diverged.\n\n## 2. Read each changed file in full\n\nDo not just read the diff. For every changed file, read the entire file to\nunderstand the full context the change lives in. A diff can look correct in\nisolation but contradict something earlier on the same page.\n\nThen read the diff for the detailed changes:\n\n```bash\n# Adjust the comparison to match step 1\ngit diff --unified=10              # uncommitted\ngit diff --unified=10 HEAD~1       # last commit\ngit diff --unified=10 main...HEAD  # branch\n```\n\n## 3. Follow cross-references\n\nFor each changed file, check what links to it and what it links to:\n\n- Search for other pages that reference the changed content (grep for the\n  filename, heading anchors, or key phrases)\n- Read linked pages to verify the change doesn't create contradictions\n  across pages\n- Check that anchor links in cross-references still match heading IDs\n\nA change that's correct on its own page can break the story told by a\nrelated page.\n\n## 4. Verify factual accuracy\n\nDon't assume the change is factually correct just because it reads well.\n\n- If the change describes how a feature behaves, verify against upstream\n  docs or source code\n- If the change includes a URL, check that it resolves\n- If the change references a CLI flag, option, or API field, confirm it\n  exists\n\n## 5. Evaluate as a reader\n\nConsider someone landing on this page from a search result, with no prior\ncontext:\n\n- Does the page make sense on its own?\n- Is the changed section clear without having read the issue or diff?\n- Would a reader be confused by anything the change introduces or leaves\n  out?\n\n## 6. Review code and template changes\n\nFor non-Markdown changes (JS, HTML, CSS, Hugo templates):\n\n- Trace through the common execution path\n- Trace through at least one edge case (no stored preference, Alpine fails\n  to load, first visit vs returning visitor)\n- Ask whether the change could produce unexpected browser or runtime\n  behavior that no automated tool would catch\n\n## 7. Decision\n\n**Approve** if the change is correct, coherent, complete, and factually\naccurate.\n\n**Request changes** if:\n- The change does not correctly solve the stated problem\n- There is a factual error or contradiction (on-page or cross-page)\n- A cross-reference is broken or misleading\n- A reader would be confused\n\nWhen requesting changes, be specific: quote the exact text that is wrong,\nexplain why, and suggest the correct fix.\n",".agents/skills/review-pr/SKILL.md":"---\nname: review-pr\ndescription: >\n  Review one or more incoming Docker documentation pull requests as a\n  maintainer. Independently validate technical claims, assess editorial fit\n  and information architecture, choose a verdict, and draft exact inline or\n  PR-wide feedback behind a confirmation gate. Use for requests such as\n  \"review PR 123\", \"is this PR correct?\", \"does this information belong\n  here?\", \"validate this PR\", or \"help review backlog PRs\". Do not use to\n  maintain or fix a PR you own; use maintain-pr for that.\n---\n\n# Review PR\n\nReview incoming contributions for factual correctness and whether they make\nthe documentation better as a whole. Treat a technically true addition as\ninsufficient when it is misplaced, overemphasized, redundant, or unhelpful\nto the page's intended reader.\n\n## Preserve the write boundary\n\nPerform the review in two phases:\n\n1. Research the PR, decide a verdict, and present the exact proposed\n   comment or review text.\n2. Wait for explicit user confirmation, then post only the confirmed text.\n\nBefore confirmation, do not post comments, submit a GitHub review, approve or\nrequest changes, resolve threads, push commits, edit labels, or otherwise\nmutate GitHub. A request to review or draft feedback is not confirmation to\npost it. Ask `Post these comments?` and stop. Treat revisions to a draft as\nunconfirmed until the user explicitly asks to post them.\n\n## 1. Gather the full context\n\nFor each PR, inspect its metadata, body, commits, changed files, checks,\nconversation, reviews, and linked issues. Always fetch inline comments\nseparately because `gh pr view --json reviews` omits them.\n\n```bash\ngh pr view <PR> --repo docker/docs \\\n  --json number,title,url,state,author,body,baseRefName,headRefName,headRefOid,commits,files,comments,reviews,reviewDecision,statusCheckRollup\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, side, commit_id}]'\ngh pr diff <PR> --repo docker/docs\n```\n\nRead linked issues and relevant discussion. An issue is evidence that a\nreader was confused, but it does not establish the reporter's diagnosis or\njustify a new highlighted note by itself. Green CI establishes only that automated\nchecks passed, not that the content is correct.\n\nFetch the PR head when local inspection is useful. Compare it with the\ncanonical upstream base rather than assuming the local branch is fresh.\nRead each changed file in full, not only its diff.\n\n## 2. Research independently\n\nVerify every material claim against authoritative sources such as product\nsource code, upstream documentation, specifications, release notes, or safe\nlocal reproduction. Do not accept the PR description, issue diagnosis, or\nexisting review feedback as fact.\n\nSearch the documentation for related explanations and canonical pages. Read\n`STYLE.md`, `COMPONENTS.md`, and applicable repository instructions. Check\nwhether a changed file is generated or maintained upstream and identify the correct\nupstream repository instead of proposing a local edit.\n\nDistinguish among:\n\n- a wrong fact\n- a correct fact expressed inaccurately\n- a correct fact placed on the wrong page\n- content already explained elsewhere\n- a real discovery problem better addressed with a short signpost and link\n- a request that needs no documentation change.\n\nIf an external claim or replacement URL cannot be verified, report that\nlimitation instead of guessing.\n\n## 3. Assess editorial fit\n\nApply these questions to each addition:\n\n- Does it change a reader's decision or next action on this page?\n- Is this the canonical page for the concept?\n- Is the fact general, or specific to this page, feature, or component?\n- Is the information already documented elsewhere?\n- Would a concise local signpost to canonical coverage solve the discovery\n  problem better than duplicating the explanation?\n- Is the visual and textual weight proportional to the information's value?\n- Does it preserve the page's scope, flow, and character?\n\nPrefer one coherent explanation in the canonical location. Add local context\nonly when it helps the reader complete the task at hand. Avoid stray notes,\ncallouts, and exhaustive edge cases whose prominence exceeds their value.\n\n## 4. Choose a decisive verdict\n\nLead with one of these outcomes:\n\n- **Approve**: correct, useful, well placed, and ready to merge.\n- **Approve with optional polish**: ready to merge; suggestions are genuinely\n  non-blocking.\n- **Focused rewrite**: the underlying need is valid, but wording, scope,\n  placement, or structure should change before merge.\n- **Close / no docs change**: incorrect, redundant, out of scope, or not a\n  documentation problem.\n\nExplain the verdict with evidence. When wording is the issue, provide exact\nreplacement text rather than a vague request to improve it.\n\n## 5. Place feedback deliberately\n\nUse an inline comment when the finding is anchored to a narrow changed line\nor range and acting on it is local. Examples include an inaccurate sentence,\nan ambiguous option description, a broken link, or a precise wording\nreplacement.\n\nUse a PR-wide comment for scope, information architecture, overall approach,\nmultiple intertwined edits, or a proposed replacement section. Do not attach\nholistic feedback to an arbitrary line.\n\nUse both when appropriate: put the overall direction in the PR-wide comment\nand line-specific corrections inline. Do not repeat the same point in both.\nConsolidate related feedback so the author receives the fewest comments that\nremain clear and actionable.\n\nFor every proposed inline comment, resolve and display the current changed\nfile path and right-side diff line. If the target line is not part of the\ncurrent diff or cannot be identified reliably, use a PR-wide comment that\nquotes the target text instead. Never guess a line number.\n\nEnd comments posted on the user's behalf with an accurate agent-disclosure\nfooter, such as `Generated by Codex`.\n\n## 6. Present drafts and stop\n\nBefore any GitHub write, show the review in this form, omitting empty\nsections:\n\n```markdown\n## Verdict\n\nFocused rewrite\n\n## Findings\n\n- <finding and evidence>\n\n## Proposed inline comments\n\n1. `path/to/file.md:42`\n   > Exact comment text\n\n## Proposed PR-wide comment\n\n> Exact comment text\n\nPost these comments?\n```\n\nFor multiple PRs, give each PR its own verdict and comment set. Make the\nconfirmation scope unambiguous. Do not interpret approval of one PR's drafts\nas approval to post comments on the others.\n\n## 7. Post only confirmed feedback\n\nImmediately before posting, re-fetch the PR head SHA and diff. If either the\nhead or an inline target changed, stop and show the updated draft or\nplacement for confirmation.\n\nPost confirmed inline comments as a single comment-only review when\npractical. Use the current head SHA and right-side diff lines:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/reviews --method POST --input <payload>\n```\n\nThe JSON payload contains `commit_id`, `event: \"COMMENT\"`, and a `comments`\narray whose entries contain `path`, `line`, `side: \"RIGHT\"`, and `body`.\nSubmitting a review with `APPROVE` or `REQUEST_CHANGES` requires separate,\nexplicit user authorization; a verdict alone does not grant it.\n\nPost confirmed holistic feedback separately:\n\n```bash\ngh pr comment <PR> --repo docker/docs --body-file <file>\n```\n\nUse a safely created temporary file or API input so Markdown, backticks, and\nshell substitutions are preserved literally. Post exactly the confirmed\ntext. Verify the resulting review/comments and report their URLs and\nplacements. If GitHub rejects an inline location, do not silently fall back\nto a PR-wide comment; report the failure and prepare a revised placement for\nconfirmation.\n\n## Definition of done\n\n- Verify technical claims with authoritative evidence.\n- Evaluate usefulness, placement, duplication, and proportionality.\n- Give a decisive verdict and exact actionable wording.\n- Choose inline and PR-wide placement based on the feedback's scope.\n- Show every exact draft and target before any GitHub mutation.\n- Post only after explicit confirmation and verify what was posted.\n",".agents/skills/review-pr/agents/openai.yaml":"interface:\n  display_name: \"Review PR\"\n  short_description: \"Validate incoming documentation pull requests\"\n  default_prompt: \"Use $review-pr to validate this incoming documentation PR and draft maintainer feedback.\"\n",".agents/skills/testcontainers-guides-migrator/SKILL.md":"---\nname: testcontainers-guide-migrator\ndescription: >\n  Migrate a Testcontainers guide from testcontainers.com into the Docker docs site (docs.docker.com).\n  Converts AsciiDoc to Hugo Markdown, updates code to the latest Testcontainers API, splits into\n  chapters with stepper navigation, verifies code compiles and tests pass, and validates against\n  Docker docs style rules. Use when asked to migrate a testcontainers guide, add a TC guide, or\n  port content from testcontainers.com to Docker docs.\n---\n\n# Migrate a Testcontainers Guide\n\nYou are migrating guides from https://testcontainers.com/guides/ into the Docker docs Hugo site.\nEach guide lives in its own GitHub repo under `testcontainers/tc-guide-*`, written in AsciiDoc.\nThe source repos are listed in the testcontainers-site build.sh:\nhttps://github.com/testcontainers/testcontainers-site/blob/main/build.sh#L23-L45\n\n## Inputs\n\nThe user provides one or more guides to migrate. Resolve these from the inventory below:\n\n- **REPO_NAME**: GitHub repo (e.g. `tc-guide-getting-started-with-testcontainers-for-java`)\n- **SLUG**: guide slug inside `guide/` dir (e.g. `getting-started-with-testcontainers-for-java`)\n- **LANG**: language identifier (go, java, dotnet, nodejs, python)\n- **GUIDE_ID**: short kebab-case name (e.g. `getting-started`)\n\n## Guide inventory\n\nThese are the 21 guides from testcontainers.com/guides/ and their source repos:\n\n| # | Title | Repo | Lang | GUIDE_ID |\n|---|-------|------|------|----------|\n| 1 | Introduction to Testcontainers | tc-guide-introducing-testcontainers | (none) | introducing |\n| 2 | Getting started for Java | tc-guide-getting-started-with-testcontainers-for-java | java | getting-started |\n| 3 | Testing Spring Boot REST API | tc-guide-testing-spring-boot-rest-api | java | spring-boot-rest-api |\n| 4 | Testcontainers lifecycle (JUnit 5) | tc-guide-testcontainers-lifecycle | java | lifecycle |\n| 5 | Configuration of services in container | tc-guide-configuration-of-services-running-in-container | java | service-configuration |\n| 6 | Replace H2 with real database | tc-guide-replace-h2-with-real-database-for-testing | java | replace-h2 |\n| 7 | Testing ASP.NET Core web app | tc-guide-testing-aspnet-core | dotnet | aspnet-core |\n| 8 | Testing Spring Boot Kafka Listener | tc-guide-testing-spring-boot-kafka-listener | java | spring-boot-kafka |\n| 9 | REST API integrations with MockServer | tc-guide-testing-rest-api-integrations-using-mockserver | java | mockserver |\n| 10 | Getting started for .NET | tc-guide-getting-started-with-testcontainers-for-dotnet | dotnet | getting-started |\n| 11 | AWS integrations with LocalStack | tc-guide-testing-aws-service-integrations-using-localstack | java | aws-localstack |\n| 12 | Testcontainers in Quarkus apps | tc-guide-testcontainers-in-quarkus-applications | java | quarkus |\n| 13 | Getting started for Go | tc-guide-getting-started-with-testcontainers-for-go | go | getting-started |\n| 14 | jOOQ and Flyway with Testcontainers | tc-guide-working-with-jooq-flyway-using-testcontainers | java | jooq-flyway |\n| 15 | Getting started for Node.js | tc-guide-getting-started-with-testcontainers-for-nodejs | nodejs | getting-started |\n| 16 | REST API integrations with WireMock | tc-guide-testing-rest-api-integrations-using-wiremock | java | wiremock |\n| 17 | Local dev with Testcontainers Desktop | tc-guide-simple-local-development-with-testcontainers-desktop | java | local-dev-desktop |\n| 18 | Micronaut REST API with WireMock | tc-guide-testing-rest-api-integrations-in-micronaut-apps-using-wiremock | java | micronaut-wiremock |\n| 19 | Micronaut Kafka Listener | tc-guide-testing-micronaut-kafka-listener | java | micronaut-kafka |\n| 20 | Getting started for Python | tc-guide-getting-started-with-testcontainers-for-python | python | getting-started |\n| 21 | Keycloak with Spring Boot | tc-guide-securing-spring-boot-microservice-using-keycloak-and-testcontainers | java | keycloak-spring-boot |\n\nAlready migrated: **#2 (Java getting-started)**, **#13 (Go getting-started)**, **#20 (Python getting-started)**\n\n## Step 0: Pre-flight\n\n1. Confirm `testing-with-docker` tag exists in `data/tags.yaml`. If not, add:\n   ```yaml\n   testing-with-docker:\n     title: Testing with Docker\n   ```\n2. Check if new terms need adding to `_vale/config/vocabularies/Docker/accept.txt`.\n3. Read `STYLE.md` and `COMPONENTS.md` to refresh on Docker docs conventions.\n\n## Step 1: Clone the guide repo\n\nClone the guide repo to a temporary directory. This gives you all source files locally — no HTTP calls needed.\n\n```bash\ngit clone --depth 1 https://github.com/testcontainers/{REPO_NAME}.git <tmpdir>/{REPO_NAME}\n```\n\nWhere `<tmpdir>` is a temporary directory on your system (e.g. the output of `mktemp -d`).\n\nThe repo structure is:\n- `<tmpdir>/{REPO_NAME}/guide/{SLUG}/index.adoc` — the AsciiDoc guide source\n- `<tmpdir>/{REPO_NAME}/src/` — application source code (referenced by `include::` directives)\n- `<tmpdir>/{REPO_NAME}/testdata/` — test data files (SQL scripts, configs, etc.)\n- `<tmpdir>/{REPO_NAME}/pom.xml` or `go.mod` — build config\n\n1. Read `guide/{SLUG}/index.adoc` to get the guide content.\n2. Find all `include::{codebase}/path/to/file[]` directives. The `{codebase}` attribute points to a remote URL, but since you have the repo cloned, read the files directly from disk instead (e.g. `include::{codebase}/src/main/java/Foo.java[]` → read `<tmpdir>/{REPO_NAME}/src/main/java/Foo.java`).\n3. If includes have `[lines=\"X..Y\"]`, extract only those lines from the local file.\n4. Note the `[source,lang]` block preceding each include — that determines the code fence language.\n\nThis cloned repo also serves as the base for Step 6 (code verification) — you can run the tests directly in it to confirm they pass before updating the code to the latest API.\n\n## Step 2: Convert AsciiDoc to Markdown\n\n| AsciiDoc | Markdown |\n|---|---|\n| `== Heading` | `## Heading` |\n| `=== Heading` | `### Heading` |\n| `*bold*` (AsciiDoc bold) | `**bold**` |\n| `https://url[Link text]` | `[Link text](url)` |\n| `[source,lang]\\n----\\ncode\\n----` | `` ```lang\\ncode\\n``` `` |\n| `[source,shell]` with `$` prompts | `` ```console `` |\n| `[NOTE]\\ntext` or `====\\n[NOTE]\\n...\\n====` | `> [!NOTE]\\n> text` |\n| `[TIP]\\ntext` | `> [!TIP]\\n> text` |\n| `:toc:`, `:toclevels:`, `:codebase:` | Remove entirely |\n| `include::{codebase}/path[]` | Replace with fetched code in a code fence |\n| YAML front matter (date, draft, repo) | Remove; transform to Docker docs format |\n\n## Step 3: Apply Docker docs style rules\n\nThese are mandatory (from STYLE.md and AGENTS.md):\n\n- **No \"we\"**: \"We are going to create\" → \"Create\" or \"Start by creating\"\n- **No \"let us\" / \"let's\"**: → imperative voice or \"You can...\"\n- **No hedge words**: remove \"simply\", \"easily\", \"just\", \"seamlessly\"\n- **No meta-commentary**: remove \"it's worth noting\", \"it's important to understand\"\n- **No \"allows you to\" / \"enables you to\"**: → \"lets you\" or rephrase\n- **No \"click\"**: → \"select\"\n- **No bold for emphasis or product names**: only bold UI elements\n- **No time-relative language**: remove \"currently\", \"new\", \"recently\", \"now\"\n- **No exclamations**: remove \"Voila!!!\" etc.\n- Use `console` language hint for interactive shell blocks with `$` prompts\n- Use contractions: \"it's\", \"you're\", \"don't\"\n\n## Step 4: Update code to latest Testcontainers API\n\nResearch the latest API version for the target language before writing code.\n\n**Best practices reference**: The Testcontainers team maintains Claude skills with up-to-date API patterns and best practices for each language at https://github.com/testcontainers/claude-skills/ — check the relevant language skill (testcontainers-go, testcontainers-node, testcontainers-dotnet) for current API signatures, cleanup patterns, wait strategies, and anti-patterns to avoid.\n\nFor each language, check the cloned repo's existing code, then update to the latest API. Key patterns per language:\n\n**Go** (testcontainers-go v0.41.0):\n- `postgres.RunContainer(ctx, opts...)` → `postgres.Run(ctx, \"image\", opts...)`\n- `testcontainers.WithImage(...)` → image is now the 2nd positional param to `Run()`\n- Manual `WithWaitStrategy(wait.ForLog(...))` → `postgres.BasicWaitStrategies()`\n- `t.Cleanup(func() { ctr.Terminate(ctx) })` → `testcontainers.CleanupContainer(t, ctr)`\n- `if err != nil { log.Fatal(err) }` → `require.NoError(t, err)` (use testify require/assert)\n- Helper functions should accept `t *testing.T` as first param, call `t.Helper()`\n- No `TearDownSuite()` needed if `CleanupContainer` is registered in the helper\n- Go version prerequisite: 1.25+\n\n**Java** (testcontainers-java 2.0.4):\n- Artifacts renamed in 2.x: `org.testcontainers:postgresql` → `org.testcontainers:testcontainers-postgresql`\n- Check the latest version at https://java.testcontainers.org/\n- Use `@Testcontainers` and `@Container` annotations for JUnit 5 lifecycle\n- Prefer module-specific containers (e.g. `PostgreSQLContainer`) over `GenericContainer`\n- Use `@DynamicPropertySource` for Spring Boot integration\n\n**.NET** (testcontainers-dotnet):\n- Check the latest NuGet package version\n- Use `IAsyncLifetime` for container lifecycle in xUnit\n- Use builder pattern: `new PostgreSqlBuilder().Build()`\n\n**Node.js** (testcontainers-node):\n- Check the latest npm version\n- Use module-specific packages (e.g. `@testcontainers/postgresql`)\n- Use `GenericContainer` for services without a dedicated module\n\n**Python** (testcontainers-python):\n- Check the latest PyPI version\n- Use context managers (`with PostgresContainer() as postgres:`)\n- Use module-specific containers when available\n\nFor all languages: consult the corresponding Testcontainers skill at https://github.com/testcontainers/claude-skills/ for current best practices and anti-patterns.\n\n## Step 5: Create guide directory structure\n\nDirectory: `content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n\nEach guide is its own top-level entry under `/guides/`. Do NOT nest guides inside a shared parent section — otherwise they won't appear individually in the tag/language filters on the guides listing page.\n\n### _index.md (landing page)\n\n```yaml\n---\ntitle: {Full guide title}\nlinkTitle: {Short title for guides listing}\ndescription: {One-line description}\nkeywords: testcontainers, {lang}, testing, {technologies used}\nsummary: |\n  {2-3 line summary for the guides listing card}\ntoc_min: 1\ntoc_max: 2\ntags: [testing-with-docker]\nlanguages: [{lang}]\nparams:\n  time: {estimated} minutes\n---\n\n<!-- Source: https://github.com/testcontainers/{REPO_NAME} -->\n```\n\nContent: what you'll learn (bulleted list), prerequisites, and a NOTE linking to `https://testcontainers.com/getting-started/` for newcomers.\n\n### Sub-pages (chapters)\n\nSplit the guide into logical chapters. Each sub-page:\n\n```yaml\n---\ntitle: {Chapter title}\nlinkTitle: {Short title for stepper}\ndescription: {One-line description}\nweight: {10, 20, 30, ...}\n---\n```\n\n**No `tags`, `languages`, or `params` on sub-pages** — only on `_index.md`.\n\nTypical chapter breakdown:\n| Weight | File | Content |\n|--------|------|---------|\n| 10 | `create-project.md` | Project setup, dependencies, business logic |\n| 20 | `write-tests.md` | First test using testcontainers |\n| 30 | `test-suites.md` | Reusing containers, test helpers, suites |\n| 40 | `run-tests.md` | Running tests, summary, further reading |\n\nAdapt the split to the guide's content — some guides may need fewer or more chapters.\n\n## Step 6: Verify code compiles and tests pass\n\nThis is CRITICAL. The code in the guide MUST compile and all tests MUST pass. Do not skip this step.\n\n### 6a: Use the cloned repo as the verification project\n\nThe repo you cloned in Step 1 (`<tmpdir>/{REPO_NAME}`) already contains a working project with all source files, build config, and tests. Use it as the starting point:\n\n```bash\ncd <tmpdir>/{REPO_NAME}\n```\n\nFirst, verify the **original** code compiles and tests pass before you change anything. This confirms a good baseline.\n\n### 6b: Update the code in the cloned repo\n\nAfter confirming the original works, apply the API updates (from Step 4) directly in the cloned repo's source files. This is the same code you're putting in the guide — keep them in sync.\n\n### 6c: Update dependencies and compile\n\nRun compilation inside a container for reproducibility — no need to install the language toolchain on the host. Use the appropriate language Docker image, mounting the cloned repo:\n\n```bash\ndocker run --rm -v \"<tmpdir>/{REPO_NAME}\":/app -w /app <language-image> sh -c \"<compile command>\"\n```\n\nPick the right image for the language (e.g. `golang:1.25-alpine`, `maven:3-eclipse-temurin-21`, `gradle:jdk21`, `mcr.microsoft.com/dotnet/sdk:9.0`, `node:22-alpine`, `python:3.13-alpine`). Update dependencies to the latest Testcontainers version and compile.\n\nIf compilation fails, fix the code and update the guide markdown to match.\n\n### 6d: Run tests in a container with Docker socket mounted\n\nRun tests in the same kind of container, but **mount the Docker socket** so Testcontainers can create sibling containers.\n\n#### macOS Docker Desktop workarounds\n\nWhen running on macOS with Docker Desktop, these environment variables and flags are **required**:\n\n- **`TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`** — On macOS, containers can't reach sibling containers via the Docker bridge IP (`172.17.0.x`). This tells Testcontainers (including Ryuk) to connect via `host.docker.internal` instead. **Do NOT disable Ryuk** — it is a core Testcontainers feature and the guides must demonstrate proper usage.\n- **`docker-java.properties`** with `api.version=1.47` — Docker Desktop's minimum API version is 1.44, but docker-java defaults to 1.24. Create this file in the project root and mount it to `/root/.docker-java.properties` inside Java containers.\n- **`-Dspotless.check.skip=true`** — The Spotless Maven plugin in the source repos is incompatible with JDK 21. Skip it since it's a code formatter, not part of the test.\n- **`-Dmicronaut.test.resources.enabled=false`** — Micronaut's Test Resources service starts a separate process that can't connect to Docker from inside a container. The guide tests use Testcontainers directly, not Test Resources. Only needed for Micronaut guides.\n#### Java guide test command\n\n```bash\n# Create docker-java.properties in the project root\necho \"api.version=1.47\" > <tmpdir>/{REPO_NAME}/docker-java.properties\n\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v \"<tmpdir>/{REPO_NAME}/docker-java.properties\":/root/.docker-java.properties \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  maven:3.9-eclipse-temurin-21 \\\n  mvn -B test -Dspotless.check.skip=true -Dspotless.apply.skip=true\n```\n\nFor Quarkus guides, use `maven:3.9-eclipse-temurin-17` instead (Quarkus 3.22.3 compiles for Java 17).\n\n#### Go guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  golang:1.25-alpine \\\n  sh -c \"apk add --no-cache gcc musl-dev && go test -v -count=1 ./...\"\n```\n\n#### Python guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  python:3.13-slim \\\n  sh -c \"pip install -r requirements.txt && python -m pytest\"\n```\n\n#### .NET guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  mcr.microsoft.com/dotnet/sdk:9.0 \\\n  dotnet test\n```\n\n#### Node.js guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  node:22-alpine \\\n  sh -c \"npm install && npm test\"\n```\n\n#### Important: run tests sequentially\n\nRun guide tests **one at a time**. Running multiple concurrent DinD or sibling-container tests can overwhelm Docker Desktop's containerd store and cause `meta.db: input/output error` corruption, requiring a Docker Desktop restart.\n\n### 6e: Fix until green\n\nIf any test fails, debug and fix the code in both the temporary project AND the guide markdown. Re-run until all tests pass. Do not proceed until verified.\n\n## Step 7: Update cross-references\n\n1. **`content/manuals/testcontainers.md`**: Add a bullet under the `## Guides` section:\n   ```markdown\n   - [Guide title](/guides/testcontainers-{LANG}-{GUIDE_ID}/)\n   ```\n2. **Do NOT update** `content/guides/testcontainers-cloud/_index.md` — keep its external links.\n3. Link to `https://testcontainers.com/getting-started/` for the Testcontainers overview.\n4. Use internal paths for already-migrated guides; keep `testcontainers.com` links for unmigrated ones.\n\n## Step 8: Validate\n\n**IMPORTANT**: Run ALL validation locally before committing. Vale checks run on CI and will block the PR if they fail — fixing after push wastes CI cycles and review time.\n\n1. `npx --no-install rumdl fmt content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n2. `npx --no-install rumdl fmt content/manuals/testcontainers.md`\n3. `docker buildx bake lint` — must pass with no errors\n4. `docker buildx bake vale` — then check for errors in the new files:\n   ```bash\n   grep -A2 \"testcontainers-{LANG}-{GUIDE_ID}\" tmp/vale.out\n   ```\n   Fix ALL errors before proceeding. Common issues:\n   - **Vale.Spelling**: tech terms (library names, tools) not in the dictionary → add to `_vale/config/vocabularies/Docker/accept.txt` (alphabetical order)\n   - **Vale.Terms**: wrong casing (e.g. \"python\" → \"Python\") → fix in the markdown. Watch for package names like `testcontainers-python` triggering false positives — rephrase to \"Testcontainers for Python\" in prose.\n   - **Docker.Avoid**: hedge words like \"very\", \"simply\" → reword\n   - **Docker.We**: first-person plural → rewrite to \"you\" or imperative\n   - Info-level suggestions (e.g. \"VS Code\" → \"versus\") are not blocking but review them\n\n   Re-run `docker buildx bake vale` after fixes until no errors remain in the new files.\n5. Verify in local dev server (`HUGO_PORT=1314 docker compose watch`):\n   - Guide appears when filtering by its language\n   - Guide appears when filtering by `Testing with Docker` tag\n   - Stepper navigation works across chapters\n   - All links resolve (no 404s)\n6. Verify all external URLs return 200:\n   ```bash\n   curl -s -o /dev/null -w \"%{http_code}\" -L \"{url}\"\n   ```\n\n## Step 9: Commit\n\nOne commit per guide. Message format:\n```\nfeat(guides): add testcontainers {lang} {guide-id} guide\n\nMigrated from https://github.com/testcontainers/{REPO_NAME}\nUpdated to testcontainers-{lang} v{version} API.\n```\n\n## Special cases\n\n- **introducing-testcontainers**: Language-agnostic, conceptual. May overlap with `content/manuals/testcontainers.md`. Review for deduplication before migrating.\n- **local-dev-testcontainers-desktop**: About Testcontainers Desktop (now part of Docker Desktop). May need significant rewriting rather than mechanical migration.\n- **Java guides**: Many share the same language. Each still gets its own `testcontainers-java-{GUIDE_ID}` directory.\n\n## Reference: completed migration (Go getting-started)\n\nUse `content/guides/testcontainers-go-getting-started/` as the reference implementation:\n- `_index.md` — landing page with frontmatter, prerequisites, learning objectives\n- `create-project.md` (weight: 10) — project setup and business logic\n- `write-tests.md` (weight: 20) — first test with testcontainers-go\n- `test-suites.md` (weight: 30) — container reuse with testify suites\n- `run-tests.md` (weight: 40) — running tests, summary, further reading\n",".agents/skills/triage-issue/SKILL.md":"---\nname: triage-issue\ndescription: >\n  Analyze a single GitHub issue for docker/docs — check whether the problem\n  still exists, determine a verdict, and report findings. Use when asked to\n  triage, assess, or review an issue, even if the user doesn't say \"triage\"\n  explicitly: \"triage issue 1234\", \"is issue 500 still valid\", \"should we\n  close #200\", \"look at this issue\", \"what's going on with #200\".\nargument-hint: \"<issue-number>\"\ncontext: fork\n---\n\n# Triage Issue\n\nGiven GitHub issue **$ARGUMENTS** from docker/docs, figure out whether\nit's still a real problem and say what should happen next.\n\n## 1. Fetch the issue\n\n```bash\ngh issue view $ARGUMENTS --repo docker/docs \\\n  --json number,title,body,state,labels,createdAt,updatedAt,closedAt,assignees,author,comments\n```\n\n## 2. Understand the problem\n\nRead the issue body and all comments. Identify:\n\n- What is the reported problem?\n- What content, URL, or file does it reference?\n- Has anyone already proposed a fix or workaround in the comments?\n\nCheck for linked PRs in the issue timeline, not only in the issue body or\ncomments:\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/timeline --paginate \\\n  --jq '.[] | select(.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") | {event, created_at, source: .source.issue.html_url, title: .source.issue.title, state: .source.issue.state}'\n```\n\nIf an open PR already addresses the issue, don't open another PR. Review the\nexisting PR instead, and report that the issue already has an associated PR. A\nmerged PR is strong evidence the issue is fixed. A closed-without-merge PR means\nthe issue is likely still open.\n\n## 3. Follow URLs\n\nFind all `docs.docker.com` URLs in the issue body and comments. For each:\n\n- Fetch the URL to check if it still exists (404 = content removed or moved)\n- Check whether the content still contains the problem described\n- Note when the page was last updated relative to when the issue was filed\n\nFor non-docs URLs (GitHub links, external references), fetch them too if\nthey are central to understanding the issue.\n\n## 4. Check the repository\n\nIf the issue references specific files, content sections, or code:\n\n- Find and read the current version of that content\n- Check whether the problem has been fixed, content moved, or file removed\n- Remember the `/manuals` prefix mapping when looking up files\n\n## 5. Check for upstream ownership\n\nIf the issue is about content in `_vendor/` or `data/cli/`, it cannot be\nfixed here. Identify which upstream repo owns it (see the vendored content\ntable in CLAUDE.md).\n\n## 6. Decide and act\n\nAfter investigating, pick one of these verdicts and take the corresponding\naction on the issue:\n\n- **Close it** — the problem is already fixed, the content no longer exists,\n  or the issue is too outdated to be useful. Close the issue with a comment\n  explaining why:\n\n  ```bash\n  gh issue close $ARGUMENTS --repo docker/docs \\\n    --comment \"Closing: <one-sentence reason>\"\n  ```\n\n- **Fix it** — the problem is real and fixable in this repo. Name the\n  file(s) and what needs to change. Label the issue `status/confirmed` and\n  remove `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/confirmed'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Escalate upstream** — the problem is real but lives in vendored content.\n  Name the upstream repo. Label the issue `status/upstream` and remove\n  `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/upstream'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Leave it open** — you can't determine the current state, or the issue\n  needs human judgment. Label the issue `status/needs-analysis`:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/needs-analysis'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\nDon't overthink the classification. An old issue isn't stale if the problem\nstill exists. An upstream issue is still valid — it's just not fixable here.\n\nAlso apply the most relevant `area/` label based on the content affected.\nAvailable area labels: `area/accounts`, `area/admin`, `area/ai`,\n`area/api`, `area/billing`, `area/build`, `area/build-cloud`, `area/cli`,\n`area/compose`, `area/compose-spec`, `area/config`, `area/contrib`,\n`area/copilot`, `area/desktop`, `area/dhi`, `area/engine`,\n`area/enterprise`, `area/extensions`, `area/get-started`, `area/guides`,\n`area/hub`, `area/install`, `area/networking`, `area/offload`,\n`area/release-notes`, `area/samples`, `area/scout`, `area/security`,\n`area/storage`, `area/subscription`, `area/swarm`, `area/ux`. Pick one\n(or at most two if the issue clearly spans areas). Skip if none fit.\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n  --method POST --field 'labels[]=area/<name>'\n```\n\n## 7. Report\n\nWrite a short summary: what the issue reports, what you found, and what\nshould happen next. Reference the specific files, URLs, or PRs that support\nyour conclusion. Skip metadata fields — the issue itself has the dates and\nlabels. Mention the action you took (closed, labeled, etc.).\n\n## Notes\n\n- Always check timeline cross-references before deciding to fix an issue\n- Do not narrate your process — produce the final report\n- End every issue comment with an accurate agent-disclosure footer that names\n  the active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n.\n",".agents/skills/write/SKILL.md":"---\nname: write\ndescription: >\n  Write a documentation fix on a branch. Makes the minimal change, formats,\n  self-reviews, and commits. Use after research has identified what to change.\n  \"write the fix\", \"make the changes\", \"implement the fix for #1234\".\nhooks:\n  PostToolUse:\n    - matcher: \"Edit|Write\"\n      hooks:\n        - type: command\n          command: \"bash ${CLAUDE_SKILL_DIR}/scripts/post-edit.sh\"\n---\n\n# Write\n\nMake the minimal change that resolves the issue. Research has already\nidentified what to change — this skill handles the edit, formatting,\nself-review, and commit.\n\n## 1. Create a branch\n\n```bash\ngit checkout -b fix/issue-<number>-<short-desc> main\n```\n\nUse a short kebab-case description derived from the issue title (3-5 words).\n\n## 2. Read then edit\n\nAlways read each file before modifying it. Make the minimal change that\nfixes the issue. Do not improve surrounding content, add comments, or\naddress adjacent problems.\n\nFollow the writing guidelines in CLAUDE.md, STYLE.md, and COMPONENTS.md.\n\n## 3. Front matter check\n\nEvery content page requires `title`, `description`, and `keywords` in its\nfront matter. If any are missing from a file you touch, add them.\n\n## 4. Validate\n\nrumdl runs automatically after each edit via the PostToolUse hook.\nRun lint manually after all edits are complete:\n\n```bash\nscripts/lint.sh <changed-files>\n```\n\nThe lint script runs rumdl and Vale on only the files you pass it,\nso the output is scoped to your changes. Fix any errors it reports.\n\n## 5. Self-review\n\nRe-read each changed file: right file, right lines, change is complete,\nfront matter is present. Run `git diff` and verify only intended changes\nare present.\n\n## 6. Commit\n\nStage only the changed files:\n\n```bash\ngit add <files>\ngit diff --cached --name-only  # verify — no package-lock.json or other noise\ngit commit -m \"$(cat <<'EOF'\ndocs: <short description under 72 chars> (fixes #NNNN)\n\n<What was wrong: one sentence citing the specific problem.>\n<What was changed: one sentence describing the exact edit.>\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\"\n```\n\nThe commit body is mandatory. A reviewer reading only the commit should\nunderstand the problem and the fix without opening the issue.\n\n## Notes\n\n- Never edit `_vendor/` or `data/cli/` — these are vendored\n- If a file doesn't exist, check for renames:\n  `git log --all --full-history -- \"**/filename.md\"`\n- If the fix requires a URL that cannot be verified, stop and report a\n  blocker rather than guessing\n","AGENTS.md":"# AGENTS.md\n\nInstructions for AI agents working on Docker documentation.\nThis site builds https://docs.docker.com/ using Hugo.\n\n## Project structure\n\n```text\ncontent/          # Documentation source (Markdown + Hugo front matter)\n├── manuals/      # Product docs (Engine, Desktop, Hub, etc.)\n├── guides/       # Task-oriented guides\n├── reference/    # API and CLI reference\n└── includes/     # Reusable snippets\nlayouts/          # Hugo templates and shortcodes\ndata/             # YAML data files (CLI reference, etc.)\nassets/           # CSS (Tailwind v4) and JS (Alpine.js)\nstatic/           # Images, fonts\n_vendor/          # Vendored Hugo modules (read-only)\n```\n\n## URL prefix stripping\n\nThe `/manuals` prefix is stripped from published URLs:\n`content/manuals/desktop/install.md` becomes `/desktop/install/` on the live\nsite.\n\nWhen writing internal cross-references in source files, keep the `/manuals/`\nprefix in the path — Hugo requires the full source path. The stripping only\naffects the published URL, not the internal link target. Anchor links must\nexactly match the generated heading ID (Hugo lowercases and slugifies\nheadings).\n\n## Vendored content (do not edit)\n\nContent in `_vendor/` and CLI reference data in `data/cli/` are vendored\nfrom upstream repos. Content pages under `content/reference/cli/` are\ngenerated from `data/cli/` YAML. Do not edit any of these files — changes\nmust go to the source repository:\n\n| Content | Source repo |\n|---------|-------------|\n| CLI reference (`docker`, `docker build`, etc.) | docker/cli |\n| Buildx reference | docker/buildx |\n| Compose reference | docker/compose |\n| Model Runner reference | docker/model-runner |\n| Dockerfile reference | moby/buildkit |\n| Engine API reference | moby/moby |\n| AI Governance API (`content/reference/api/ai-governance/api.yaml`) | docker/governor-services (private) |\n\nIf a validation failure or broken link traces back to vendored content, note\nthe upstream repo that needs fixing. Do not attempt to fix it locally.\n\n`content/reference/api/ai-governance/api.yaml` is a verbatim copy of the\nupstream `openapi.yaml` — do not edit it by hand. Re-vendor it with\n`hack/sync-governance-api.sh`, which fetches the latest spec from the private\n`docker/governor-services` repo (using your own `gh` auth).\n\n## Writing guidelines\n\nRead and follow [STYLE.md](STYLE.md) and [COMPONENTS.md](COMPONENTS.md).\nThese contain all style rules, shortcode syntax, and front matter requirements.\n\n### Style violations to avoid\n\nEvery piece of writing must avoid these words and patterns (enforced by Vale):\n\n- Hedge words: \"simply\", \"easily\", \"just\", \"seamlessly\"\n- Meta-commentary: \"it's worth noting\", \"it's important to understand\"\n- \"allows you to\" or \"enables you to\" — use \"lets you\" or rephrase\n- \"we\" — use \"you\" or \"Docker\"\n- \"click\" — use \"select\"\n- Bold for emphasis or product names — only bold UI elements\n- Time-relative language: \"currently\", \"new\", \"recently\", \"now\"\n\n### Version-introduction notes\n\nExplicit version anchors (\"Starting with Docker Desktop version X...\") are\ndifferent from time-relative language — they mark when a feature was\nintroduced, which is permanently true.\n\n- Recent releases (~6 months): leave version callouts in place\n- Old releases: consider removing if the callout adds little value\n- When in doubt, keep the callout and flag for maintainer review\n\n### Vale gotchas\n\n- Use lowercase \"config\" in prose — `vale.Terms` flags a capital-C \"Config\"\n\n### Updating the vocabulary\n\nIf Vale flags a legitimate tech term, product name, or compound identifier\nas a misspelling, add it to `_vale/config/vocabularies/Docker/accept.txt`.\nThis is optional — only update when a real new term is missing, not to\nsilence individual violations.\n\n- Use the canonical form for case-sensitive product names (`PyTorch`,\n  `GitHub`, `Kubernetes`, `BuildKit`). `Vale.Terms` enforces that exact\n  case across the docs.\n- Use `[Aa]bcd` character-class regex for words that legitimately appear\n  in multiple cases (e.g., sentence-starting capitalization, or a name\n  that's also a generic noun). This covers spelling without enforcing\n  a single canonical form.\n- Avoid broad regex patterns — entries that match many words at once\n  (especially with `(?i)`) suppress other rule checks on every match.\n- Don't add a wrong-cased entry to silence one false positive — it\n  cascades into `Vale.Terms` violations on every correct usage.\n\n## Alpine.js patterns\n\nDo not combine Alpine's `x-show` with the HTML `hidden` attribute on the\nsame element. `x-show` toggles inline `display` styles, but `hidden` applies\n`display: none` via the user-agent stylesheet — the element stays hidden\nregardless of `x-show` state. Use `x-cloak` for pre-Alpine hiding instead.\nThe site defines `[x-cloak=\"\"] { display: none !important }` in `global.css`.\n\n## Front matter requirements\n\nEvery content page under `content/` requires:\n\n- `title:` — page title\n- `description:` — short description for SEO/previews\n- `keywords:` — list of search keywords\n\nAdditional common fields:\n\n- `linkTitle:` — sidebar label (keep under 30 chars)\n- `weight:` — ordering within a section\n\n## Hugo shortcodes\n\nShortcodes are defined in `layouts/shortcodes/`. Syntax reference is in\nCOMPONENTS.md. Wrong shortcode syntax fails silently during build but\nproduces broken HTML — always check COMPONENTS.md for correct syntax.\n\n## Commands\n\n```sh\nnpx --no-install rumdl fmt <file>  # Format Markdown before committing\nnpx prettier --write <file>        # Format non-Markdown files\nscripts/lint.sh <file>...          # Lint specific files (rumdl + Vale)\ndocker buildx bake validate        # Run all validation checks\ndocker buildx bake lint            # Markdown linting only\ndocker buildx bake vale            # Style guide checks only\ndocker buildx bake test            # HTML and link checking\n```\n\nFor incremental work, prefer `scripts/lint.sh` over the `bake` targets —\nit runs the same checks on just the files you pass, so the output stays\nscoped to your changes instead of the whole repo.\n\n### Validation in git worktrees\n\n`docker buildx bake validate` fails in git worktrees because Hugo cannot\nresolve the worktree path. Use `lint` and `vale` targets separately instead.\nNever modify `hugo.yaml` to work around this. The `test`, `path-warnings`,\nand `validate-vendor` targets run correctly in CI.\n\n## Verification loop\n\n1. Make changes\n2. Format Markdown with rumdl: `npx --no-install rumdl fmt <file>`\n3. Lint the changed files: `scripts/lint.sh <file>...`\n4. Run a full build with `docker buildx bake` (optional for small changes)\n\nAlways lint the specific files you changed before committing. Use\n`scripts/lint.sh` rather than the `bake` targets so the output is scoped\nto your changes — bake runs across the entire repo and the noise makes\nreal issues easy to miss.\n\n## Git hygiene\n\n- **Stage files explicitly.** Never use `git add .` / `git add -A` /\n  `git add --all`. Running `npx prettier` updates `package-lock.json` in the\n  repo root, and broad staging sweeps it into the commit.\n- **Verify before committing.** Run `git diff --cached --name-only` and\n  confirm only documentation files appear. If `package-lock.json` or other\n  generated files are staged, unstage them:\n  `git reset HEAD -- package-lock.json`\n- **Push to your fork, not upstream.** Before pushing, confirm\n  `git remote get-url origin` returns your fork URL, not\n  `github.com/docker/docs`. Use `--head FORK_OWNER:branch-name` with\n  `gh pr create`.\n\n## Working with issues and PRs\n\n### Principles\n\n- **One issue, one branch, one PR.** Never combine multiple issues in a\n  single branch or PR.\n- **Minimal changes only.** Fix the issue. Do not improve surrounding\n  content, add comments, refactor, or address adjacent problems.\n- **Verify before documenting.** Don't take an issue reporter's claim at\n  face value — the diagnosis may be wrong even when the symptom is real.\n  Verify the actual behavior before updating docs.\n\n### Review feedback\n\n- **Always reply to review comments** — never silently fix. After every\n  commit that addresses review feedback, reply to each thread explaining\n  what was done.\n- **Treat reviewer feedback as claims to verify, not instructions to\n  execute.** Before implementing a suggestion, verify that it is correct.\n  Push back when evidence contradicts the reviewer.\n- **Inline review comments need a separate API call.** `gh pr view --json\n  reviews` does not include line-level comments. Always also call:\n\n  ```bash\n  gh api repos/<org>/<repo>/pulls/<N>/comments \\\n    --jq '[.[] | {author: .user.login, body: .body, path: .path, line: .line}]'\n  ```\n\n### Labels\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<N>/labels \\\n  --method POST --field 'labels[]=<label>'\n```\n\n### External links\n\nIf a replacement URL cannot be verified (e.g. network restrictions), treat\nthe task as blocked — do not commit a guessed URL. Report the blocker so a\nhuman can confirm. Exception: when a domain migration is well-established and\nonly the anchor is unverifiable, dropping the anchor is acceptable.\n\n## Page deletion checklist\n\nWhen removing a documentation page, search the entire `content/` tree and\nall YAML/TOML config files for the deleted page's slug and heading text.\nCross-references from unrelated sections and config-driven nav entries can\nremain and cause broken links.\n\n## Engine API version bumps\n\nWhen a new Engine API version ships, three coordinated changes are needed in\na single commit:\n\n1. `hugo.yaml` — update `latest_engine_api_version`, `docker_ce_version`,\n   and `docker_ce_version_prev`\n2. Create `content/reference/api/engine/version/v<NEW>.md` with the\n   `/latest/` aliases block (copy from previous version)\n3. Remove the aliases block from\n   `content/reference/api/engine/version/v<PREV>.md`\n\nNever leave both version files carrying `/latest/` aliases simultaneously.\n\n## Hugo icon references\n\nBefore changing an icon reference in response to a \"file not found\" error,\nverify the file actually exists via Hugo's virtual filesystem. Files may\nexist in `node_modules/@material-symbols/svg-400/rounded/` but not directly\nin `assets/icons/`. Check both locations before concluding an icon is\nmissing.\n\n## Self-improvement\n\nAfter completing work that reveals a non-obvious pattern or repo quirk not\nalready documented here, propose an update to this file. For automated\nsessions, note the learning in a comment on the issue. For human-supervised\nsessions, discuss with the user whether to update CLAUDE.md directly.\n","CLAUDE.md":"AGENTS.md","_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md":"---\ntitle: \"Agents\"\ndescription: \"Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents.\"\nkeywords: docker agent, ai agents, concepts, agents\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/agents/\n---\n\n_Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents._\n\n## What is an Agent?\n\nAn agent in Docker Agent is defined by:\n\n- **Model** — The AI model powering it (e.g., Claude, GPT-5, Gemini). See [Models](../models/index.md).\n- **Description** — A brief summary of what the agent does (used by other agents for delegation)\n- **Instruction** — The system prompt that defines the agent's behavior and personality\n- **Tools** — Capabilities like filesystem access, shell commands, or external APIs\n- **Sub-agents** — Other agents it can delegate tasks to\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: |\n      You are an expert developer. Write clean, efficient code\n      and explain your reasoning step by step.\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n```\n\n## The Root Agent\n\nEvery Docker Agent configuration has a **root agent** — the entry point that receives user messages. In a single-agent setup, this is the only agent. In a multi-agent setup, the root agent acts as a coordinator, delegating tasks to specialized sub-agents.\n\n> [!NOTE]\n> **Naming**\n>\n> The first agent defined in your YAML (or the one named `root`) is the root agent by default. You can also specify which agent to start with using `docker agent run config.yaml -a agent_name`.\n\n## Agent Properties\n\n| Property               | Type    | Required | Description                                                    |\n| ---------------------- | ------- | -------- | -------------------------------------------------------------- |\n| `model`                | string  | ✓        | Model reference (inline like `openai/gpt-5` or a named model) |\n| `description`          | string  | ✓        | What the agent does — used by other agents for delegation      |\n| `instruction`          | string  | ✓        | System prompt defining behavior                                |\n| `toolsets`             | array   | ✗        | List of tool configurations                                    |\n| `sub_agents`           | array   | ✗        | Names of agents this agent can delegate to                     |\n| `fallback`             | object  | ✗        | Fallback model configuration for resilience                    |\n| `add_date`             | boolean | ✗        | Include current date in context                                |\n| `add_environment_info` | boolean | ✗        | Include OS, working directory, git info in context             |\n| `max_iterations`       | int     | ✗        | Max tool-calling loops (default: unlimited)                    |\n| `commands`             | object  | ✗        | Named prompts callable via `/command`                          |\n| `skills`               | boolean \\| list | ✗    | Enable skill discovery and loading. `true` = `[\"local\"]`; list values may combine `\"local\"` with remote skill-server URLs. |\n\n## Model Fallbacks\n\nAgents can automatically fail over to alternative models when the primary model is unavailable:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2 # retries per model for 5xx errors\n      cooldown: 1m # stick with fallback after 429\n```\n\n## Named Commands\n\nDefine reusable prompts that can be invoked as commands:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a helpful assistant.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\n```bash\n# Run a named command\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n```\n\nCommands support environment variable interpolation using JavaScript template literal syntax. Undefined variables expand to empty strings.\n\n## Default Agent\n\nRunning `docker agent run` without a config argument uses `docker-agent.yaml`, `docker-agent.yml`, or `docker-agent.hcl` from the current directory when present. Otherwise, it uses a capable built-in default agent for quick tasks without needing any configuration.\n\n```bash\n# Use the project config or built-in default agent\n$ docker agent run\n\n# Override the default with an alias\n$ docker agent alias add default /path/to/my-agent.yaml\n$ docker agent run  # now runs your custom agent\n```\n\n> [!TIP]\n> **See also**\n>\n> For reusable task-specific instructions, see [Skills](../../features/skills/index.md). For multi-agent patterns, see [Multi-Agent](../multi-agent/index.md). For full config reference, see [Agent Config](../../configuration/agents/index.md).\n","_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md":"---\ntitle: \"Tools\"\ndescription: \"Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more.\"\nkeywords: docker agent, ai agents, concepts, tools\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/tools/\n---\n\n_Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more._\n\n## How Tools Work\n\nWhen an agent needs to perform an action, it makes a **tool call**. The Docker Agent runtime executes the tool and returns the result to the agent, which can then use it to continue its work.\n\n1. Agent receives a user message\n2. Agent decides it needs to use a tool (e.g., read a file)\n3. Docker Agent executes the tool and returns the result\n4. Agent incorporates the result and responds\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing tools that have side effects (shell commands, file writes). Use `--yolo` to auto-approve all tool calls.\n\n## Built-in Tools\n\nDocker Agent ships with several built-in tools that require no external dependencies. Each is enabled by adding its `type` to the agent's `toolsets` list:\n\n| Tool | Description |\n| --- | --- |\n| [Filesystem](../../tools/filesystem/index.md) | Read, write, list, search, and navigate files and directories |\n| [Shell](../../tools/shell/index.md) | Execute shell commands synchronously |\n| [Background Jobs](../../tools/background-jobs/index.md) | Run and manage long-running shell commands |\n| [Think](../../tools/think/index.md) | Step-by-step reasoning scratchpad for planning and decision-making |\n| [Todo](../../tools/todo/index.md) | Task list management for complex multi-step workflows |\n| [Tasks](../../tools/tasks/index.md) | Persistent task database shared across sessions |\n| [Memory](../../tools/memory/index.md) | Persistent key-value storage backed by SQLite |\n| [Fetch](../../tools/fetch/index.md) | Read content from HTTP/HTTPS URLs (GET only) |\n| [Script](../../tools/script/index.md) | Define custom shell scripts as named tools |\n| [LSP](../../tools/lsp/index.md) | Connect to Language Server Protocol servers for code intelligence |\n| [API](../../tools/api/index.md) | Create custom tools that call HTTP APIs without writing code |\n| [OpenAPI](../../tools/openapi/index.md) | Generate tools from an OpenAPI 3.x document |\n| [RAG](../../tools/rag/index.md) | Retrieval-augmented generation over indexed sources |\n| [Model Picker](../../tools/model-picker/index.md) | Let the agent pick between several models per turn |\n| [User Prompt](../../tools/user-prompt/index.md) | Ask users questions and collect interactive input |\n| [Open URL](../../tools/open-url/index.md) | Open a fixed URL in the user's default browser |\n| [Transfer Task](../../tools/transfer-task/index.md) | Delegate tasks to sub-agents (auto-enabled with `sub_agents`) |\n| [Background Agents](../../tools/background-agents/index.md) | Dispatch work to sub-agents concurrently |\n| [Handoff](../../tools/handoff/index.md) | Hand the conversation off to another local agent in the same config (auto-enabled with `handoffs:`) |\n| [A2A](../../tools/a2a/index.md) | Connect to remote agents via the Agent-to-Agent protocol |\n| [MCP Catalog](../../tools/mcp-catalog/index.md) | Discover and activate remote MCP servers from the Docker MCP Catalog on demand |\n| [Git](../../tools/git/index.md) | Read-only git repository inspection |\n| [Scheduler](../../tools/scheduler/index.md) | Schedule instructions to run at a time or on a recurring interval |\n| [Webhook](../../tools/webhook/index.md) | Outbound notifications to Slack, Discord, Telegram, IFTTT, and more |\n| [Plan](../../tools/plan/index.md) | Shared persistent scratchpad for multi-agent collaboration |\n| [Session Plan](../../tools/session_plan/index.md) | Per-session plan tracker for the draft/review/execute workflow |\n| [Session Context](../../tools/session_context/index.md) | Reference a previous session as context |\n\n## MCP Tools\n\nDocker Agent supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for extending agents with external tools. There are three ways to connect MCP tools:\n\n- **Docker MCP** (recommended) — Run MCP servers in Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway). Browse the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n- **Local MCP (stdio)** — Run MCP servers as local processes communicating over stdin/stdout.\n- **Remote MCP (Streamable HTTP / SSE)** — Connect to MCP servers running on a network. See [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n```\n\nSee [Tool Config](../../configuration/tools/index.md#mcp-tools) for full MCP configuration reference.\n\n> [!TIP]\n> **See also**\n>\n> For full configuration reference, see [Tool Config](../../configuration/tools/index.md).\n","_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md":"---\ntitle: \"Agent Configuration\"\ndescription: \"Complete reference for defining agents in your YAML configuration.\"\nkeywords: docker agent, ai agents, configuration, yaml, agent configuration\nlinkTitle: \"Agent Config\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/agents/\n---\n\n_Complete reference for defining agents in your YAML configuration._\n\nA configuration must define at least one agent under `agents`.\n\n## Full Schema\n\n<!-- yaml-lint:skip -->\n```yaml\nagents:\n  agent_name:\n    model: string # Required: model reference\n    description: string # Required: what this agent does\n    instruction: string # Required (unless instruction_file): system prompt\n    instruction_file: string | [list] # Optional: load the system prompt from one or more files relative to this config (mutually exclusive with instruction)\n    sub_agents: [list] # Optional: local or external sub-agent references\n    toolsets: [list] # Optional: tool configurations (use `type: rag` for RAG sources)\n    fallback: # Optional: fallback config\n      models: [list]\n      retries: 2\n      cooldown: 1m\n    add_date: boolean # Optional: add date to context\n    add_environment_info: boolean # Optional: add env info to context\n    add_prompt_files: [list] # Optional: include additional prompt files\n    add_description_parameter: bool # Optional: add description to tool schema\n    redact_secrets: boolean # Optional: scrub detected secrets out of tool args, outgoing chat messages, and tool output\n    code_mode_tools: boolean # Optional: let the agent write JavaScript to orchestrate tool calls (see Code Mode)\n    max_iterations: int # Optional: max tool-calling loops\n    max_consecutive_tool_calls: int # Optional: max identical consecutive tool calls\n    max_old_tool_call_tokens: int # Optional: token budget for old tool call content (disabled unless positive)\n    max_tool_result_tokens: int # Optional: per-tool-result token cap with middle-out truncation (disabled unless positive)\n    num_history_items: int # Optional: limit conversation history\n    session_compaction: boolean # Optional: disable automatic session compaction (default: true)\n    compaction_threshold: float # Optional: context-window fraction that triggers auto-compaction (0–1, default: 0.9)\n    compaction_model: string # Optional: model used for session-compaction (summary generation)\n    use_toolsets: [list] # Optional: names of top-level toolsets to merge into this agent\n    readonly: boolean # Optional: restrict all toolsets to read-only tools only\n    skills: boolean | [list] # Optional: enable skill discovery (true/false or list of names and/or sources)\n    use_commands: [list] # Optional: names of top-level commands groups to merge into this agent\n    use_skills: [list] # Optional: names of top-level skills groups to merge into this agent\n    commands: # Optional: named prompts\n      name: \"prompt text\" # or {instruction: \"prompt\", agent: \"sub_agent_name\"} or {url: \"https://...\"} (TUI only)\n    welcome_message: string # Optional: message shown at session start\n    handoffs: [list] # Optional: agent names this agent can hand off to\n    force_handoff: string # Optional: agent that always receives the conversation when this agent stops\n    hooks: # Optional: lifecycle hooks\n      pre_tool_use: [list]\n      tool_response_transform: [list]\n      post_tool_use: [list]\n      session_start: [list]\n      session_end: [list]\n      on_user_input: [list]\n      stop: [list]\n      notification: [list]\n    structured_output: # Optional: constrain output format\n      name: string\n      schema: object\n    cache: # Optional: response cache (skip the model on repeat questions)\n      enabled: boolean\n      case_sensitive: boolean\n      trim_spaces: boolean\n      path: string\n    harness: # Optional: delegate to an external coding CLI (Claude Code, Codex, opencode, pi)\n      type: string # Required: claude-code | codex | opencode | pi\n      model: string # Optional: model override forwarded to the CLI (omit for the CLI's own default)\n      effort: string # claude-code only: low | medium | high | xhigh | max (omit for the Claude Code default)\n      agent: string # opencode only: agent profile name\n      thinking: boolean # opencode only: enable extended thinking\n```\n\n> [!TIP]\n> **See also**\n>\n> For model parameters, see [Model Config](../models/index.md). For tool details, see [Tool Config](../tools/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n\n## Properties Reference\n\n| Property                    | Type    | Required | Description                                                                                                                                                                   |\n| --------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `model`                     | string  | ✓        | Model reference. Either inline (`openai/gpt-5`) or a named model from the `models` section.                                                                              |\n| `description`               | string  | ✓        | Brief description of the agent's purpose. Used by coordinators to decide delegation.                                                                                          |\n| `instruction`               | string  | ✓        | System prompt that defines the agent's behavior, personality, and constraints. Required unless `instruction_file` is set.                                                      |\n| `instruction_file`          | string \\| array  | ✗        | Path(s) to a file or files (relative to the config file's directory) whose contents become the agent's instruction, loaded at startup. Accepts a single path or a list; multiple files are concatenated in order, separated by a blank line. Mutually exclusive with `instruction`. Each path must be a local relative path inside the config directory (absolute paths and `..` traversal are rejected). Only supported for local file-based configs, not OCI/URL sources. See [External Instruction Files](#external-instruction-files) below. |\n| `sub_agents`                | array   | ✗        | List of agent names or external OCI references this agent can delegate to. Supports local agents, registry references (e.g., `myorg/agent:tag`), and named references (`name:reference`). Automatically enables the `transfer_task` tool. Pin external OCI references to a digest (`name@sha256:…`) to skip the per-run registry lookup that tag references incur. See [External Sub-Agents](../../concepts/multi-agent/index.md#external-sub-agents-from-registries). |\n| `toolsets`                  | array   | ✗        | List of tool configurations. See [Tool Config](../tools/index.md).                                                                                                        |\n| `fallback`                  | object  | ✗        | Automatic model failover configuration.                                                                                                                                       |\n| `add_date`                  | boolean | ✗        | When `true`, injects the current date into the agent's context.                                                                                                               |\n| `add_environment_info`      | boolean | ✗        | When `true`, injects working directory, OS, CPU architecture, and git info into context.                                                                                      |\n| `add_prompt_files`          | array   | ✗        | List of file paths whose contents are appended to the system prompt. Useful for including coding standards, guidelines, or additional context.                                |\n| `add_description_parameter` | boolean | ✗        | When `true`, adds agent descriptions as a parameter in tool schemas. Helps with tool selection in multi-agent scenarios.                                                      |\n| `redact_secrets`            | boolean | ✗        | When `true`, scrubs detected secrets (API keys, tokens, private keys, etc.) out of tool-call arguments, outgoing chat messages, and tool output before they reach a tool, the model, or downstream consumers. See [Redacting Secrets](#redacting-secrets) below.   |\n| `code_mode_tools`           | boolean | ✗        | When `true`, replaces the agent's individual tools with a single tool that runs a JavaScript script calling as many of them as needed in one turn. See [Code Mode](../../features/code-mode/index.md). |\n| `max_iterations`            | int     | ✗        | Maximum number of tool-calling loops. Default: unlimited (0). Set this to prevent infinite loops.                                                                             |\n| `max_consecutive_tool_calls` | int     | ✗        | Maximum consecutive identical tool calls before the agent is terminated, preventing degenerate loops. Default: `5`.                                                          |\n| `max_old_tool_call_tokens`  | int     | ✗        | Maximum number of tokens to keep from old tool call arguments and results. Older tool calls beyond this budget have their content replaced with a placeholder, saving context space. Tokens are approximated as `len/4`. Truncation is disabled by default; set a positive value to enable it. Set to `-1` to disable truncation (unlimited). |\n| `max_tool_result_tokens`    | int     | ✗        | Maximum number of tokens to keep from each tool result when it is added to the session. Oversized results are truncated middle-out: the head and tail are kept and the removed middle is replaced with a truncation marker. Textual documents attached to the result share the same budget. Tokens are approximated as `len/4`. The cap is disabled by default; set a positive value to enable it. `0` and `-1` both leave tool results unbounded. |\n| `num_history_items`         | int     | ✗        | Limit the number of conversation history messages sent to the model. Useful for managing context window size with long conversations. Default: unlimited (all messages sent). |\n| `session_compaction`        | boolean | ✗        | When `false`, disables automatic session compaction for this agent: neither the proactive threshold trigger nor the post-overflow auto-recovery runs. The manual `/compact` command remains available. Default: `true`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_threshold`      | float   | ✗        | Fraction of the model's context window at which proactive auto-compaction triggers. Must be greater than `0` and at most `1`. A `compaction_threshold` set on the agent's model takes precedence. Default: `0.9`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_model`          | string  | ✗        | Model used for session compaction (summary generation). Can be a named model or an inline `provider/model` string. This agent-level value takes precedence over a `compaction_model` set on the agent's model or provider; when none is set, the agent's own model compacts. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `skills`                    | bool/array | ✗     | Enable automatic skill discovery. `true` loads all discovered local skills, `false` disables them. A list can mix skill sources (`local` or `https://…` URLs) and skill names to include — see [Skills](../../features/skills/index.md).                                                     |\n| `commands`                  | object  | ✗        | Named prompts that can be run with `docker agent run config.yaml /command_name`. Can be simple strings or objects with `instruction` and/or `agent` fields for agent switching, or a `url` field to open a link in the browser (TUI only). See [Named Commands](#named-commands) below. |\n| `use_commands`              | list of string | ✗   | Names of top-level `commands` groups to merge into this agent. Inline `commands` entries take precedence on name conflicts. Default: `[]`. |\n| `use_skills`                | list of string | ✗   | Names of top-level `skills` groups to merge into this agent. Inline skills are deduplicated by name against merged entries. Default: `[]`. |\n| `use_toolsets`              | list of string | ✗   | Names of top-level `toolsets` groups to merge into this agent. See [Reusable Toolsets](../overview/index.md#reusable-toolsets-toolsets). Default: `[]`. |\n| `readonly`                  | boolean | ✗   | When `true`, every toolset on this agent is filtered to expose only read-only tools (those annotated with a read-only hint). Mutating tools are removed at load time and cannot be called even if the model tries. See [Read-Only Agents](#read-only-agents) below. |\n| `welcome_message`           | string  | ✗        | Message displayed to the user when a session starts. Rendered as Markdown in the TUI. **Not sent to the model** — it exists purely for the user's benefit. Useful for telling users what the agent can do and what commands are available. |\n| `handoffs`                  | array   | ✗        | List of agent names this agent can hand off the conversation to. Enables the `handoff` tool. See [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).                  |\n| `force_handoff`             | string  | ✗        | Name of an agent that unconditionally receives the conversation whenever this agent produces a final response. The runtime performs the switch itself, bypassing the LLM's tool-calling, guaranteeing deterministic pipelines. Must not reference the agent itself, and chains must not form a cycle. See [Forced Handoffs](../../concepts/multi-agent/index.md#forced-handoffs). |\n| `hooks`                     | object  | ✗        | Lifecycle hooks for running commands at various points. See [Hooks](../hooks/index.md).                                                                                   |\n| `structured_output`         | object  | ✗        | Constrain agent output to match a JSON schema. See [Structured Output](../structured-output/index.md).                                                                    |\n| `cache`                     | object  | ✗        | Response cache. When the same user question is asked again, the previous answer is replayed verbatim and the model is not called. See [Response Cache](#response-cache) below.                  |\n| `harness`                   | object  | ✗        | Run this agent through an external coding CLI instead of a model. **Note:** Any `toolsets:` defined on the same agent are silently ignored when `harness:` is set — the external CLI brings its own tools. See [Coding Harnesses](../../features/harnesses/index.md). |\n\n> [!WARNING]\n> **max_iterations**\n>\n> Default is `0` (unlimited). Always set `max_iterations` for agents with powerful tools like `shell` to prevent infinite loops. A value of 20–50 is typical for development agents.\n\n> [!TIP]\n> **Managing long sessions**\n>\n> `max_old_tool_call_tokens`, `max_tool_result_tokens`, `num_history_items`, `session_compaction`, and `compaction_threshold` all help keep long-running sessions inside the model's context window. See the [Context & Compaction guide](../../guides/compaction/index.md) for how to combine them.\n\n## External Instruction Files\n\nLong system prompts can be kept in their own files instead of being inlined in\nthe YAML, using `instruction_file`. This separates infrastructure configuration\n(models, providers, tools) from behavioral content (the prompt), which keeps\nversion-control diffs focused, reduces merge conflicts on shared configs, and\nlets instruction content be edited without risking YAML syntax errors.\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-5-mini\n    description: Routes work between specialist agents\n    instruction_file: instructions/coordinator.md\n    sub_agents:\n      - writer\n  writer:\n    model: openai/gpt-5-mini\n    description: Drafts and edits written content\n    instruction_file: instructions/writer.md\n```\n\nThe path is resolved relative to the config file's directory and the file's\ncontents are loaded as the agent's instruction when the config is loaded. Notes:\n\n- **Mutually exclusive** with `instruction`. Setting both is an error.\n- Each path must be a **local relative path inside the config directory**.\n  Absolute paths and `..` traversal are rejected.\n- A **list** of files is also accepted; their contents are concatenated in\n  order, separated by a blank line. This lets a shared preamble be reused\n  across agents while each agent appends its own specifics:\n\n  ```yaml\n  agents:\n    writer:\n      model: openai/gpt-5-mini\n      description: Drafts and edits written content\n      instruction_file:\n        - instructions/shared-preamble.md\n        - instructions/writer.md\n  ```\n\n- Only supported for **local file-based configs**, not agents loaded from OCI\n  registries or URLs. When an agent is pushed with `docker agent share push`,\n  the file contents are inlined into the pushed artifact, so the published\n  agent stays self-contained.\n\nA runnable example lives in [`examples/instruction_file.yaml`](https://github.com/docker/docker-agent/blob/main/examples/instruction_file.yaml).\n\n## Prompt Files\n\n`add_prompt_files` injects the contents of one or more files into the agent's\ncontext at the start of every turn — handy for repo-wide conventions like\n`AGENTS.md` or `CLAUDE.md` that should stay available without being pasted\ninto `instruction`:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A helpful coding assistant\n    instruction: You are an expert software developer.\n    add_prompt_files:\n      - AGENTS.md\n```\n\nFor each name, the agent loads the closest match found by walking up from the\ncurrent working directory, plus (if it's a different file) a copy at that\nname directly under the user's home directory — so a personal `~/AGENTS.md`\ncan layer on top of a repo-local one. Missing files are skipped rather than\nerroring. Because resolution and the read happen on every turn, edits to the\nfile are picked up without restarting the agent.\n\nUse `--prompt-file` to add files for a single run without editing the\nconfig. It's merged with any `add_prompt_files` already set on the agent,\nwith duplicates dropped:\n\n```bash\n$ docker agent run agent.yaml --prompt-file CONTRIBUTING.md\n```\n\nResolved prompt files show up as their own entries in the `/context` dialog — see [File Attachments](../../features/tui/index.md#file-attachments) in the Terminal UI guide.\n\nSee [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how prompt files compare to `@`/`/attach` attachments, the `rag` toolset, and sending content over the API/chat server.\n\n## Response Cache\n\nThe response cache short-circuits the model when the same user question is asked again. The first time a question is asked, the agent calls the model normally and stores the assistant's reply. Subsequent identical questions skip the model entirely and replay the stored reply verbatim.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Cached assistant\n    instruction: You are a helpful assistant.\n    cache:\n      enabled: true          # required to turn the cache on\n      case_sensitive: false  # default: false (\"Hello\" == \"hello\")\n      trim_spaces: true      # default: false (\"  hello  \" == \"hello\")\n      path: ./cache.json     # optional: persist to disk; omit for in-memory\n```\n\n| Property         | Type    | Default | Description                                                                                                                                                                                                                       |\n| ---------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `enabled`        | boolean | `false` | Master switch. When `false` (or when the `cache` section is omitted), no caching is performed.                                                                                                                                     |\n| `case_sensitive` | boolean | `false` | When `true`, questions must match exactly (including case) to hit the cache.                                                                                                                                                       |\n| `trim_spaces`    | boolean | `false` | When `true`, leading and trailing whitespace is stripped from the question before it is compared.                                                                                                                                  |\n| `path`           | string  | _empty_ | When set, cache entries are persisted to a JSON file at the given path and reloaded on startup so the cache survives restarts. Relative paths resolve against the agent config directory. When empty, the cache lives in memory only. |\n\n**How it works**\n\n- The cache key is the latest user message in the session, normalized according to `case_sensitive` and `trim_spaces`.\n- On a hit, the cached reply is added to the session as the assistant message and stop hooks fire normally — the rest of the agent (tools, sub-agents, the model) is bypassed.\n- On a miss, the agent runs normally; the final assistant message produced by the first stop of the run is then stored under the question's key.\n- Only the response to the original user question of a run is cached; follow-up turns inside the same `RunStream` are not.\n\n**File-backed storage**\n\nWhen `path` is set, every `Store` rewrites the entire cache file. Writes are **atomic**: the new content is written to a sibling temp file, `fsync`'d, and renamed over the destination, so a concurrent reader (or a process that crashes mid-write) will always see either the previous content or the new content in full — never a partially written file. The parent directory is also `fsync`'d after the rename so the rename itself is durable.\n\n**Cross-process sharing**\n\nMultiple processes can share the same `path:` cache file safely. Every `Store` takes an exclusive advisory lock on a sibling `<path>.lock` file (POSIX `flock(2)` on Unix, `LockFileEx` on Windows), reloads the current on-disk state under the lock, merges the new entry, and writes back atomically. Two processes that store *different* keys at the same time both see their writes preserved on disk; the lock window is short (one read + one fsync'd write).\n\n`Lookup` watches the file's modification time and reloads the in-memory map when the file has advanced since its last load, so writes from a sibling process become visible without a restart. The `<path>.lock` sentinel file is created on first write and never deleted: removing it would let two processes lock different inodes and lose mutual exclusion.\n\n## Redacting Secrets\n\nThe `redact_secrets` flag is a single agent-level switch that scrubs accidentally leaked credentials, tokens, and private keys out of an agent's I/O. It wires up three complementary defenses:\n\n1. A `pre_tool_use` built-in hook that scrubs detected secrets from the **arguments of every tool call**, before the tool sees them.\n2. A `before_llm_call` built-in hook that scrubs the same patterns from **outgoing chat messages** — message content, multi-part text content, prior reasoning content, and the JSON-encoded arguments of any tool call still in the conversation — before they reach the model provider.\n3. A `tool_response_transform` built-in hook that scrubs **tool output at the source**, so the secret never reaches event consumers, the persisted session file, the `post_tool_use` hook input, or the next LLM call.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: A helpful assistant that scrubs secrets before they leak\n    instruction: |\n      You are a helpful assistant. If the user accidentally pastes a token,\n      do your best work without echoing the secret back.\n    redact_secrets: true\n    toolsets:\n      - type: shell\n```\n\nDetection uses the [portcullis](https://github.com/docker/portcullis) ruleset, which recognises common secret patterns including:\n\n- GitHub Personal Access Tokens (`ghp_*`, `gho_*`, `ghu_*`, `ghs_*`, `ghr_*`, fine-grained `github_pat_*`)\n- AWS access keys (`AKIA*`, `ASIA*`, …) and secret access keys\n- GitLab PATs (`glpat-*`), Hugging Face tokens (`hf_*`)\n- Stripe (`sk_live_*`, `pk_test_*`, …), Slack (`xoxb-*`, …), Shopify, Twilio, Discord, Atlassian, Mailchimp, SendGrid, and many more\n- JWTs, GCP service-account JSON, Heroku keys, Docker Hub PATs (`dckr_pat_*`)\n- PEM-encoded private keys (`-----BEGIN … PRIVATE KEY-----` blocks)\n\nEach detected span is replaced with the literal string `[REDACTED]`; the surrounding text is preserved so a redacted argument still looks like a legitimate flag (e.g. `--token=[REDACTED]`). Redaction is idempotent — applying it twice yields the same result.\n\n> [!NOTE]\n> **False positives vs. false negatives**\n>\n> False positives are extremely rare: every rule pairs a regex with a discriminating keyword, so plain English never trips detection. **False negatives are possible** — only patterns the ruleset recognises are scrubbed, so this is a defense-in-depth feature, not a substitute for keeping secrets out of the conversation in the first place. Pair it with a proper [secret manager](../../guides/secrets/index.md) for the credentials your agent actually needs.\n\n> [!NOTE]\n> **Equivalent hook entry**\n>\n> Setting `redact_secrets: true` on the agent is shorthand for auto-registering all three legs of the feature as hook entries. They share the _same_ built-in name (`type: builtin`, `command: redact_secrets`) on `pre_tool_use`, `before_llm_call`, and `tool_response_transform` respectively — the implementation dispatches on the hook event. You can spell them out by hand to scope a leg to a subset of tools (set `matcher:` to a regex), stack them with other rewriters in a specific order, or enable just one or two legs. See [`examples/redact_secrets_hooks.yaml`](https://github.com/docker/docker-agent/blob/main/examples/redact_secrets_hooks.yaml) for a complete manual wiring and the [Hooks reference](../hooks/index.md#available-built-ins) for the builtin's event coverage.\n\n## Welcome Message\n\nDisplay a message when users start a session:\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-5\n    description: Development assistant\n    instruction: You are a helpful coding assistant.\n    welcome_message: |\n      👋 Welcome! I'm your development assistant.\n\n      I can help you with:\n      - Writing and reviewing code\n      - Running tests and debugging\n      - Explaining concepts\n\n      What would you like to work on?\n```\n\n## Deferred Tool Loading\n\nToolsets support `defer` to load tools on-demand and speed up agent startup. See [Deferred Tool Loading](../tools/index.md#deferred-tool-loading) for details.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-purpose assistant\n    instruction: You have access to many tools.\n    toolsets:\n      - type: mcp\n        ref: docker:github-official\n        defer: true\n      - type: filesystem\n```\n\n## Fallback Configuration\n\nAutomatically switch to backup models when the primary fails:\n\n| Property   | Type   | Default | Description                                                |\n| ---------- | ------ | ------- | ---------------------------------------------------------- |\n| `models`   | array  | `[]`    | Fallback models to try in order                            |\n| `retries`  | int    | `2`     | Retries per model for 5xx errors. `-1` to disable.         |\n| `cooldown` | string | `1m`    | How long to stick with a fallback after a rate limit (429) |\n\n**Error handling:**\n\n- **Retryable** (same model with backoff): HTTP 5xx, 408, network timeouts\n- **Non-retryable** (skip to next model): HTTP 429, 4xx client errors\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2\n      cooldown: 1m\n```\n\n## Named Commands\n\n> [!TIP]\n> **Full reference**\n>\n> This section covers the basics. For URL commands, agent-switching commands, reusable top-level `commands:` groups, and hiding commands with `--disable-commands`, see [Custom Commands](../commands/index.md).\n\nDefine reusable prompt shortcuts that can send prompts to the current agent, switch to a different sub-agent, or open a URL in the browser:\n\n> **Note:** Named slash commands execute immediately, even while the agent is processing another message. Unlike regular chat messages (which are queued), slash commands interrupt or direct the agent even while it is mid-response.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n      deploy: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n      \n      # Advanced format with agent switching\n      plan:\n        agent: planner  # Switch to the 'planner' agent\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"  # Optional: send this prompt after switching\n      \n      # Agent switching without instruction - forwards remaining text as prompt\n      review:\n        agent: reviewer  # Any text after /review is sent to the reviewer agent\n\n      # URL command - opens a link in the browser instead of messaging the agent\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n```\n\n### Command Formats\n\nCommands support three formats:\n\n1. **Simple string format**: The string becomes the instruction sent to the current agent\n\n   ```yaml\n   df: \"Check disk space\"\n   ```\n\n2. **Advanced object format**: Supports agent switching and optional instructions\n\n   ```yaml\n   plan:\n     agent: planner  # Required: name of any agent defined in the team\n     instruction: \"Plan: ${args.join(\\\" \\\")}\"  # Optional: prompt to send after switching\n     description: \"Switch to planning mode\"  # Optional: shown in help text\n   ```\n\n3. **URL format**: Opens a link in the browser instead of messaging the agent\n\n   ```yaml\n   docs:\n     url: https://docs.docker.com/          # Required: URL to open\n     description: \"Open the documentation\"  # Optional: shown in help text\n   ```\n\nWhen `agent` is set without `instruction`, any text typed after the slash command (e.g., `/plan build a web app`) is forwarded as a prompt to the target agent. The target agent can be **any agent defined in the team configuration** — it does not need to be listed in the current agent's `sub_agents` array.\n\n**Argument and expansion syntax**\n\nAn `instruction` string can reference the command's arguments and expand tool calls:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments, in the order the user typed them after the command\n- `${args.join(\" \")}` — all arguments joined into a single string\n- `${tool_name({...})}` — calls a tool and inlines its return value (any tool available to the agent)\n- `!tool_name(key=value)` — legacy tool-call form: calls a tool with plain `key=value` arguments and inlines its output\n\n### Agent-Switching Commands\n\nCommands with an `agent` field switch the active agent for that command's scope. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the appropriate specialist.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n      # Simple prompt command (no switching)\n      status: \"Summarize what we have accomplished so far\"\n\n  planner:\n    model: openai/gpt-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\n**Agent-switching vs. `handoff`**\n\n| | Agent-switching command | `handoff` tool |\n| --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` |\n| **Session** | Stays in the same session | Stays in the same session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation |\n| **Return** | User must explicitly switch back | Target agent can chain to another agent |\n\n**Agent-switching vs. `transfer_task`**\n\n`transfer_task` launches a **sub-session**: the root agent sends a task, the child runs in isolation, and the result is returned to the root. The root agent stays in control and the child's work is never in the main conversation. Use `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n```bash\n# Run commands from the CLI\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\nCommands use JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation. Undefined variables expand to empty strings.\n\nThe same syntax is also expanded in agent and toolset instructions: `agents.<name>.instruction` and `toolsets[*].instruction` support `${env.X}` placeholders (with optional `||` defaults and ternary expressions). `agents.<name>.description` and `agents.<name>.welcome_message` also support it.\n\nNote that path-like fields (`working_dir`, `path`) primarily use a shell-style syntax (`$VAR`, `${VAR}`, `~`), and also accept `${env.X}` as an alias (though not richer JS expressions). See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full table.\n\n### URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — both standard web URLs and custom schemes such as `docker-desktop://` for deep links. URL commands are TUI-only — they have no effect when run from the CLI.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Read-Only Agents\n\nSet `readonly: true` on an agent to restrict all of its toolsets to tools that are annotated as read-only. Mutating tools are filtered out at load time — the agent cannot list or call them, even if the model hallucinates a call.\n\nYou can also set `readonly: true` on an individual toolset to restrict only that toolset while leaving others unrestricted.\n\n```yaml\nagents:\n  # Agent-level readonly: every toolset is restricted to read-only tools.\n  inspector:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only inspector that can explore but never modify.\n    instruction: Explore the project. Do not make changes.\n    readonly: true\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  # Toolset-level readonly: only the filesystem toolset is restricted;\n  # the shell toolset keeps all of its tools.\n  mixed:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only file access, full shell access.\n    instruction: You can read files and run any shell command.\n    toolsets:\n      - type: filesystem\n        readonly: true\n      - type: shell\n```\n\nSee [`examples/readonly.yaml`](https://github.com/docker/docker-agent/blob/main/examples/readonly.yaml) for a complete example.\n\n> [!NOTE]\n> **Which tools are read-only?**\n>\n> Whether a tool is read-only is determined by its `ReadOnlyHint` annotation. For built-in tools, read-only operations (list/read/search) carry the hint; mutating operations (write/delete/execute) do not. Custom and MCP tools expose the hint via their own annotations.\n\n## Complete Example\n\n```yaml\nmodels:\n  claude:\n    provider: anthropic\n    model: claude-sonnet-4-5\n    max_tokens: 64000\n\nagents:\n  root:\n    model: claude\n    description: Technical lead coordinating development\n    instruction: |\n      You are a technical lead. Analyze requests and delegate\n      to the right specialist. Always review work before responding.\n    welcome_message: \"👋 I'm your tech lead. How can I help today?\"\n    sub_agents: [developer, researcher]\n    add_date: true\n    add_environment_info: true\n    fallback:\n      models: [openai/gpt-5]\n    toolsets:\n      - type: think\n    commands:\n      review: \"Review all recent code changes for issues\"\n    hooks:\n      session_start:\n        - type: command\n          command: \"./scripts/setup.sh\"\n\n  developer:\n    model: claude\n    description: Expert software developer\n    instruction: Write clean, tested, production-ready code.\n    max_iterations: 30\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n\n  researcher:\n    model: openai/gpt-5\n    description: Web researcher with memory\n    instruction: Search for information and remember findings.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n      - type: memory\n        path: ./research.db\n```\n","_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md":"---\ntitle: \"Custom Commands\"\ndescription: \"Define slash commands that send prompts, open URLs, or switch agents, and reuse them across agents with top-level command groups.\"\nkeywords: docker agent, ai agents, configuration, yaml, custom commands, slash commands\nlinkTitle: \"Custom Commands\"\nweight: 55\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/commands/\n---\n\n_Define slash commands that send prompts, open URLs, or switch agents._\n\n## What Slash Commands Are\n\nA slash command is a named shortcut a user types in the TUI (`/df`, `/deploy`, `/plan`) or on the CLI (`docker agent run agent.yaml /df`) instead of typing out a full prompt. Every agent can declare its own commands under `commands:`, and top-level `commands:` groups let multiple agents share the same set without duplicating them.\n\nUnlike regular chat messages — which are queued while the agent is busy — slash commands (both built-in and named) execute immediately, even mid-response.\n\nCommands come in three shapes:\n\n| Shape | What it does |\n| --- | --- |\n| [Prompt command](#prompt-commands) | Sends a prompt to the current agent |\n| [URL command](#url-commands) | Opens a link in the user's browser (full TUI only) |\n| [Agent-switching command](#agent-switching-commands) | Switches the active agent, optionally with a prompt (full TUI and CLI) |\n\n> [!IMPORTANT]\n> **Behavior differs by frontend**\n>\n> `url` and `agent` are only fully honored in the **full TUI**, which checks `url` before `agent` (a URL command opens the browser and stops there; an agent-switching command switches before sending any instruction). The **lean TUI** doesn't special-case either field — it only resolves a command's expanded text and sends it as a chat message, so a URL-only command silently sends whatever trailing text followed the slash (often nothing, opening no browser) and an agent-switching command sends its instruction to the *current* agent instead of the target. The **CLI** (`docker agent run agent.yaml /command`) switches agents like the full TUI, but has no browser to open, so `url` has no effect there. The **HTTP API** (`POST /api/sessions/:id/agent/:agent`) resolves agent-switching commands server-side: if the message content starts with a slash command whose `agent` field is set, the active agent is switched and the message is rewritten before the turn runs. Prompt-only and URL commands are not resolved server-side and pass through to the model unchanged.\n\n## Prompt Commands\n\nThe simplest form: a string value that becomes the instruction sent to the current agent.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A system administrator assistant.\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\nFor more control, use the object form with an `instruction:` field, plus an optional `description:` shown in completion dialogs and help text:\n\n```yaml\ncommands:\n  deploy:\n    description: \"Deploy the application to staging\"\n    instruction: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n```\n\nCommands support JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation, with optional `||` defaults and ternary expressions — the same syntax as agent `instruction` and `description`. Undefined variables expand to the empty string. See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full picture.\n\nPrompt commands can also reference the text typed after the slash and call tools, using the same `${...}` expansion engine as `${env.VAR}`:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments (whitespace-tokenized; quoted substrings keep their spaces together).\n- `${args}` or `${args.join(\" \")}` — the full argument list.\n- `${tool_name({key: value, ...})}` — calls an agent tool and inlines its output. JS expressions are evaluated before tool commands, so tool output is never itself re-evaluated as JS.\n- `` !tool_name(key=value) `` — legacy bang syntax for the same tool-call inlining; still supported alongside `${tool_name({...})}`.\n\nIf `instruction` uses none of the `${args...}` placeholders, any text typed after the slash is appended to the resolved instruction automatically.\n\n```yaml\ncommands:\n  fix:\n    description: \"Fix a file, with optional extra options\"\n    instruction: \"Fix the file ${args[0]} with options ${args[1]}\"\n  run:\n    description: \"Run a command with all the typed arguments\"\n    instruction: 'Run command with args: ${args.join(\" \")}'\n  lint:\n    description: \"Show the current lint output\"\n    instruction: 'Lint: ${shell({cmd: \"task lint\"})}'\n```\n\n```bash\n# Run commands from the CLI too\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ docker agent run agent.yaml /fix main.go --verbose\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\n## URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — standard web URLs and custom schemes such as `docker-desktop://` for deep links.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\n> [!NOTE]\n> **Full TUI only**\n>\n> URL commands only open a browser in the full TUI. The CLI and lean TUI don't check the `url` field at all, so `docker agent run agent.yaml /docs` never opens a browser there — but the command is still dispatched: its resolved text (usually empty, for a URL-only command) is sent as a prompt and can trigger a model turn.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Agent-Switching Commands\n\nA command with an `agent` field switches the active agent for the rest of the conversation. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the right specialist.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(' ')}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\nWhen `agent` is set **without** `instruction`, any text typed after the slash command (e.g. `/review fix the auth bug`) is forwarded as a prompt to the target agent. When both are set, the agent is switched first, then the instruction is sent to the new agent. Either way, the target can be **any agent defined in the team**, not just one of the current agent's own `sub_agents` — `sub_agents` above is shown because `planner` and `reviewer` also happen to be delegation targets, not because `agent:` requires it.\n\nAgent switching stays in the same session — the target agent sees the full conversation history, and the user must explicitly switch back (there's no automatic return). This is different from the two other ways agents hand off work:\n\n| | Agent-switching command | `handoff` tool | `transfer_task` |\n| --- | --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` | Model calls `transfer_task()` |\n| **Session** | Stays in the same session | Stays in the same session | Launches an isolated sub-session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation | Child runs in isolation; only the result returns |\n| **Control** | User must explicitly switch back | Target agent can chain to another agent | Root agent stays in control |\n\nUse `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n## Reusable Command Groups\n\nRepeated command sets across agents can be hoisted into the top-level `commands:` section and pulled in by name with `use_commands:` — the same reuse pattern as `mcps:` for MCP servers and `toolsets:` for shared toolsets.\n\n```yaml\ncommands:\n  ci:\n    deploy: \"Deploy the application\"\n    test: \"Run the test suite\"\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Lead developer\n    instruction: You are the lead developer. Coordinate the team.\n    use_commands: [ci]      # reuse the \"ci\" command group\n    commands:\n      lint: \"Run the linter\"  # inline command, merged in (wins on conflict)\n\n  docs-writer:\n    model: anthropic/claude-sonnet-4-5\n    description: Documentation writer\n    instruction: You write and maintain the project documentation.\n    use_commands: [ci]      # same group, reused without duplication\n```\n\nAn agent's own inline `commands:` entries take precedence over merged `use_commands:` entries on name conflicts. See [`examples/shared-commands-skills.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared-commands-skills.yaml) for a complete example that also covers the equivalent `skills:` / `use_skills:` pattern.\n\n## Hiding Commands\n\nUse `--disable-commands` to hide and disable specific slash commands in the TUI — built-in ones (`/cost`, `/eval`, `/model`, …) or your own named ones. Accepts a comma-separated list; the leading slash is optional and matching is case-insensitive.\n\n```bash\n$ docker agent run agent.yaml --disable-commands=\"/cost,/eval,/model\"\n```\n\nThis is useful for shipping a distributed agent with a narrower command surface — for example, hiding `/model` so a published agent always runs its intended model.\n\n## Built-in Commands\n\nThe TUI ships its own slash commands (`/new`, `/compact`, `/sessions`, `/settings`, …) alongside whatever an agent defines. See [Slash Commands](../../features/tui/index.md#slash-commands) in the TUI reference for the full list.\n\n## Command Configuration Reference\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `description` | string | Shown in completion dialogs and help text. |\n| `instruction` | string | The prompt sent to the agent. Supports argument expansion (`${args[0]}`, `${args.join(\" \")}`, …), tool calls (`${tool_name({...})}`), and the legacy bang syntax `!tool_name(...)`. |\n| `agent` | string | Name of an agent in the team to switch to when this command is invoked — any agent in the team's `agents:` map, not just one of the current agent's `sub_agents`. When set without `instruction`, any text typed after the slash command is forwarded as a prompt to the target agent. |\n| `url` | string | URL to open in the user's default browser when this command is invoked, instead of sending a prompt to the agent (full TUI only — see [URL Commands](#url-commands)). The token `{{session_id}}` is replaced at invocation time with the current session ID (URL-query-escaped). |\n\n`instruction` and `agent` can be combined (the agent is switched first, then the instruction is sent to the new agent). In the full TUI, if `url` is set, it takes precedence over `agent` and `instruction` — the command only opens the browser; the lean TUI and CLI don't check `url` at all, so a URL-only command instead sends its (usually empty) resolved text as a prompt. See [Behavior differs by frontend](#what-slash-commands-are) above. The simple string form is shorthand for `{ instruction: \"...\" }`.\n","_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md":"---\ntitle: \"Tool Configuration\"\ndescription: \"Complete reference for configuring built-in tools, MCP tools, and Docker-based tools.\"\nkeywords: docker agent, ai agents, configuration, yaml, tool configuration\nlinkTitle: \"Tool Config\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/tools/\naliases:\n  - /ai/docker-agent/reference/toolsets/\n---\n\n_Complete reference for configuring built-in tools, MCP tools, and Docker-based tools._\n\n## Built-in Tools\n\nBuilt-in tools are included with Docker Agent and require no external dependencies. Add them to your agent's `toolsets` list by `type`. Each tool's dedicated page covers its full configuration options, available operations, and examples.\n\n| Type | Description | Page |\n| --- | --- | --- |\n| `filesystem` | Read, write, list, search, navigate | [Filesystem](../../tools/filesystem/index.md) |\n| `git` | Read-only repository inspection (status, log, branches, show, blame) | [Git](../../tools/git/index.md) |\n| `shell` | Execute shell commands synchronously | [Shell](../../tools/shell/index.md) |\n| `background_jobs` | Run and manage long-running shell commands | [Background Jobs](../../tools/background-jobs/index.md) |\n| `scheduler` | Schedule instructions to run at a time or on a recurring interval | [Scheduler](../../tools/scheduler/index.md) |\n| `think` | Reasoning scratchpad | [Think](../../tools/think/index.md) |\n| `plan` | Shared persistent scratchpad for multi-agent collaboration | [Plan](../../tools/plan/index.md) |\n| `session_plan` | Per-session markdown plan for the draft-review-execute workflow | [Session Plan](../../tools/session_plan/index.md) |\n| `session_context` | Reference a previous session as context (read-only) | [Session Context](../../tools/session_context/index.md) |\n| `todo` | Task list management | [Todo](../../tools/todo/index.md) |\n| `memory` | Persistent key-value storage (SQLite) | [Memory](../../tools/memory/index.md) |\n| `tasks` | Persistent task database shared across sessions | [Tasks](../../tools/tasks/index.md) |\n| `fetch` | HTTP `GET` requests with text/markdown/html output | [Fetch](../../tools/fetch/index.md) |\n| `script` | Custom shell scripts as tools | [Script](../../tools/script/index.md) |\n| `lsp` | Language Server Protocol integration | [LSP](../../tools/lsp/index.md) |\n| `api` | Custom HTTP API tools | [API](../../tools/api/index.md) |\n| `openapi` | Import every operation of an OpenAPI 3.x document as tools | [OpenAPI](../../tools/openapi/index.md) |\n| `rag` | Retrieval-augmented generation over indexed sources | [RAG](../../tools/rag/index.md) |\n| `model_picker` | Let the agent pick between several models per turn | [Model Picker](../../tools/model-picker/index.md) |\n| `user_prompt` | Interactive user input | [User Prompt](../../tools/user-prompt/index.md) |\n| `open_url` | Open a fixed URL in the user's default browser | [Open URL](../../tools/open-url/index.md) |\n| `transfer_task` | Delegate to sub-agents (auto-enabled) | [Transfer Task](../../tools/transfer-task/index.md) |\n| `background_agents` | Parallel sub-agent dispatch | [Background Agents](../../tools/background-agents/index.md) |\n| `webhook` | Reliable notifications to a configured destination, with retries (Slack, Discord, Telegram, IFTTT, Teams, …) | [Webhook](../../tools/webhook/index.md) |\n| `handoff` | Local conversation handoff to another agent in the same config (auto-enabled by `handoffs:`) | [Handoff](../../tools/handoff/index.md) |\n| `a2a` | A2A remote agent connection | [A2A](../../tools/a2a/index.md) |\n| `mcp_catalog` | Discover and activate remote MCP servers from the Docker MCP Catalog on demand | [MCP Catalog](../../tools/mcp-catalog/index.md) |\n\n**Example:**\n\n```yaml\ntoolsets:\n  - type: filesystem\n  - type: shell\n  - type: background_jobs\n  - type: think\n  - type: todo\n  - type: memory\n    path: ./dev.db\n```\n\n## MCP Tools\n\nExtend agents with external tools via the [Model Context Protocol](https://modelcontextprotocol.io/). For a standalone overview of the `mcp` toolset see the [MCP tool page](../../tools/mcp/index.md).\n\n> [!TIP]\n> **Reusable MCP definitions**\n>\n> Repeated MCP server definitions can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`. See [Reusable MCP Servers](../overview/index.md#reusable-mcp-servers-mcps).\n\n### Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway):\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo # web search\n  - type: mcp\n    ref: docker:github-official # GitHub integration\n```\n\nBrowse available tools at the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`)                             |\n| `tools`       | array  | Optional: only expose these tools                                |\n| `instruction` | string | Custom instructions injected into the agent's context            |\n| `config`      | any    | MCP server-specific configuration (passed during initialization) |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n\n### Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `command` | string | Command to execute the MCP server |\n| `args` | array | Command arguments |\n| `tools` | array | Optional: only expose these tools |\n| `env` | object | Environment variables (key-value pairs) |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context |\n| `version` | string | Package reference for [auto-installing](#auto-installing-tools) the command binary |\n\n### Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp-server.example.com\"\n      transport_type: \"streamable\"\n      headers:\n        Authorization: \"Bearer your-token\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: true\n    tools: [\"search_web\", \"fetch_url\"]\n```\n\n| Property                | Type    | Description                                                                                                           |\n| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |\n| `remote.url`            | string  | URL of the MCP server. Accepts `https://`, `http://`, and `unix://` (Unix domain socket) schemes.                     |\n| `remote.transport_type` | string  | `streamable` or `sse`                                                                                                 |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. `${env.VAR}` reads an environment variable; `${headers.NAME}` forwards a header from the caller's incoming request (useful when Docker Agent runs as an API server). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers.        |\n\n## Auto-Installing Tools\n\nWhen configuring MCP or LSP tools that require a binary command, Docker Agent can **automatically download and install** the command if it's not already available on your system. This uses the [aqua registry](https://github.com/aquaproj/aqua-registry) — a curated index of CLI tool packages.\n\n### How It Works\n\n1. When a toolset with a `command` is loaded, Docker Agent checks if the command is available in your `PATH`\n2. If not found, it checks the Docker Agent tools directory (`~/.cagent/tools/bin/`)\n3. If still not found, it looks up the command in the aqua registry and installs it automatically\n\n### Explicit Package Reference\n\nUse the `version` property to specify exactly which package to install:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls\n    version: \"golang/tools@v0.21.0\"\n    args: [\"mcp\"]\n  - type: lsp\n    command: rust-analyzer\n    version: \"rust-lang/rust-analyzer@2024-01-01\"\n    file_types: [\".rs\"]\n```\n\nThe format is `owner/repo` or `owner/repo@version`. When a version is omitted, the latest release is used.\n\n### Automatic Detection\n\nIf the `version` property is not set, Docker Agent tries to auto-detect the package from the command name by searching the aqua registry:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls  # auto-detected as golang/tools\n    args: [\"mcp\"]\n```\n\n### Checksum Verification\n\nWhere the aqua registry includes a checksum manifest, downloaded binaries are verified against it before installation. Verification behaviour depends on the checksum type advertised:\n\n- **Strong checksums (sha256, sha512, etc.)** — verified before the binary is installed. If the downloaded archive does not match, the install is aborted and an error is returned (fails closed).\n- **Unsupported or weak checksum types (e.g. md5, sha1)** — skipped with a warning; installation proceeds without verification.\n- **No manifest** — if no checksum is advertised in the registry entry, the binary is installed without verification.\n\n### version_overrides Resolution\n\nThe auto-installer correctly resolves **`version_overrides`** entries in the aqua registry. Many common tools (for example, `fzf`) keep their package configuration — including download URLs and checksums — under `version_overrides` rather than at the top level of their registry entry. These tools previously failed to install silently; they are now handled correctly.\n\n### Disabling Auto-Install\n\n**Per toolset** — set `version` to `\"false\"` or `\"off\"`:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: my-custom-server\n    version: \"false\"\n```\n\n**Globally** — set the `DOCKER_AGENT_AUTO_INSTALL` environment variable:\n\n```bash\nexport DOCKER_AGENT_AUTO_INSTALL=false\n```\n\n### Environment Variables\n\n| Variable                     | Default            | Description                                      |\n| ---------------------------- | ------------------ | ------------------------------------------------ |\n| `DOCKER_AGENT_AUTO_INSTALL`  | (enabled)          | Set to `false` to disable all auto-installation  |\n| `DOCKER_AGENT_TOOLS_DIR`     | `~/.cagent/tools/` | Base directory for installed tools               |\n| `GITHUB_TOKEN`               | —                  | GitHub token to raise API rate limits (optional) |\n\nInstalled binaries are placed in `~/.cagent/tools/bin/` and cached so they are only downloaded once.\n\n> [!TIP]\n> Auto-install supports both Go packages (via `go install`) and GitHub release binaries (via archive download). The aqua registry metadata determines which method is used.\n\n## Toolset Lifecycle\n\nLong-running toolsets — local MCP servers (stdio), remote MCP servers (Streamable HTTP / SSE), and LSP servers — are managed by a single supervisor that can auto-reconnect them when they crash, time out, or drop their session. The `lifecycle` block on the toolset lets you tune that supervisor per toolset. It applies to every `type: mcp` and `type: lsp` toolset.\n\nThe simplest knob is `profile`, which picks a preset:\n\n| Profile | Auto-restart | Use case |\n| --- | --- | --- |\n| `resilient` | Yes | Default. Exponential backoff on disconnect; the agent keeps running if the toolset is unavailable. Matches the historical Docker Agent behaviour. |\n| `strict` | No | Fail-fast. Marks the toolset as required. Intended for CI / headless runs where a missing dependency should be a hard error. |\n| `best-effort` | No | Single attempt, no retries. Good for experimental MCPs whose flakiness should not amplify into a restart loop. |\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; shown here for clarity\n\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: strict\n\n  - type: mcp\n    ref: docker:openbnb-airbnb\n    lifecycle:\n      profile: best-effort\n```\n\n### Tuning the defaults\n\nAny field set on `lifecycle` overrides the profile preset, so you can mix-and-match: pick a profile and only override the knobs you care about.\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: [\"docker\", \"mcp\", \"gateway\"]\n    lifecycle:\n      profile: resilient\n      max_restarts: 10        # keep trying longer than the default of 5\n      backoff:\n        initial: 500ms\n        max: 1m\n        multiplier: 2\n        jitter: 0.2           # 20% random offset to avoid thundering-herd retries\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `profile` | string | One of `resilient` (default), `strict`, `best-effort`. Picks defaults for every other field. |\n| `restart` | string | When the supervisor should reconnect after a disconnect: `never`, `on_failure` (default), or `always`. For **remote** MCP toolsets (Streamable HTTP / SSE), `on_failure` is automatically promoted to `always` so idle-timeout closes reconnect gracefully — `never` is still honored. |\n| `max_restarts` | int | Maximum consecutive restart attempts before the toolset is marked `Failed`. `0` uses the profile default (5); `-1` means unlimited. |\n| `backoff.initial` | duration | First wait between attempts (Go duration: `500ms`, `1s`, …). Default: `1s`. |\n| `backoff.max` | duration | Cap on the wait between attempts. Default: `32s`. |\n| `backoff.multiplier` | number | Multiplier applied each attempt. Default: `2`. |\n| `backoff.jitter` | number | Fraction (0..1) of the computed delay applied as a uniform random offset. `0` disables jitter (default). |\n| `required` | boolean | Marks the toolset as critical. Today this is informational; a future eager-startup phase will refuse to start the agent when a required toolset cannot reach Ready. Defaults to `true` under `strict`, `false` otherwise. |\n| `startup_timeout` | duration | Cap on the initial connect+initialize duration. Enforced since v1.94.0: on expiry the toolset stays stopped and the runtime retries on the next turn. |\n| `call_timeout` | duration | Cap on an individual tool call, including one reconnect-retry. Enforced: on expiry the call is cancelled and surfaced to the model as a tool error; cancellation is propagated to the server. `0`/unset means no timeout — opt-in only, no profile default. |\n\n> [!NOTE]\n> **`required` is not yet enforced**\n>\n> The schema validates this field and the supervisor stores it, but no code path acts on it yet. It is documented now so config files written today keep working when the planned eager-startup phase lands. Picking the `strict` profile is forward-compatible — it will start enforcing `required=true` automatically.\n\n### Inspecting and restarting toolsets at runtime\n\nThe TUI exposes the supervisor through two slash commands:\n\n- `/tools` — the unified tools dialog. Its top section lists every toolset on the current agent with its lifecycle state (`Stopped`, `Starting`, `Ready`, `Degraded`, `Restarting`, `Failed`), restart count, and last error; its bottom section lists every tool the agent can call, grouped by category. Use this to answer both \"what can the agent do?\" and \"is anything degraded?\" with one command.\n- `/toolset-restart <name>` — force the supervisor to reconnect the named toolset. Useful after completing OAuth, when a remote MCP server has been redeployed, or when an LSP like `gopls` is stuck.\n\nSee the [TUI reference](../../features/tui/index.md) for the full list of slash commands.\n\nSee [`examples/lifecycle.yaml`](https://github.com/docker/docker-agent/blob/main/examples/lifecycle.yaml) for a complete lifecycle configuration example.\n\n## TOON-Encoded Tool Outputs\n\nMany MCP servers return verbose JSON responses that consume a lot of context budget. The `toon` field on a toolset transparently re-encodes matching tools' JSON output as [TOON](https://github.com/alpkeskin/gotoon) — a compact, model-friendly key/value format — before the result is shown to the model.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"          # toonify every tool from this MCP server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `toon`   | string | Comma-delimited list of regular expressions matching tool names whose JSON output should be re-encoded as TOON. Non-JSON outputs and non-matching tools are passed through untouched. |\n\nWhen a tool's output is not valid JSON, it is returned unchanged — TOON encoding is best-effort and never breaks tools that emit plain text.\n\n> [!NOTE]\n> **When to use TOON**\n>\n> TOON typically yields 30-60% smaller payloads than equivalent JSON for MCP tools that return arrays of records (issue lists, search results, file listings, …). It works best when the schema is regular; one-off responses with deeply nested or heterogeneous shapes may benefit less.\n\n## Per-Toolset Model Routing\n\nThe `model` field on a toolset overrides which LLM is invoked for the **next turn** after a tool from that toolset returns — letting you process simple tool results (file reads, knowledge-base lookups, shell stdout) with a cheaper or faster model while keeping the agent's primary model for reasoning.\n\n```yaml\nmodels:\n  primary:\n    provider: anthropic\n    model: claude-sonnet-4-5\n  fast:\n    provider: anthropic\n    model: claude-haiku-4-5\n\nagents:\n  root:\n    model: primary\n    toolsets:\n      - type: filesystem\n        model: fast            # process file reads with the fast model\n      - type: shell\n        model: fast            # ditto for shell stdout\n      - type: mcp\n        ref: docker:github-official\n        model: openai/gpt-4o-mini  # inline provider/model also works\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `model`  | string | Model used for the LLM turn that processes tool results from this toolset. Either a name from the `models:` section or an inline `provider/model` (e.g. `openai/gpt-4o-mini`). The override is **one-shot**: subsequent turns return to the agent's primary model. |\n\nWhen multiple tool calls in a single turn come from toolsets with different `model` overrides, the runtime picks the override of the **first** tool call that has one set. See [`examples/per_tool_model_routing.yaml`](https://github.com/docker/docker-agent/blob/main/examples/per_tool_model_routing.yaml) for a complete configuration.\n\n## Tool Filtering\n\nToolsets may expose many tools. Use the `tools` property to whitelist only the ones your agent needs. This works for any toolset type — not just MCP:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n  - type: filesystem\n    tools: [\"read_file\", \"search_files_content\"]\n  - type: shell\n    tools: [\"shell\"]\n```\n\n> [!TIP]\n> Filtering tools improves agent performance — fewer tools means less confusion for the model about which tool to use.\n\n## Tool Instructions\n\nAdd context-specific instructions that get injected when a toolset is loaded:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\nBy default, the `instruction:` field **replaces** the toolset's built-in instructions (if any). To keep the built-in guidance and add your own rules on top, include the `{ORIGINAL_INSTRUCTIONS}` placeholder anywhere in your instruction text. At runtime it expands to the toolset's default instructions:\n\n```yaml\ntoolsets:\n  # Enrich: keep built-in instructions, then add your own rules\n  - type: filesystem\n    instruction: |\n      {ORIGINAL_INSTRUCTIONS}\n\n      ## Project-specific rules\n      - Never modify files outside the `src/` directory.\n      - Always create a backup before overwriting a file.\n\n  # Enrich: prepend your rules before the built-in instructions\n  - type: shell\n    instruction: |\n      Important: only run commands inside the project root.\n      {ORIGINAL_INSTRUCTIONS}\n\n  # Replace: omit the placeholder to discard built-in instructions entirely\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Only read GitHub issues. Never create, edit, or close anything.\n```\n\nThree patterns at a glance:\n\n| Pattern | Description |\n| --- | --- |\n| `{ORIGINAL_INSTRUCTIONS}` then your text | Append your rules after the defaults |\n| Your text then `{ORIGINAL_INSTRUCTIONS}` | Prepend your rules before the defaults |\n| No placeholder | Replace the defaults entirely |\n\nSee [`examples/toolset_instructions.yaml`](https://github.com/docker/docker-agent/blob/main/examples/toolset_instructions.yaml) for a complete example.\n\n## Deferred Tool Loading\n\nLoad tools on-demand to speed up agent startup. When a toolset is deferred, its tools are registered lazily — the tool server process is not started until the agent first calls one of its tools. This is useful for large toolsets (e.g., an MCP server with hundreds of tools) where startup time matters.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  - type: mcp\n    ref: docker:slack\n    defer: true\n  - type: filesystem\n```\n\nOr defer specific tools within a toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer:\n      - \"list_issues\"\n      - \"search_repos\"\n```\n\nWhen `defer` is a list of tool names, only those specific tools are deferred; all other tools in the toolset load eagerly. Setting `defer: true` defers the entire toolset.\n\n### Tool Discovery with `search_tool`\n\nWhen an entire toolset is deferred (`defer: true`), the deferred toolset exposes two built-in tools to the agent:\n\n- **`search_tool`** — Discover available deferred tools by keyword. The search uses **fuzzy matching** against both tool names and descriptions: all characters of the query must appear in the target string in order (but not necessarily adjacently), so a query like `\"crfil\"` matches `\"create_file\"`. Returns a list of matching tool names with descriptions.\n- **`add_tool`** — Activate a discovered tool by name so it becomes available for use.\n\nThese tools let the agent browse a large toolset on-demand without activating every tool upfront.\n\nSee [`examples/deferred.yaml`](https://github.com/docker/docker-agent/blob/main/examples/deferred.yaml) for a complete example.\n\n## Combined Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Built-in tools\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n      - type: memory\n        path: ./dev.db\n      - type: user_prompt\n      # LSP for code intelligence\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      # Custom scripts\n      - type: script\n        shell:\n          run_tests:\n            description: Run the test suite\n            cmd: task test\n          lint:\n            description: Run the linter\n            cmd: task lint\n      # Custom API tool\n      - type: api\n        api_config:\n          name: get_status\n          method: GET\n          endpoint: \"https://api.example.com/status\"\n          instruction: Check service health\n      # Docker MCP tools\n      - type: mcp\n        ref: docker:github-official\n        tools: [\"list_issues\", \"create_issue\"]\n      - type: mcp\n        ref: docker:duckduckgo\n      # Remote MCP\n      - type: mcp\n        remote:\n          url: \"https://internal-api.example.com/mcp\"\n          transport_type: \"streamable\"\n          headers:\n            Authorization: \"Bearer ${env.INTERNAL_TOKEN}\"\n```\n\n> [!WARNING]\n> **Toolset Order Matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins. Order your toolsets intentionally.\n","_vendor/github.com/docker/docker-agent/docs/features/skills/index.md":"---\ntitle: \"Skills\"\ndescription: \"Skills provide specialized instructions that agents can load on demand when a task matches a skill's description.\"\nkeywords: docker agent, ai agents, features, skills\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/features/skills/\n---\n\n_Skills provide specialized instructions that agents can load on demand when a task matches a skill's description._\n\n## How Skills Work\n\n1. Docker Agent scans standard directories for `SKILL.md` files\n2. Skill metadata (name, description) is injected into the agent's system prompt\n3. When a user request matches a skill, the agent reads the full instructions\n4. The agent follows the skill's detailed instructions to complete the task\n\n## Enabling Skills\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills: true\n    toolsets:\n      - type: filesystem # required for reading skill files\n```\n\n> [!TIP]\n> Skills are perfect for encoding team-specific workflows (PR review, deployment, coding standards) that apply across projects.\n\n## Filtering Skills\n\nThe `skills` field also accepts a list, letting you restrict the agent to a specific subset of skills instead of exposing every discovered one. List items are classified automatically:\n\n- `\"local\"` or any `http://` / `https://` URL → a **source** to load skills from\n- any other string → the **name** of a skill to include\n\nWhen only names are given, local sources are used by default.\n\n```yaml\nagents:\n  # Load every discovered local skill (same as `skills: true`).\n  full:\n    skills: true\n\n  # Load local skills, but only expose \"commit\" and \"poem\".\n  scoped:\n    skills:\n      - commit\n      - poem\n\n  # Combine an explicit source with a name filter.\n  remote_filtered:\n    skills:\n      - https://skills.example.com\n      - commit\n\n  # Disable skills entirely.\n  none:\n    skills: false\n```\n\nA name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored.\n\n## Inline Skills\n\nInstead of (or alongside) loading skills from files and URLs, you can define skills directly in the agent config. An inline skill is a mapping item in the `skills` list, freely mixed with the string items above:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills:\n      - name: changelog\n        description: Write a concise changelog entry from a diff or description.\n        instructions: |\n          Produce a single changelog entry in Keep a Changelog style.\n          Pick the right category (Added, Changed, Fixed, Removed) and write\n          one imperative sentence summarising the user-visible change.\n\n      # A fork-mode inline skill runs in an isolated sub-agent.\n      - name: triage\n        description: Triage a bug report in an isolated context.\n        context: fork\n        instructions: |\n          Restate the problem, list likely root causes most-probable-first,\n          and propose the smallest reproduction and next concrete action.\n\n      # Inline skills mix freely with sources and name filters.\n      - local\n    toolsets:\n      - type: filesystem\n```\n\nInline skills carry their body in the config itself, so they need no `SKILL.md` file and require no filesystem source. They are **always exposed** — the name filter only applies to file- and URL-based skills. Because inline skills travel inside the agent YAML, they also work in `--sandbox` mode without any kit staging, and they can be shared with the agent via `share push`.\n\n### Inline Skill Fields\n\n| Field           | Required | Description                                                                |\n| --------------- | -------- | -------------------------------------------------------------------------- |\n| `name`          | Yes      | Skill identifier used by `read_skill` / `run_skill` and the `/<name>` command |\n| `description`   | Yes      | Short description shown to the agent for skill matching                    |\n| `instructions`  | Yes      | The skill body (what a `SKILL.md` would contain below its frontmatter)     |\n| `context`       | No       | Set to `fork` to run the skill as an isolated sub-agent                    |\n| `model`         | No       | Override the model used while running a fork-mode skill                    |\n| `allowed_tools` | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (glob or exact). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`      | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session on top of the inherited tools. |\n\n> [!NOTE]\n> **Inline vs. file-based skills**\n>\n> Inline skills support the subset of the SKILL.md format that fits in YAML. They cannot bundle supporting files (no `read_skill_file`) or use `` !`command` `` expansion. For skills that need bundled resources or executable helpers, use a `SKILL.md` directory instead.\n\n## SKILL.md Format\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\nlicense: Apache-2.0\nmetadata:\n  author: my-org\n  version: \"1.0\"\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Minimize image size by using slim base images\n4. Follow security best practices (non-root user, etc.)\n```\n\n### Frontmatter Fields\n\n| Field            | Required | Description                                                                 |\n| ---------------- | -------- | --------------------------------------------------------------------------- |\n| `name`           | Yes      | Unique skill identifier                                                     |\n| `description`    | Yes      | Short description shown to the agent for skill matching                     |\n| `context`        | No       | Set to `fork` to run the skill as an isolated sub-agent (see below)         |\n| `model`          | No       | Override the model used while running the skill as a sub-agent (fork only)  |\n| `allowed-tools`  | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (YAML list or comma-separated string). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`       | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session (YAML list or comma-separated string). |\n| `license`        | No       | License identifier (e.g. `Apache-2.0`)                                      |\n| `compatibility`  | No       | Free-text compatibility notes                                               |\n| `metadata`       | No       | Arbitrary key-value pairs (e.g. `author`, `version`)                        |\n\n## Running a Skill as a Sub-Agent\n\nBy default, when an agent invokes a skill it reads the instructions inline into its own conversation. For complex, multi-step skills this can consume a large portion of the agent's context window and pollute the parent conversation with intermediate tool calls.\n\nAdding `context: fork` to the SKILL.md frontmatter tells the agent to run the skill in an **isolated sub-agent** instead:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\n---\n\n# Bump Dependencies\n\n1. List outdated deps\n2. Update each one, run tests, commit or revert\n3. Produce a summary table\n```\n\nWhen the agent encounters a task that matches a `context: fork` skill, it uses the `run_skill` tool instead of `read_skill`. This:\n\n- **Spawns a child session** with the skill content as the system prompt and the caller's task as the user message\n- **Isolates the context window** — the sub-agent has its own conversation history, so lengthy tool-call chains don't eat into the parent's token budget\n- **Folds the result** — only the sub-agent's final answer is returned to the parent as the tool result\n- **Inherits the parent's model and tools** — the sub-agent can use all tools available to the parent agent (scope this with `allowed_tools` / `toolsets`, see [Scoping a fork skill's tools](#scoping-a-fork-skills-tools))\n\n> [!TIP]\n> **When to use context: fork**\n>\n> Use `context: fork` for skills that involve many steps, heavy tool usage, or that should not clutter the main conversation — for example dependency bumping, large refactors, or code generation pipelines.\n\n### Overriding the model for a fork skill\n\nFork skills can declare a `model` field in their frontmatter to use a\ndifferent model than the parent agent for the duration of the sub-session.\nThis is useful when a skill is best handled by a faster, cheaper, or more\nspecialised model — for example a powerful reasoning model for refactors,\nor a fast model for routine bookkeeping work. The override only applies\nwhile the skill is running; the parent agent keeps its own model.\n\nThe `model` value accepts either a named model from the agent config or\nan inline `provider/model` reference (and the same comma-separated alloy\nsyntax as the rest of the agent config):\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\nmodel: openai/gpt-4o-mini\n---\n\n# Bump Dependencies\n\n1. ...\n```\n\nIf the model reference cannot be resolved (unknown name, missing\ncredentials, runtime not configured for model switching, …) the skill\nfalls back to the agent's currently-active model (its configured\ndefault, or any override the user previously set via the model picker)\nand a warning is logged.\n\nWhen the skill completes, the agent's previous model is restored — but\nonly if no one else changed the model in the meantime. If the user\nswitches the model via the TUI model picker while the fork skill is\nrunning, their choice is preserved (the deferred restore becomes a\nno-op).\n\n### Scoping a fork skill's tools\n\nBy default a fork skill inherits the parent agent's entire tool set. Two\noptional fields let you scope what the sub-session can use. Both apply\n**only to fork-mode skills** and work the same whether the skill is\ninline or loaded from a `SKILL.md` file.\n\n`allowed_tools` (frontmatter: `allowed-tools`) is an **allow-list** over\nthe inherited tools: only tools whose names match an entry are kept,\neverything else is hidden from the sub-session. Entries support glob\npatterns (e.g. `read_*`) and otherwise match exactly. This is the\nClaude-Code-compatible `allowed-tools` field, now enforced for fork\nskills rather than merely recorded.\n\n`toolsets` references reusable [top-level toolsets](../../configuration/overview/index.md#reusable-toolsets-toolsets)\nby name. The referenced toolsets are exposed in the sub-session **in\naddition to** the inherited tools, and they bypass the `allowed_tools`\nfilter (the skill explicitly asked for them).\n\n```yaml\ntoolsets:\n  web:\n    type: fetch\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    toolsets:\n      - type: filesystem\n      - type: shell\n    skills:\n      # Inherits the parent tools but is restricted to read-only filesystem\n      # access while it runs — shell and write tools are hidden.\n      - name: audit\n        description: Review the repository layout without modifying anything.\n        context: fork\n        allowed_tools:\n          - read_file\n          - list_directory\n          - directory_tree\n        instructions: Inspect the repository structure and summarise it.\n\n      # Brings in the top-level `web` toolset on top of the parent's tools.\n      - name: research\n        description: Research a topic using web fetches in an isolated context.\n        context: fork\n        toolsets:\n          - web\n        instructions: Research the requested topic and summarise with links.\n```\n\nThe equivalent in a `SKILL.md` file uses frontmatter lists:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: research\ndescription: Research a topic using web fetches\ncontext: fork\nallowed-tools:\n  - fetch\ntoolsets:\n  - web\n---\n```\n\n> [!NOTE]\n> **Fork only**\n>\n> Both fields are rejected by config validation when set on a non-fork skill, and a `toolsets` entry that doesn't resolve to a top-level toolset is a load-time error.\n\n## Search Paths\n\nSkills are discovered from these locations (later overrides earlier):\n\n### Global\n\n| Path                | Search Type                             |\n| ------------------- | --------------------------------------- |\n| `~/.codex/skills/`  | Recursive (searches all subdirectories) |\n| `~/.claude/skills/` | Flat (immediate children only)          |\n| `~/.agents/skills/` | Recursive (searches all subdirectories) |\n\n### Project (from git root to current directory)\n\n| Path              | Search Type                                |\n| ----------------- | ------------------------------------------ |\n| `.claude/skills/` | Flat (cwd only)                            |\n| `.github/skills/` | Flat (each directory from git root to cwd) |\n| `.agents/skills/` | Flat (each directory from git root to cwd) |\n\n## Invoking Skills\n\nSkills can be invoked in multiple ways:\n\n- **Automatic:** The agent detects when your request matches a skill's description and loads it automatically\n- **Explicit:** Reference the skill name in your prompt: \"Use the create-dockerfile skill to...\"\n- **Slash command:** Use `/{skill-name}` to invoke a skill directly\n\n```bash\n# In the TUI, invoke skill directly:\n/create-dockerfile\n\n# Or mention it in your message:\n\"Create a dockerfile for my Python app (use the create-dockerfile skill)\"\n```\n\n## Precedence\n\nWhen multiple skills share the same name:\n\n1. Global skills load first\n2. Project skills load next, from git root toward current directory\n3. Skills closer to the current directory override those further away\n4. At the same directory level, `.agents/skills/` overrides `.github/skills/`\n\n## Skills in Sandbox Mode\n\nWhen you run an agent with [`--sandbox`](../../configuration/sandbox/index.md), the sandbox VM has its own filesystem with no access to your host's skill directories. Docker Agent handles this transparently via the [auto-kit](../../configuration/sandbox/index.md#auto-kit): every discovered local skill is staged into a per-agent kit on the host, run through best-effort secret redaction (see the [auto-kit](../../configuration/sandbox/index.md#secret-redaction) docs), and bind-mounted read-only into the sandbox so the agent sees the same skills inside the VM as on the host. No configuration is required — use `--no-kit` only if you explicitly want to run the sandbox without any host skills.\n\n## Creating a Skill\n\n```bash\n# Create the skill directory\n$ mkdir -p ~/.agents/skills/create-dockerfile\n\n# Write the SKILL.md file\n$ cat > ~/.agents/skills/create-dockerfile/SKILL.md << 'EOF'\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Use slim base images to minimize size\n4. Run as non-root user for security\nEOF\n```\n\nThe skill will automatically be available to any agent with skills enabled (`skills: true`, or a list that targets its name — see [Filtering Skills](#filtering-skills)).\n\n> [!NOTE]\n> **See also**\n>\n> Skills are enabled in the [Agent Config](../../configuration/agents/index.md) with the `skills` property (boolean or list). For tool-based capabilities, see [Tools](../../concepts/tools/index.md).\n>\n> Example configs: [`examples/skills_inline.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_inline.yaml) (inline skill definition), [`examples/skills_fork_toolsets.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_fork_toolsets.yaml) (scoping a fork skill's tools), [`examples/skills_filter.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_filter.yaml) (filtering which skills load).\n","_vendor/github.com/docker/docker-agent/docs/tools/_index.md":"---\ntitle: \"Built-in Tools\"\ndescription: \"Built-in toolsets agents can use out of the box.\"\nweight: 40\n---\n","_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md":"---\ntitle: \"A2A Tool\"\ndescription: \"Connect to remote agents via the Agent-to-Agent protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, a2a tool\nlinkTitle: \"A2A\"\nweight: 60\ncanonical: https://docs.docker.com/ai/docker-agent/tools/a2a/\n---\n\n_Connect to remote agents via the Agent-to-Agent protocol._\n\n## Overview\n\nThe A2A tool connects to a remote agent exposed over the A2A (Agent-to-Agent) protocol. Unlike [`handoff`](../handoff/index.md), which only targets local agents declared in the same config, `a2a` reaches out to an agent running on the network.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: a2a\n    url: \"http://localhost:8080/a2a\"\n    # Optional: custom tool name (defaults to a sanitized form of the URL / agent card name)\n    name: research_agent\n    # Optional: custom HTTP headers (typically for auth)\n    headers:\n      Authorization: \"Bearer ${env.A2A_TOKEN}\"\n      X-Tenant: \"acme\"\n```\n\nThe `Authorization` header shown above authenticates to endpoints served with `docker agent serve a2a --auth-token`.\n\n## Properties\n\n| Property   | Type             | Required | Description                                                                                              |\n| ---------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `url`      | string           | ✓        | A2A server endpoint URL (must include scheme).                                                           |\n| `name`     | string           | ✗        | Tool name registered for the remote agent. Defaults to a name derived from the server's agent card.     |\n| `headers`  | map\\[string\\]string | ✗     | Extra HTTP headers sent with every request (useful for `Authorization`, tenant selection, tracing, \\u2026). |\n\n> [!TIP]\n> **See also**\n>\n> For full details on the A2A protocol and serving agents as A2A endpoints, see [A2A Protocol](../../features/a2a/index.md).\n","_vendor/github.com/docker/docker-agent/docs/tools/api/index.md":"---\ntitle: \"API Tool\"\ndescription: \"Create custom tools that call HTTP APIs.\"\nkeywords: docker agent, ai agents, tools, toolsets, api tool\nlinkTitle: \"API\"\nweight: 240\ncanonical: https://docs.docker.com/ai/docker-agent/tools/api/\n---\n\n_Create custom tools that call HTTP APIs._\n\n## Overview\n\nThe API tool type lets you define custom tools that make HTTP requests to external APIs. This is useful for integrating agents with REST APIs, webhooks, or any HTTP-based service without writing code.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Integrating with REST APIs that don't have an MCP server\n> - Simple HTTP operations (GET, POST)\n> - Quick prototyping before building a full MCP server\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant with API access\n    instruction: You can look up weather information.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_weather\n          method: GET\n          endpoint: \"https://api.weather.example/v1/current?city=${city}\"\n          instruction: Get current weather for a city\n          args:\n            city:\n              type: string\n              description: City name to get weather for\n          required: [\"city\"]\n          headers:\n            Authorization: \"Bearer ${env.WEATHER_API_KEY}\"\n```\n\n## Properties\n\nThe `api` toolset accepts the following toolset-level fields in addition to the `api_config` block:\n\n| Property            | Type    | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `api_config`        | object  | ✓        | The HTTP tool definition. See the table below.                                                                                                                                                                                                                    |\n| `timeout`           | int     | ✗        | HTTP client timeout in seconds (default: `30`). Applies to every call the generated tool makes.                                                                                                                                                                   |\n| `allow_private_ips` | boolean | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the configured endpoint legitimately targets internal services. See [Reaching internal services](#reaching-internal-services). |\n\n### `api_config`\n\n| Property        | Type   | Required | Description                                      |\n| --------------- | ------ | -------- | ------------------------------------------------ |\n| `name`          | string | ✓        | Tool name (how the agent references it)          |\n| `method`        | string | ✓        | HTTP method: `GET` or `POST`                     |\n| `endpoint`      | string | ✓        | URL endpoint (supports `${param}` interpolation) |\n| `instruction`   | string | ✗        | Description shown to the agent                   |\n| `args`          | object | ✗        | Parameter definitions (JSON Schema properties)   |\n| `required`      | array  | ✗        | List of required parameter names                 |\n| `headers`       | object | ✗        | HTTP headers to include. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request, useful when docker agent is itself exposed as an HTTP server). |\n| `output_schema` | object | ✗        | JSON Schema for the response. Used by MCP / Code Mode consumers; tool responses are still returned to the model as raw strings.                                                                                          |\n\n## HTTP Methods\n\n### GET Requests\n\nFor GET requests, parameters are interpolated into the URL:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: search_users\n      method: GET\n      endpoint: \"https://api.example.com/users?q=${query}&limit=${limit}\"\n      instruction: Search for users by name\n      args:\n        query:\n          type: string\n          description: Search query\n        limit:\n          type: integer\n          description: Maximum results (default 10)\n      required: [\"query\"]\n```\n\n### POST Requests\n\nFor POST requests, parameters are sent as JSON in the request body:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: create_task\n      method: POST\n      endpoint: \"https://api.example.com/tasks\"\n      instruction: Create a new task\n      args:\n        title:\n          type: string\n          description: Task title\n        description:\n          type: string\n          description: Task description\n        priority:\n          type: string\n          enum: [\"low\", \"medium\", \"high\"]\n          description: Task priority\n      required: [\"title\"]\n      headers:\n        Content-Type: \"application/json\"\n        Authorization: \"Bearer ${env.API_TOKEN}\"\n```\n\n## URL Interpolation\n\nUse `${param}` syntax to insert parameter values into URLs:\n\n```yaml\nendpoint: \"https://api.example.com/users/${user_id}/posts/${post_id}\"\n```\n\nParameter values are inserted as strings by the template expansion. Add URL encoding in the template when needed (for example, `${encodeURIComponent(city)}`).\n\n## Headers\n\nHeaders can include environment variables:\n\n```yaml\nheaders:\n  Authorization: \"Bearer ${env.API_KEY}\"\n  X-Custom-Header: \"static-value\"\n  Content-Type: \"application/json\"\n```\n\n## Output Schema\n\nOptionally document the expected response format:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: get_user\n      method: GET\n      endpoint: \"https://api.example.com/users/${id}\"\n      instruction: Get user details by ID\n      args:\n        id:\n          type: string\n          description: User ID\n      required: [\"id\"]\n      output_schema:\n        type: object\n        properties:\n          id:\n            type: string\n          name:\n            type: string\n          email:\n            type: string\n          created_at:\n            type: string\n```\n\n## Example: GitHub API\n\n```yaml\nagents:\n  github_assistant:\n    model: openai/gpt-4o\n    description: Assistant that can query GitHub\n    instruction: You can look up GitHub repositories and users.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_repo\n          method: GET\n          endpoint: \"https://api.github.com/repos/${owner}/${repo}\"\n          instruction: Get information about a GitHub repository\n          args:\n            owner:\n              type: string\n              description: Repository owner (user or org)\n            repo:\n              type: string\n              description: Repository name\n          required: [\"owner\", \"repo\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n            Authorization: \"Bearer ${env.GITHUB_TOKEN}\"\n\n      - type: api\n        api_config:\n          name: get_user\n          method: GET\n          endpoint: \"https://api.github.com/users/${username}\"\n          instruction: Get information about a GitHub user\n          args:\n            username:\n              type: string\n              description: GitHub username\n          required: [\"username\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n```\n\n## Limitations\n\n- Only supports GET and POST methods\n- Response body is limited to 1MB\n- Default 30-second timeout per request (override with the `timeout` field)\n- Only HTTP and HTTPS URLs are supported\n- No support for file uploads or multipart forms\n- By default, requests to non-public IP ranges (loopback, RFC1918, link-local, the cloud-metadata endpoint, multicast, the unspecified address) are refused at dial time — even when DNS for an otherwise-public host resolves there. Set `allow_private_ips: true` to disable that check.\n\n## Reaching internal services\n\n```yaml\ntoolsets:\n  - type: api\n    timeout: 60\n    allow_private_ips: true\n    api_config:\n      name: get_local_status\n      method: GET\n      endpoint: \"http://localhost:8080/health\"\n      instruction: Check the local service health\n```\n\n> [!WARNING]\n> **SSRF**\n>\n> Setting `allow_private_ips: true` re-exposes the SSRF surface for this tool. Only enable it when the configured `endpoint` is a trusted internal service — a prompt-injected agent cannot redirect the call elsewhere because the endpoint is fixed in config, but redirects from the configured host can still reach unexpected places.\n\n> [!TIP]\n> **For Complex APIs**\n>\n> For APIs that need authentication flows, pagination, or complex request/response handling, consider using an MCP server instead. The API tool is best for simple, stateless HTTP operations.\n\n> [!WARNING]\n> **Security**\n>\n> API keys and tokens in headers are visible in debug logs. Use environment variables (`${env.VAR}`) rather than hardcoding secrets in configuration files.\n","_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md":"---\ntitle: \"Background Agents Tool\"\ndescription: \"Dispatch work to sub-agents concurrently and collect results asynchronously.\"\nkeywords: docker agent, ai agents, tools, toolsets, background agents tool\nlinkTitle: \"Background Agents\"\nweight: 90\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-agents/\n---\n\n_Dispatch work to sub-agents concurrently and collect results asynchronously._\n\n## Overview\n\nThe background agents tool lets an orchestrator dispatch work to sub-agents concurrently and collect results asynchronously. Unlike [transfer_task](../transfer-task/index.md) (which blocks until the sub-agent finishes), background agent tasks run in parallel — the orchestrator can start several tasks, do other work, and check on them later.\n\n## Available Tools\n\n| Tool                     | Description                                                     |\n| ------------------------ | --------------------------------------------------------------- |\n| `run_background_agent`   | Start a sub-agent task in the background; returns a task ID     |\n| `list_background_agents` | List all background tasks with their status and runtime         |\n| `view_background_agent`  | View live output or final result of a task by ID                |\n| `stop_background_agent`  | Cancel a running task by ID                                     |\n\n### `run_background_agent` parameters\n\n| Parameter         | Type   | Required | Description                                                                 |\n| ----------------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to run. Must be listed under the caller's `sub_agents`. |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.        |\n| `expected_output` | string | ✗        | Optional description of the result format the caller expects.               |\n\n`run_background_agent` returns a **task ID** string. Tools run by the sub-agent inherit the parent session's permissions. Because background tasks run non-interactively, any tool call that would normally prompt the user for approval will be automatically denied. To allow background agents to run mutating tools, you must explicitly approve them in the parent session (e.g. via YOLO mode or explicit allow rules).\n\nBackground delegation shares the same runtime guards as `transfer_task`: delegation cycles are rejected and chains are capped at 10 nested delegations. See [Delegation Limits](../transfer-task/index.md#delegation-limits).\n\n### `view_background_agent` and `stop_background_agent` parameters\n\n| Parameter | Type   | Required | Description                                                    |\n| --------- | ------ | -------- | -------------------------------------------------------------- |\n| `task_id` | string | ✓        | Task ID returned by `run_background_agent` or `list_background_agents`. |\n\n`list_background_agents` takes no parameters.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: background_agents\n```\n\nNo configuration options. Requires the agent to have `sub_agents` configured so the background tasks have agents to dispatch to.\n\n## Example\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Orchestrates parallel research\n    instruction: Fan out research tasks and synthesize results.\n    sub_agents: [researcher]\n    toolsets:\n      - type: background_agents\n      - type: think\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Research topics thoroughly.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\n> [!TIP]\n> **When to Use**\n>\n> Use `background_agents` when your orchestrator needs to fan out work to multiple specialists in parallel — for example, researching several topics simultaneously or running independent code analyses side by side.\n\nIn the TUI, each background task's token usage is accounted for live: the sidebar's Agents panel shows the sub-agent's context usage percentage on its roster row, the Agent Inspector shows its exact token counts, and the task's cost joins the session total.\n\n## Using Harness Sub-Agents\n\nBackground agents work equally well with [harness-backed sub-agents](../../features/harnesses/index.md) — sub-agents driven by external coding CLIs such as Claude Code or Codex. This lets you dispatch multiple independent coding tasks in parallel:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Orchestrator that fans out coding tasks\n    instruction: |\n      Dispatch the frontend and backend tasks in parallel,\n      then collect results and produce a summary.\n    sub_agents:\n      - claude-coder\n      - codex-coder\n    toolsets:\n      - type: background_agents\n\n  claude-coder:\n    description: Frontend specialist (Claude Code)\n    harness:\n      type: claude-code\n      effort: medium\n\n  codex-coder:\n    description: Backend specialist (Codex)\n    harness:\n      type: codex\n```\n\nThe orchestrator calls `run_background_agent` for each coding task, then uses `list_background_agents` and `view_background_agent` to collect results when they finish.\n\n> [!NOTE]\n> **Harness toolsets are ignored**\n>\n> Harness agents use the external CLI's own tools — any `toolsets:` configured on the harness agent are silently ignored. See [Coding Harnesses](../../features/harnesses/index.md) for details and caveats.\n\nSee [`examples/coding_harness_background_agents.yaml`](https://github.com/docker/docker-agent/blob/main/examples/coding_harness_background_agents.yaml) for a complete configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md":"---\ntitle: \"Background Jobs Tool\"\ndescription: \"Run and manage long-running shell commands.\"\nkeywords: docker agent, ai agents, tools, toolsets, background jobs, shell\nlinkTitle: \"Background Jobs\"\nweight: 21\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-jobs/\n---\n\n_Run and manage long-running shell commands._\n\n## Overview\n\nThe `background_jobs` toolset starts shell commands that should keep running while the agent continues with other work, such as local servers, file watchers, long builds, or test suites. It returns a job ID immediately, captures combined stdout/stderr up to 10 MB per job, and terminates all running jobs when the agent session ends.\n\nUse the [`shell`](../shell/index.md) toolset for short synchronous commands. Add both toolsets when an agent needs both synchronous commands and long-running processes.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n  - type: background_jobs\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                                                                          |\n| -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `env`    | object  | Environment variables to set for all background job commands.                                                                                        |\n| `recall` | boolean | Let `run_background_job` expose a `recall` parameter so jobs can steer the agent when they finish (see [Background job recall](#background-job-recall)). Default `false`. |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Background job recall\n\nSet `recall: true` to let the `run_background_job` tool expose a `recall` boolean parameter:\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    recall: true\n```\n\nWhen the agent starts a background job with `recall: true`, Docker Agent sends a steering message back into the running agent loop after the job finishes. The message contains a short completion sentence and the job output, so the agent can react without polling `view_background_job`.\n\nUse recall for finite background work where completion matters (for example, a long build or test suite). Avoid it for servers and watchers that are expected to run until stopped. See [`examples/shell_recall.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shell_recall.yaml) for a complete configuration.\n\n## Available Tools\n\nThe background jobs toolset exposes five tools:\n\n| Tool Name              | Description                                                                                    |\n| ---------------------- | ---------------------------------------------------------------------------------------------- |\n| `run_background_job`   | Start a command asynchronously and return a job ID immediately. Use for servers/watchers/etc. |\n| `list_background_jobs` | List all background jobs with their status, runtime, and metadata.                             |\n| `view_background_job`  | View the buffered output and status of a specific background job by ID.                        |\n| `stop_background_job`  | Stop a running background job. Child processes are terminated too.                             |\n| `wait_background_job`  | Block until a job finishes and return its exit code and output. Safe on already-finished jobs. |\n\n### `run_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                                                 |\n| --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute in the background.                                                                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                                                                                     |\n| `recall`  | boolean | ✗        | Only available when the `background_jobs` toolset has `recall: true`. When true, send a steering message with the job output when it finishes. |\n\n`view_background_job` and `stop_background_job` each take a single required `job_id` string returned by `run_background_job` or `list_background_jobs`.\n\n### `wait_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                    |\n| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------- |\n| `job_id`  | string  | ✓        | Job ID returned by `run_background_job` or `list_background_jobs`.                                             |\n| `timeout` | integer | ✗        | Maximum seconds to wait (default: `60`). If the job is still running when the limit fires, the tool returns the current output with a notice and the job continues in the background. |\n\n> [!WARNING]\n> **Safety**\n>\n> Background jobs run shell commands with the same access as the agent process. Stop servers and watchers when they are no longer needed, and use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n","_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md":"---\ntitle: \"Fetch Tool\"\ndescription: \"Read content from HTTP/HTTPS URLs.\"\nkeywords: docker agent, ai agents, tools, toolsets, fetch tool\nlinkTitle: \"Fetch\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/tools/fetch/\n---\n\n_Read content from HTTP/HTTPS URLs._\n\n## Overview\n\nThe fetch tool lets agents retrieve content from one or more HTTP/HTTPS URLs. It is **read-only** — only `GET` requests are supported. The tool respects `robots.txt`, limits response size (1 MB per URL), and can return content as plain text, Markdown (converted from HTML), or raw HTML.\n\n> [!NOTE]\n> **GET only**\n>\n> The fetch tool does **not** support `POST`, `PUT`, `DELETE` or other methods, and does not expose request bodies or per-call custom headers (the toolset can still attach static [credential headers](#custom-headers) to every request). To call REST endpoints with other verbs, use the [API tool](../api/index.md) or an [OpenAPI toolset](../openapi/index.md).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: fetch\n```\n\n### Options\n\n| Property            | Type          | Default | Description                                                                                                                                                                                                                                                                                                      |\n| ------------------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `timeout`           | int           | `30`    | Default request timeout in seconds (overridable per tool call).                                                                                                                                                                                                                                                  |\n| `allowed_domains`   | array[string] | _none_  | Allow-list of hosts the tool may fetch. When set, every URL whose host is **not** in the list is rejected before any network call is made. Mutually exclusive with `blocked_domains`.                                                                                                                            |\n| `blocked_domains`   | array[string] | _none_  | Deny-list of hosts the tool must not fetch. URLs whose host matches one of these patterns are rejected before any network call (including `robots.txt`) is made. Mutually exclusive with `allowed_domains`.                                                                                                      |\n| `allow_private_ips` | boolean       | `false` | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast, and the unspecified address). Required to reach `localhost` / internal services. See [SSRF protection](#ssrf-protection-and-reaching-localhost) below. |\n| `headers`           | map[string]string | _none_ | Static HTTP headers attached to **every** request the toolset issues (including `robots.txt`). Values support `${env.VAR}` for secrets. Caller-supplied entries override the default `User-Agent` and the format-driven `Accept` header. Headers are stripped on cross-host redirects so credentials never leak to a third-party host. See [Custom headers](#custom-headers) below. |\n\n### Domain matching\n\nDomain patterns in `allowed_domains` and `blocked_domains` use the following rules (case-insensitive):\n\n- **Bare domain** — `example.com` matches the host `example.com` _and_ any subdomain such as `docs.example.com`. It does **not** match unrelated hosts that share a suffix (e.g. `badexample.com`).\n- **Leading dot** — `.example.com` matches **only** strict subdomains (`docs.example.com`, `a.b.example.com`), not the apex `example.com`.\n- **Wildcard glob** — `*.example.com` is an alias for the leading-dot form; the apex is excluded. The `*` is only valid as a leading `*.` token (entries like `foo.*`, `*.*.example.com`, or a bare `*` are rejected at config-load time).\n- **IP literal** — IP addresses are matched exactly (`169.254.169.254`).\n- **CIDR range** — `169.254.0.0/16`, `10.0.0.0/8`, `::1/128`, `fc00::/7`. Matches when the URL's host parses as an IP inside the network. Hostname hosts never match a CIDR pattern. Malformed CIDRs are rejected at config-load time.\n- **Trailing dots** in FQDN-form URLs (`http://example.com./`) are stripped before matching, so they cannot bypass a deny-list entry.\n\nThe lists are mutually exclusive: a single fetch toolset may set either `allowed_domains` or `blocked_domains`, but not both.\n\nWhen a list is configured, every redirect target is re-checked against the same list. A request to an allowed origin that redirects to a forbidden host is rejected before any data is read from the redirect.\n\n> [!WARNING]\n> **Limitations**\n>\n> Matching is purely string-based on the URL host. It does **not** perform DNS resolution and does **not** normalise alternative IP encodings (decimal `2852039166`, hex `0xa9.0xfe.0xa9.0xfe`, octal, etc. IPv4-mapped IPv6 addresses ARE normalized to their IPv4 form). If you need to deny access to a specific IP, also list its alternative encodings, or block at the network layer.\n\n### Custom Timeout\n\n```yaml\ntoolsets:\n  - type: fetch\n    timeout: 60\n```\n\n### Custom headers\n\nAttach static headers — typically credentials — to every request. Values support `${env.VAR}` interpolation so secrets stay out of YAML, and headers are dropped on cross-host redirects so a redirect chain cannot leak them to a third-party host:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docs.internal.example.com\n    headers:\n      Authorization: \"Bearer ${env.INTERNAL_DOCS_TOKEN}\"\n      X-Internal-Client: \"docker-agent\"\n```\n\n> [!WARNING]\n> **Pair credential headers with an allow-list**\n>\n> When `headers` carries credentials (e.g. `Authorization`), set `allowed_domains` to the specific hosts that should receive them. Stdlib already strips a small allow-list (`Authorization`, `Cookie`, `WWW-Authenticate`) on cross-domain redirects, and the fetch tool additionally strips every operator-supplied header on cross-host redirects — but an allow-list is the strongest guarantee against accidental exfiltration.\n\n### Restrict to specific domains\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docker.com          # docker.com and *.docker.com\n      - github.com          # github.com and *.github.com\n      - .githubusercontent.com  # only subdomains, e.g. raw.githubusercontent.com\n```\n\n### Block sensitive hosts\n\n```yaml\ntoolsets:\n  - type: fetch\n    blocked_domains:\n      - 169.254.169.254       # cloud metadata endpoint (literal IP)\n      - 169.254.0.0/16        # entire link-local range (CIDR)\n      - 10.0.0.0/8            # RFC1918 private range\n      - \"*.internal.example.com\"  # any subdomain (wildcard)\n      - internal.example.com  # internal corporate hostname\n```\n\n> [!NOTE]\n> **Already blocked by default**\n>\n> You do **not** need to add loopback, RFC1918, link-local (incl. `169.254.169.254`), multicast or the unspecified address to `blocked_domains` to be safe — the fetch tool already refuses connections to those ranges at dial time, after DNS resolution. The example above is only useful if you also want to reject those hosts _before_ any network call (and to surface a clearer error message to the agent), or if you have set `allow_private_ips: true` and want to deny a specific subset.\n\n### SSRF protection and reaching localhost\n\nBy default, the fetch tool refuses connections to **non-public IP addresses** — even when DNS for an otherwise-public host resolves to one of them (so DNS rebinding is also blocked). The check happens at dial time, after DNS resolution, and rejects:\n\n- **Loopback** — `127.0.0.0/8`, `::1` (this is what blocks `http://localhost/...` and `http://127.0.0.1/...`)\n- **RFC1918 private ranges** — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n- **Link-local** — `169.254.0.0/16` (IPv4, including the cloud-metadata endpoint `169.254.169.254`) and `fe80::/10` (IPv6)\n- **Multicast** and the **unspecified** address (`0.0.0.0`, `::`)\n- **IPv4-mapped IPv6** — addresses like `::ffff:127.0.0.1` or `::ffff:169.254.169.254` are normalized to their IPv4 form and blocked accordingly\n\nThis is the default because LLM-driven fetches are a classic Server-Side Request Forgery (SSRF) vector: a prompt-injected URL can otherwise reach internal services, cloud metadata, or admin interfaces on the host running the agent.\n\nIf an agent legitimately needs to call **localhost** or an **internal service**, opt in with `allow_private_ips: true`:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allow_private_ips: true\n    allowed_domains:\n      - localhost\n      - 127.0.0.1\n      - 10.0.0.0/8            # internal corporate range\n```\n\n> [!WARNING]\n> **Pair with an allow-list**\n>\n> Setting `allow_private_ips: true` alone re-exposes the SSRF surface. We strongly recommend combining it with an `allowed_domains` entry that restricts the tool to the specific internal hosts or CIDRs the agent actually needs (e.g. `localhost`, `127.0.0.1`, or your internal CIDR).\n>\n> **Note:** `allowed_domains` is checked _before_ DNS resolution (string-based on hostname), while the SSRF check happens _after_ DNS resolution (on the resolved IP). This means `allowed_domains` and `blocked_domains` are evaluated independently of `allow_private_ips` and continue to apply. A public hostname in `allowed_domains` that resolves to a private IP will still be blocked unless `allow_private_ips: true` is set.\n\n## Tool Interface\n\nThe toolset exposes a single tool, `fetch`, with the following parameters:\n\n| Parameter | Type           | Required | Description                                                                                                 |\n| --------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------- |\n| `urls`    | array[string]  | ✓        | One or more HTTP/HTTPS URLs to fetch (all via `GET`).                                                       |\n| `format`  | string         | ✓        | Output format: `text`, `markdown`, or `html`. HTML responses are converted to text/markdown when requested. |\n| `timeout` | integer        | ✗        | Per-call request timeout in seconds. Overrides the toolset default. Valid range: `1`–`300`.                 |\n\nResponses are capped at **1 MB** per URL. Hosts that disallow the agent's user-agent via `robots.txt` are skipped with a clear error.\n\n> [!TIP]\n> **Fetch vs. API Tool**\n>\n> Use `fetch` when the agent needs to read arbitrary public URLs at runtime. Use the [API tool](../api/index.md) to expose specific, structured HTTP endpoints (including non-`GET` verbs) as named tools.\n\n## Domain Filtering\n\nThe `allowed_domains`, `blocked_domains`, and `allow_private_ips` options let you control which hosts the fetch tool may reach. The complete reference is in the [Options](#options) table and [Domain matching](#domain-matching) section above.\n\n**Key points:**\n\n- `allowed_domains` — allow-list; only listed hosts (and their subdomains for bare-domain entries) are reachable\n- `blocked_domains` — deny-list; mutually exclusive with `allowed_domains` (a config error is thrown if both are set)\n- `allow_private_ips` — defaults to `false`; set to `true` to reach loopback / RFC-1918 / link-local addresses\n- The same `allow_private_ips` flag is also supported on `api`, `openapi`, `a2a`, and remote `mcp` toolsets\n\nSee [`examples/fetch_domain_filtering.yaml`](https://github.com/docker/docker-agent/blob/main/examples/fetch_domain_filtering.yaml) for a complete filtering example, and [`examples/remote_mcp_allow_private_ips.yaml`](https://github.com/docker/docker-agent/blob/main/examples/remote_mcp_allow_private_ips.yaml) for the equivalent pattern on remote MCP toolsets.\n","_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md":"---\ntitle: \"Filesystem Tool\"\ndescription: \"Read, write, list, search, and navigate files and directories.\"\nkeywords: docker agent, ai agents, tools, toolsets, filesystem tool\nlinkTitle: \"Filesystem\"\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/tools/filesystem/\n---\n\n_Read, write, list, search, and navigate files and directories._\n\n## Overview\n\nThe filesystem tool gives agents the ability to explore codebases, read and edit files, create new files, search across files, and navigate directory structures.\n\n### Path resolution\n\nPaths are resolved relative to the **working directory** (the directory where the agent session started, or the directory specified with `--workdir`):\n\n- **Relative paths** (e.g., `src/main.go`, `../README.md`) are joined with the working directory.\n- **Absolute paths** must match the host operating system:\n  - Unix/Linux/macOS: `/home/user/project/file.txt`\n  - Windows: `C:\\Users\\user\\project\\file.txt` or `C:/Users/user/project/file.txt`\n- **Home directory expansion**: paths starting with `~` or `~/` expand to the user's home directory.\n\nWhen a file is not found, error messages include the resolved absolute path to help diagnose incorrect base directories or path formats.\n\n> [!IMPORTANT]\n> Agents must use paths appropriate for the host OS. A Windows absolute path like `C:\\file.txt` on a Unix system (or vice versa) is rejected with a clear error message.\n\n### Empty directory detection\n\nWhen `list_directory` encounters an empty directory or a directory where all entries are hidden by ignore patterns (e.g., only a `.git` folder when `ignore_vcs: true`), it explicitly reports the state:\n\n- **Empty directory**: \"Directory is empty: /path/to/dir\"\n- **All entries ignored**: \"Directory has no visible entries (N hidden by ignore patterns): /path/to/dir\"\n\nThis helps agents distinguish between an empty directory and a tool failure, avoiding unnecessary retries with shell commands.\n\n## Available Tools\n\n| Tool                   | Description                                                               |\n| ---------------------- | ------------------------------------------------------------------------- |\n| `read_file`            | Read the contents of a file (whole file, or a line range of a text file)  |\n| `read_multiple_files`  | Read several files in one call (more efficient than multiple `read_file`) |\n| `write_file`           | Create or overwrite a file with new content                               |\n| `edit_file`            | Make line-based edits (find-and-replace) in an existing file. Each edit must specify a non-empty `oldText` to match and replace; empty `oldText` values are rejected with an error. |\n| `list_directory`       | List files and directories at a given path (explicitly reports empty directories) |\n| `directory_tree`       | Recursive tree view of a directory                                        |\n| `create_directory`     | Create a new directory (creates parent directories as needed)             |\n| `remove_directory`     | Remove an empty directory                                                 |\n| `search_files_content` | Search for text or regex patterns across files                            |\n\n## edit_file Validation\n\nThe `edit_file` tool applies a sequence of find-and-replace edits to a file in memory, then writes the result back atomically. Each edit must provide a non-empty `oldText` value:\n\n- **Valid**: `{\"oldText\": \"line one\", \"newText\": \"LINE ONE\"}`\n- **Invalid**: `{\"oldText\": \"\", \"newText\": \"INJECTED\"}` — rejected with error\n\nAn empty `oldText` is never a meaningful edit: Go's `strings.Contains(s, \"\")` is always `true`, and `strings.Replace(s, \"\", new, 1)` silently inserts at offset 0. Without validation, this would prepend content to the file while still reporting success. The tool now returns an explicit error (\"oldText must not be empty\") when an edit has an empty `oldText`, and no changes are written to disk.\n\nWhen a sequence contains multiple edits and a later one is rejected, the entire operation fails and the file is left untouched — edits are applied in memory and only written once at the end, so partial application is not possible.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: filesystem\n```\n\n### Options\n\n| Property | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ignore_vcs` | boolean | `true` | When `true` (default), `.git` directories and `.gitignore` patterns are excluded from listings and searches. Set to `false` to include them. |\n| `post_edit` | array | `[]` | Commands to run after editing files matching a path pattern |\n| `post_edit[].path` | string | — | Glob pattern for files (e.g., `*.go`, `src/*/*.ts`) |\n| `post_edit[].cmd` | string | — | Command to run (use `${file}` for the edited file path) |\n| `allow_list` | array | `[]` | Directories the tools may access. Empty = unrestricted (default). |\n| `deny_list` | array | `[]` | Directories the tools must not access. Takes precedence over `allow_list`. |\n\n### Path access control\n\nBy default the filesystem tools are unrestricted: relative paths resolve\nfrom the working directory, but absolute paths and `..` traversals can\nreach anywhere the agent process can. Configure `allow_list` and/or\n`deny_list` to sandbox the toolset.\n\nEntries in either list are expanded as follows:\n\n- `\".\"` — the agent's working directory\n- `\"~\"` or `\"~/...\"` — the user's home directory\n- `\"$VAR\"` / `\"${VAR}\"` / `\"${env.VAR}\"` — environment variable expansion\n- absolute paths — used as-is\n- relative paths — anchored at the working directory\n\nSymlinks are resolved before the containment check, so a symlink inside an\nallowed root cannot be used to escape it. When an `allow_list` is set,\neach entry is opened as a Go [`*os.Root`](https://pkg.go.dev/os#Root) so\nthat the kernel's rooted-lookup semantics also reject `..` and symlink\nescapes at I/O time, not just at resolve time.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    # Restrict every operation to the working directory and the user's\n    # home folder, then carve credentials out of the home folder.\n    allow_list:\n      - \".\"\n      - \"~\"\n    deny_list:\n      - \"~/.ssh\"\n      - \"~/.aws\"\n```\n\nWhen the path supplied by the agent is rejected, the tool returns a\nstructured error rather than performing any filesystem I/O. This makes the\nrestriction visible to the model so it can adjust its plan.\n\n### Post-Edit Hooks\n\nAutomatically run formatting, linting, or other commands after the agent edits a file. The command fires once per file after each edit operation (`write_file` and `edit_file`). Use `${file}` as a placeholder for the absolute path of the edited file.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    ignore_vcs: false\n    post_edit:\n      - path: \"*.go\"\n        cmd: \"gofmt -w ${file}\"\n      - path: \"*.ts\"\n        cmd: \"prettier --write ${file}\"\n      - path: \"src/*/*.py\"\n        cmd: \"black ${file}\"\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `path` | string | Glob pattern matched against the file path. `*.go` matches any `.go` file; `src/*/*.ts` matches `.ts` files inside `src/`. |\n| `cmd` | string | Shell command to run. `${file}` expands to the absolute path of the just-edited file. |\n\nPost-edit commands run with the same working directory as the agent. If a command exits non-zero, the error is logged and surfaced to the model as a warning, but the edit is not rolled back.\n\nSee [`examples/post_edit.yaml`](https://github.com/docker/docker-agent/blob/main/examples/post_edit.yaml) for a complete example.\n","_vendor/github.com/docker/docker-agent/docs/tools/git/index.md":"---\ntitle: \"Git Tool\"\ndescription: \"Read-only inspection of the working git repository.\"\nkeywords: docker agent, ai agents, tools, toolsets, git tool\nlinkTitle: \"Git\"\nweight: 125\ncanonical: https://docs.docker.com/ai/docker-agent/tools/git/\n---\n\n_Read-only inspection of the working git repository._\n\n## Overview\n\nThe git toolset gives an agent structured, **read-only** access to the working repository — status, history, branches, a commit's changes, and line-level authorship. It is implemented with go-git, so it needs **no `git` binary**.\n\nCompared with running `git` through the `shell` tool, the git toolset returns clean, structured output the model can read reliably, is **safe by construction** (no command can modify the repository), and works even when `shell` is disabled or no `git` binary is installed.\n\n> [!NOTE]\n> The git toolset is read-only. To stage, commit, or check out, use the [`shell`](../shell/index.md) tool.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: git\n```\n\nNo configuration options. The repository is opened at the agent's working directory; a subdirectory still resolves to the repository root.\n\n> [!WARNING]\n> **The repository is discovered by walking up parent directories.** If the working\n> directory is not itself a repository but an ancestor is (for example a\n> home directory tracked as dotfiles), the toolset resolves to that ancestor and\n> `git_show` / `git_blame` can expose its full history and file contents. The\n> filesystem toolset's allow/deny lists do **not** apply here. Only enable this\n> toolset where the surrounding repository is safe to read.\n\n> [!NOTE]\n> **Performance.** go-git is pure Go, which costs speed on large repositories:\n> `git_status` rehashes the whole worktree, and `git_blame` scales with history\n> depth times file size — its 400-line output cap is applied *after* the full\n> computation, so it does not make blaming a large file cheaper.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `git_status` | Current branch and changed files (staged / unstaged / untracked). |\n| `git_log` | Recent commits (hash, date, author, subject). |\n| `git_branches` | Local branches, current one marked with `*`. |\n| `git_show` | A commit's metadata, message, and changed files with +/- counts. |\n| `git_blame` | Line-by-line authorship for a file. |\n\n### `git_log`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `limit` | No | Maximum number of commits to return (default 20). |\n| `path` | No | Only show commits that touch this path. |\n\n### `git_show`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `ref` | No | Commit hash or revision to show (default HEAD). |\n\n### `git_blame`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `path` | Yes | File path to blame, relative to the repository root. |\n| `rev` | No | Commit or revision to blame at (default HEAD). |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A code review assistant\n    instruction: |\n      Review the working changes: check git_status, then git_show the latest\n      commit, and summarize what changed.\n    toolsets:\n      - type: git\n      - type: filesystem\n```\n\nExample `git_status` output:\n\n```text\nOn branch master\n1 changed file(s) [XY = staged/worktree; M=modified A=added D=deleted R=renamed ?=untracked]:\n   M main.go\n```\n\n> [!TIP]\n> **When to use**\n>\n> Use the git toolset whenever the agent needs repository context — before editing, to review recent history, or to find who last touched a line — without exposing the writable `shell` surface.\n","_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md":"---\ntitle: \"Handoff Tool\"\ndescription: \"Hand off the active conversation to another local agent defined in the same config.\"\nkeywords: docker agent, ai agents, tools, toolsets, handoff tool\nlinkTitle: \"Handoff\"\nweight: 70\ncanonical: https://docs.docker.com/ai/docker-agent/tools/handoff/\n---\n\n_Hand off the active conversation to another local agent defined in the same config._\n\n## Overview\n\nThe `handoff` tool lets an agent transfer control of the **current conversation** to another agent in the **same config file**. Unlike [`transfer_task`](../transfer-task/index.md), which delegates a sub-task and collects the result, `handoff` rewires the session so the receiving agent continues the conversation directly with the user.\n\nThis is the core mechanism for **handoffs routing** — a pattern where a router agent classifies the user's request and hands it off to a specialist, which then owns the rest of the session.\n\n> [!NOTE]\n> **Local only**\n>\n> The `handoff` tool only targets agents declared in the **same** config file by their local name. It does **not** open network connections. To delegate to a remote agent over the network, use the [A2A toolset](../a2a/index.md) instead.\n\n## Configuration\n\nThe tool is enabled implicitly when an agent declares a non-empty `handoffs:` list. You do **not** add `- type: handoff` under `toolsets:` — it is not a toolset type.\n\n```yaml\nagents:\n  router:\n    model: openai/gpt-4o\n    description: Routes questions to the right specialist\n    instruction: |\n      Classify the user's question and hand off to the most appropriate\n      specialist. If unsure, ask a clarifying question first.\n    handoffs: [billing, support]\n\n  billing:\n    model: openai/gpt-4o\n    description: Billing specialist\n    instruction: Answer billing questions.\n\n  support:\n    model: openai/gpt-4o\n    description: Technical support specialist\n    instruction: Help with technical issues.\n```\n\nThe router agent automatically gets a `handoff` tool it can call to switch the conversation to `billing` or `support`.\n\n## Tool Interface\n\nThe `handoff` tool takes a single parameter:\n\n| Parameter | Type   | Required | Description                                                       |\n| --------- | ------ | -------- | ----------------------------------------------------------------- |\n| `agent`   | string | ✓        | The local name of the agent to hand off the conversation to.      |\n\nOnly names listed in the current agent's `handoffs:` field are valid targets.\n\n> [!TIP]\n> **See also**\n>\n> For sub-task delegation (caller stays in control, waits for the result), see [Transfer Task](../transfer-task/index.md). For remote agent connections over the network, see the [A2A toolset](../a2a/index.md). For the broader pattern, see [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).\n","_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md":"---\ntitle: \"LSP Tool\"\ndescription: \"Connect to Language Server Protocol servers for code intelligence.\"\nkeywords: docker agent, ai agents, tools, toolsets, lsp tool\nlinkTitle: \"LSP\"\nweight: 220\ncanonical: https://docs.docker.com/ai/docker-agent/tools/lsp/\n---\n\n_Connect to Language Server Protocol servers for code intelligence._\n\n## Overview\n\nThe LSP tool connects your agent to any Language Server Protocol (LSP) server, providing comprehensive code intelligence capabilities like go-to-definition, find references, diagnostics, and more.\n\n> [!NOTE]\n> **What is LSP?**\n>\n> The [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) is a standard for providing language features like autocomplete, go-to-definition, and diagnostics. Most programming languages have LSP servers available.\n\n## Available Tools\n\nThe LSP toolset provides these tools to the agent:\n\n| Tool                    | Description                                   | Read-Only |\n| ----------------------- | --------------------------------------------- | --------- |\n| `lsp_workspace`         | Get workspace info and available capabilities | ✓         |\n| `lsp_hover`             | Get type info and documentation for a symbol  | ✓         |\n| `lsp_definition`        | Find where a symbol is defined                | ✓         |\n| `lsp_references`        | Find all references to a symbol               | ✓         |\n| `lsp_document_symbols`  | List all symbols in a file                    | ✓         |\n| `lsp_workspace_symbols` | Search symbols across the workspace           | ✓         |\n| `lsp_diagnostics`       | Get errors and warnings for a file            | ✓         |\n| `lsp_code_actions`      | Get available quick fixes and refactorings    | ✓         |\n| `lsp_rename`            | Rename a symbol across the workspace          | ✗         |\n| `lsp_format`            | Format a file                                 | ✗         |\n| `lsp_call_hierarchy`    | Find incoming/outgoing calls                  | ✓         |\n| `lsp_type_hierarchy`    | Find supertypes/subtypes                      | ✓         |\n| `lsp_implementations`   | Find interface implementations                | ✓         |\n| `lsp_signature_help`    | Get function signature at call site           | ✓         |\n| `lsp_inlay_hints`       | Get type annotations and parameter names      | ✓         |\n\n## Configuration\n\n```yaml\nagents:\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code developer with LSP support\n    instruction: You are a software developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        args: []\n        file_types: [\".go\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Properties\n\n| Property      | Type   | Required | Description                                                                                                                  |\n| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `command`     | string | ✓        | LSP server executable command                                                                                                |\n| `args`        | array  | ✗        | Command-line arguments for the LSP server                                                                                    |\n| `env`         | object | ✗        | Environment variables for the LSP process                                                                                    |\n| `file_types`  | array  | ✗        | File extensions this LSP handles (e.g., `[\".go\", \".mod\"]`)                                                                   |\n| `working_dir` | string | ✗        | Working directory for the LSP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. |\n| `version`     | string | ✗        | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary |\n\n## Common LSP Servers\n\nHere are configurations for popular languages:\n\n### Go (gopls)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    version: \"golang/tools@v0.21.0\" # optional: auto-install if not in PATH\n    file_types: [\".go\"]\n```\n\nIf your Go module lives in a subdirectory (e.g. a monorepo where `go.mod` is under `./backend`), set `working_dir` so `gopls` is started from the module root:\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    working_dir: ./backend # gopls must be started from the module root\n```\n\n### TypeScript/JavaScript (typescript-language-server)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: typescript-language-server\n    args: [\"--stdio\"]\n    file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n```\n\n### Python (pylsp)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: pylsp\n    file_types: [\".py\"]\n```\n\n### Rust (rust-analyzer)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: rust-analyzer\n    file_types: [\".rs\"]\n```\n\n### C/C++ (clangd)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: clangd\n    file_types: [\".c\", \".cpp\", \".h\", \".hpp\"]\n```\n\n## Multiple LSP Servers\n\nYou can configure multiple LSP servers for different file types:\n\n```yaml\nagents:\n  polyglot:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-language developer\n    instruction: You are a full-stack developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      - type: lsp\n        command: typescript-language-server\n        args: [\"--stdio\"]\n        file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n      - type: lsp\n        command: pylsp\n        file_types: [\".py\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Workflow Instructions\n\nThe LSP tool includes built-in instructions that guide the agent on how to use it effectively. The agent learns to:\n\n1. Start with `lsp_workspace` to understand available capabilities\n2. Use `lsp_workspace_symbols` to find relevant code\n3. Use `lsp_references` before modifying any symbol\n4. Check `lsp_diagnostics` after every code change\n5. Apply `lsp_format` after edits are complete\n\n> [!TIP]\n> **Best Practice**\n>\n> Always include the `filesystem` tool alongside LSP. The agent needs filesystem access to read and write code files, while LSP provides intelligence about the code.\n\n## Capability Detection\n\nNot all LSP servers support all features. During the `initialize` handshake, Docker Agent reads the server's `ServerCapabilities` and **filters out the `lsp_*` tools the server does not advertise**. The model never sees, for example, `lsp_inlay_hints` against a server that doesn't support it, so it can't waste a turn calling a tool that would only fail.\n\nThe agent uses `lsp_workspace` to discover what's available:\n\n```text\nWorkspace Information:\n- Root: /path/to/project\n- Server: gopls v0.14.0\n- File types: .go\n\nAvailable Capabilities:\n- Hover: Yes\n- Go to Definition: Yes\n- Find References: Yes\n- Rename: Yes\n- Code Actions: Yes\n- Formatting: Yes\n- Call Hierarchy: Yes\n- Type Hierarchy: Yes\n...\n```\n\n## Auto-Restart and Lifecycle\n\nLSP toolsets are managed by the same supervisor as MCP toolsets, so a crashed `gopls` (or any other language server) is reconnected automatically with exponential backoff. Use the [`lifecycle`](../../configuration/tools/index.md#toolset-lifecycle) block to tune the policy per toolset — for example, mark `gopls` as `strict` if your CI flow requires it to be available, or use `/toolset-restart gopls` from the TUI to force a reconnect when the server gets stuck.\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: resilient # default: auto-restart on crash with exponential backoff\n```\n\n## Position Format\n\nAll LSP tools use **1-based** line and character positions:\n\n- Line 1 is the first line of the file\n- Character 1 is the first character on a line\n\n```json\n{\n  \"file\": \"/path/to/file.go\",\n  \"line\": 42,\n  \"character\": 15\n}\n```\n\n> [!TIP]\n> **Auto-Installation**\n>\n> Docker Agent can automatically download and install LSP servers if they are not found in your PATH. Use the `version` property to specify a package, or let Docker Agent auto-detect it from the command name. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) for details.\n","_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md":"---\ntitle: \"MCP Catalog Tool\"\ndescription: \"Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp catalog tool\nlinkTitle: \"MCP Catalog\"\nweight: 120\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp-catalog/\n---\n\n_Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand._\n\n## Overview\n\nThe `mcp_catalog` toolset gives an agent access to a curated subset of the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp) — every server in this subset is reachable over the **streamable-http** transport, so Docker Agent can talk to it directly without the MCP gateway or a local subprocess.\n\nServers are **not** active by default. Instead, the toolset exposes a small set of meta-tools the agent uses to search, enable, and disable servers as a turn unfolds. Tools from un-enabled servers stay hidden, so the prompt is not flooded with hundreds of tool definitions the agent will never use.\n\n> [!NOTE]\n> **When to use it**\n>\n> Use `mcp_catalog` when you want the agent to _decide at runtime_ which third-party services it needs (Notion, Stripe, Brave Search, …) instead of pinning that decision in YAML up front. For a fixed set of servers, declare each one with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) directly — the catalog adds an extra layer of meta-tools that pure `type: mcp` entries do not need.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n```\n\nThe catalog is embedded in the `docker-agent` binary and refreshed with each release. By default every server in the embedded subset is offered.\n\n### Restricting the offered servers\n\nTwo optional lists narrow what the toolset offers, so an agent sees a focused, predictable menu instead of the full catalog:\n\n- **`allowed_servers`** — when non-empty, **only** these catalog server ids are searchable and enableable; every other entry is hidden.\n- **`blocked_servers`** — removes individual ids from the offered set. It is applied **after** `allowed_servers`, so a server listed in both is blocked (block wins over allow).\n\nBoth take server ids (the `id` field returned by `search_remote_mcp_servers`). An empty or omitted list disables that filter.\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n    allowed_servers:\n      - docker-docs\n      - microsoft-learn\n      - hugging-face\n    blocked_servers:\n      - gitmcp\n```\n\n## Meta-Tools\n\nUp to five tools are exposed to the model. The disable / reset-auth pair only appears once at least one server is enabled, so the meta-tool surface stays minimal until the agent activates something.\n\n| Tool                            | When visible            | Description                                                                                                                                          |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `search_remote_mcp_servers`     | Always                  | Case-insensitive fuzzy search over id, title, description, category and tags. Returns id, auth requirements (`oauth` / `none`) and URL. |\n| `enable_remote_mcp_server`      | Always                  | Activate a server by id. **Blocks** until the connection (and any required OAuth handshake) completes; on success the server's tools are immediately live and the model continues with the user's original request in the same turn. |\n| `list_remote_mcp_servers`       | Always                  | Show currently enabled servers and their connection state.                                                                                           |\n| `disable_remote_mcp_server`     | After first enable      | Stop a server and remove its tools from the active set.                                                                                              |\n| `reset_remote_mcp_server_auth`  | After first enable      | Drop persisted OAuth credentials so the next enable triggers a fresh authorization flow. No-op for `none` servers.                       |\n\n### Workflow\n\n1. The agent calls `search_remote_mcp_servers` with a keyword matching the user's intent (`\"notion\"`, `\"stripe\"`, `\"docs\"`, `\"browser\"`, `\"grafana\"`, …).\n2. It picks a matching server id and calls `enable_remote_mcp_server`. **`enable` blocks** until the MCP handshake (and any required OAuth flow) completes:\n   - on success the server's tools are available **in the same turn** — the agent goes straight to the user's original request, no re-ask required;\n   - on failure (user dismissed the authorization dialog, server refused) the tool returns an error result naming the specific reason so the agent can recover instead of pretending the server is connected.\n3. It uses the newly activated tools as it would any other.\n4. When done, it calls `disable_remote_mcp_server` to remove the server from the active set.\n\n## Authentication\n\nThe catalog only includes servers Docker Agent can authenticate itself, so there are two auth flavours:\n\n- **`oauth`** — `enable_remote_mcp_server` surfaces an authorization URL through the elicitation pipeline (the same one used by YAML-declared remote MCP toolsets) and blocks until the user either authorizes or cancels. Once the user authorizes, tokens are persisted in the OS keyring and re-used on subsequent runs. Use `reset_remote_mcp_server_auth` to wipe them. If the user dismisses the dialog, `enable` returns an error result naming the decline so the agent can ask whether to retry.\n- **`none`** — No authentication. The server is reachable as soon as it is enabled.\n\nServers that require a caller-provided API key are intentionally excluded from the catalog. To use one, declare it explicitly with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) and supply the key via an environment variable.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Agent that can on-demand connect to remote MCP servers from the Docker MCP Catalog.\n    instruction: |\n      You can discover and activate remote MCP servers on demand.\n      Use search_remote_mcp_servers to find a server matching the\n      user's intent, then enable_remote_mcp_server to activate it.\n      Be conservative: enable only the servers you actually need for\n      the task at hand. Disable a server with disable_remote_mcp_server\n      once you are done with it.\n    toolsets:\n      - type: mcp_catalog\n```\n\nA complete, runnable configuration lives in [`examples/mcp_catalog.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog.yaml). A curated, allow/block-listed variant lives in [`examples/mcp_catalog_filtered.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog_filtered.yaml).\n\n## Notes and Limitations\n\n- **Streamable-http only.** The catalog deliberately excludes servers that require a local subprocess or the MCP gateway — declare those with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) instead.\n- **Catalog membership changes between releases.** The set of available servers is updated with each Docker Agent release as integrations are added or removed. Servers present in one release may not appear in the next.\n- **Blocking enable.** DNS, TCP, MCP handshake and any OAuth flow happen synchronously inside `enable_remote_mcp_server` so the agent gets a deterministic result in the same turn. On startup, however, the runtime probes tools non-interactively (`mcp.WithoutInteractivePrompts`); OAuth-pending servers fail fast there and are silently deferred to the next interactive turn — including the sidebar-only tool-count pass, where a dialog would be impossible.\n- **No prompt discovery.** MCP prompt lookups (`/prompts`) walk YAML-declared `mcp` toolsets directly; prompts exposed by servers activated through the catalog are not surfaced. Tools — the primary interface — work fine.\n- **Frozen at build time.** The list of servers is embedded in the binary. New entries land with each Docker Agent release.\n\n> [!TIP]\n> **Pair with permissions**\n>\n> Because the agent decides which third-party services to talk to, this toolset works best with explicit [permissions](../../configuration/permissions/index.md) on the surrounding tools (filesystem writes, shell commands) so a misrouted server cannot exfiltrate data unnoticed.\n","_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md":"---\ntitle: \"MCP Tool\"\ndescription: \"Extend agents with external tools via the Model Context Protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp tool\nlinkTitle: \"MCP\"\nweight: 130\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp/\naliases:\n  - /ai/docker-agent/integrations/mcp/\n---\n\n_Extend agents with external tools via the Model Context Protocol (MCP)._\n\n## Overview\n\nThe `mcp` toolset connects your agent to any MCP server — a process or remote service that exposes tools, resources, and prompts over the [Model Context Protocol](https://modelcontextprotocol.io/). Three flavours are supported:\n\n| Flavour | Transport | Best for |\n| --- | --- | --- |\n| **Docker MCP** | Container via the [MCP Gateway](https://github.com/docker/mcp-gateway) | Curated, sandboxed servers from the [Docker MCP Catalog](https://hub.docker.com/u/mcp) |\n| **Local stdio** | Subprocess over stdin/stdout | Custom or community MCP servers run from a binary or `npx`/`pip` package |\n| **Remote** | Streamable HTTP or SSE | Cloud services with hosted MCP endpoints (Linear, Notion, Atlassian, …) |\n\n> [!NOTE]\n> **What is MCP?**\n>\n> The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI tools. Docker Agent can both _use_ MCP servers (this page) and _expose_ agents as MCP servers — see [MCP Mode](../../features/mcp-mode/index.md).\n\n## Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the MCP Gateway. The `ref: docker:<name>` syntax pulls a curated definition from the Docker MCP Catalog:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo        # web search\n  - type: mcp\n    ref: docker:github-official   # GitHub integration\n    tools: [\"list_issues\", \"create_issue\"]\n```\n\nBrowse available servers at the [Docker MCP Catalog](https://hub.docker.com/u/mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`) or a name from the [reusable `mcps:`](../../configuration/overview/index.md#reusable-mcp-servers-mcps) block. |\n| `tools`       | array  | Optional whitelist — only expose these tools to the model.       |\n| `instruction` | string | Custom instructions injected into the agent's context.           |\n| `config`      | any    | MCP server-specific configuration passed during initialization.  |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n\n## Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property      | Type   | Description |\n| ------------- | ------ | ----------- |\n| `command`     | string | Command to execute the MCP server. |\n| `args`        | array  | Command arguments. |\n| `tools`       | array  | Optional whitelist — only expose these tools. |\n| `env`         | object | Environment variables (key-value pairs). |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context. |\n| `version`     | string | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary. |\n\n> [!TIP]\n> **Auto-installation**\n>\n> If the `command` is not in your `PATH`, Docker Agent looks it up in the [aqua registry](https://github.com/aquaproj/aqua-registry) and installs it for you. Use `version: \"false\"` to opt out, or set `DOCKER_AGENT_AUTO_INSTALL=false` globally. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools).\n\n## Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network. OAuth flows (including [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591)) are handled automatically — Docker Agent opens your browser when authentication is required and caches tokens for subsequent sessions. Tokens are refreshed silently when they expire or are revoked server-side; if a silent refresh is not possible, the OAuth prompt reappears on the next message.\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp.linear.app/mcp\"\n      transport_type: \"streamable\"               # or \"sse\" for legacy servers\n      headers:\n        Authorization: \"Bearer ${env.LINEAR_TOKEN}\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: false\n    tools: [\"search_issues\", \"create_issue\"]\n```\n\n| Property                | Type    | Description |\n| ----------------------- | ------- | ----------- |\n| `remote.url`            | string  | Base URL of the MCP server. |\n| `remote.transport_type` | string  | `streamable` or `sse`. |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. See [Remote MCP Servers](../../features/remote-mcp/index.md#per-request-header-template-expansion) for details. |\n| `remote.oauth`          | object  | Explicit OAuth client credentials for servers that don't support DCR. See [Remote MCP Servers](../../features/remote-mcp/index.md#oauth-for-servers-without-dynamic-client-registration). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers. |\n\nFor a curated list of public remote MCP endpoints (Linear, GitHub, Vercel, Notion, …) and full OAuth configuration details, see [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n## MCP Prompts\n\nMCP servers can expose **prompts** — named, parameterized templates that the server provides via the `/prompts` endpoint. Docker Agent discovers these at toolset startup and registers them as **slash commands** in the TUI, so you can invoke them directly from the input box.\n\n```text\n# Type / to see available prompts alongside built-in commands\n/review         # invoke an MCP prompt named \"review\"\n/summarize My text here   # invoke with the first argument filled in\n```\n\n**How it works:**\n\n- Each MCP prompt appears in the command palette (accessible via <kbd>Ctrl</kbd>+<kbd>K</kbd>) under the **MCP Prompts** category.\n- Typing `/<prompt-name>` in the input box invokes the prompt immediately.\n- If the prompt declares arguments and you provide text after the slash command, that text is mapped to the first declared argument.\n- If a required argument is missing, Docker Agent opens the argument input dialog before running the prompt.\n- When no argument is needed or all required arguments are supplied, the prompt runs immediately.\n\n> [!NOTE]\n> MCP prompt discovery requires a YAML-declared `mcp` toolset. Prompts from servers activated through the [Docker MCP Catalog](../../tools/mcp-catalog/index.md) (`ref: docker:<name>`) are not currently surfaced.\n\n## Embedded Resources\n\nMCP tool results can include embedded resources — images, PDFs, and text files returned directly in the tool response. Docker Agent preserves these as attachments and forwards them to the model as native content blocks:\n\n- **Anthropic** — images become `image` blocks in the `tool_result`; PDFs and other documents become `document` blocks.\n- **OpenAI** — images are forwarded as `input_image` data URIs; PDFs as `input_file` data URIs in the tool result content.\n- **Bedrock** and **Gemini** — receive equivalent provider-native representations.\n\nNo configuration is required. When an MCP server returns an embedded resource alongside its text output, the resource is automatically attached and sent to the model on the next turn. This is useful for MCP servers that generate charts, export PDFs, or return binary data as part of their responses.\n\n## Reusable Definitions (`mcps:`)\n\nRepeated MCP server configurations can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`:\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n  playwright:\n    command: npx\n    args: [\"-y\", \"@modelcontextprotocol/server-playwright\"]\n\nagents:\n  root:\n    model: openai/gpt-5\n    toolsets:\n      - type: mcp\n        ref: github\n      - type: mcp\n        ref: playwright\n```\n\nSee [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) for the full reference.\n\n## Common Options\n\nThese properties apply to every MCP toolset regardless of flavour:\n\n### Tool filtering\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n```\n\nWhitelisting tools improves model accuracy — fewer choices means less confusion.\n\n### Deferred loading\n\nSkip the toolset's startup cost until its tools are actually called:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  # Or defer specific tools within a toolset:\n  - type: mcp\n    ref: docker:slack\n    defer: [\"list_channels\", \"search_messages\"]\n```\n\n### Custom instructions\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\n### TOON-encoded outputs\n\nRe-encode verbose JSON outputs as the compact [TOON](https://github.com/alpkeskin/gotoon) format to save context budget. Typically yields 30–60% smaller payloads on list/search tools.\n\n`toon` is a regex string that is matched against tool names. Any tool whose name matches the pattern has its JSON output transparently re-encoded as TOON before it is shown to the model. The re-encoding reduces schema verbosity, which is especially useful when a model struggles with large or repetitive tool output.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"            # toonify every tool from this server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\nThe value is a comma-separated list of regexes (or a single regex). A tool name must match at least one pattern to be re-encoded. Setting `toon: \".*\"` re-encodes all tools from that toolset.\n\nSee [`examples/github-toon.yaml`](https://github.com/docker/docker-agent/blob/main/examples/github-toon.yaml) for a practical example using the GitHub MCP server.\n\n### Per-toolset model routing\n\nProcess tool results from this toolset with a different (typically cheaper / faster) model. The override is one-shot — subsequent turns return to the agent's primary model:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    model: openai/gpt-4o-mini\n```\n\nSee [Per-Toolset Model Routing](../../configuration/tools/index.md#per-toolset-model-routing).\n\n### Lifecycle (auto-restart, profiles)\n\nLocal stdio and remote MCP servers are supervised: crashed servers reconnect automatically with exponential backoff. **Remote** MCP servers (Streamable HTTP / SSE) also reconnect after idle/clean connection closes — services like Notion and Linear periodically close idle connections, and Docker Agent reconnects transparently. Tune the policy with the `lifecycle` block:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; auto-restart with backoff\n  - type: mcp\n    command: docker\n    args: [\"mcp\", \"gateway\"]\n    lifecycle:\n      profile: strict      # fail-fast: required, no retries\n```\n\nSee [Toolset Lifecycle](../../configuration/tools/index.md#toolset-lifecycle) for all profiles and tuning knobs, and [`/toolset-restart`](../../features/tui/index.md) to force a reconnect from the TUI.\n\n## Combined Example\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Docker MCP catalog entry\n      - type: mcp\n        ref: docker:duckduckgo\n\n      # Reusable definition from the top-level mcps: block\n      - type: mcp\n        ref: github\n        tools: [\"list_issues\", \"create_issue\"]\n        toon: \"list_.*\"\n\n      # Local stdio server with auto-install\n      - type: mcp\n        command: gopls\n        version: \"golang/tools@v0.21.0\"\n        args: [\"mcp\"]\n\n      # Remote MCP with OAuth (handled automatically)\n      - type: mcp\n        remote:\n          url: \"https://mcp.linear.app/mcp\"\n          transport_type: \"streamable\"\n        instruction: Use Linear for issue tracking.\n```\n\n> [!WARNING]\n> **Toolset order matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins: the duplicate from the later toolset is ignored and a warning identifies both toolsets. Order your toolsets intentionally. To keep both tools callable, give the MCP toolset a unique `name:` (its tools are then exposed as `<name>_<tool>`) or restrict the overlapping toolset with its `tools:` filter.\n\n## See Also\n\n- [Tool Configuration](../../configuration/tools/index.md) — full reference for every toolset type, plus shared options (lifecycle, TOON, model routing, …).\n- [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) — the top-level `mcps:` block.\n- [Remote MCP Servers](../../features/remote-mcp/index.md) — catalog of public remote MCP endpoints + OAuth recipes.\n- [MCP Mode](../../features/mcp-mode/index.md) — expose your own agents as MCP tools to Claude Desktop, Claude Code, etc.\n- [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) — automatic installation of MCP server binaries.\n","_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md":"---\ntitle: \"Memory Tool\"\ndescription: \"Persistent key-value storage backed by SQLite for cross-session recall.\"\nkeywords: docker agent, ai agents, tools, toolsets, memory tool\nlinkTitle: \"Memory\"\nweight: 100\ncanonical: https://docs.docker.com/ai/docker-agent/tools/memory/\n---\n\n_Persistent key-value storage backed by SQLite for cross-session recall._\n\n## Overview\n\nThe memory tool provides persistent key-value storage backed by SQLite. Data survives across sessions, allowing agents to remember facts, user preferences, project context, and past decisions. Memories can be organized with categories and searched by keyword.\n\nBy default, the database is stored at `~/.cagent/memory/<config-name>/memory.db`, where `<config-name>` is derived from the loaded configuration (typically the YAML file name) and falls back to `default` when unavailable. When the agent is loaded from an OCI reference (e.g. `docker/my-agent:latest`), characters that are reserved in filesystem paths (such as `:`) are sanitised in the `<config-name>` segment — the agent's display name elsewhere is unchanged. Agents declared in the same configuration share this database by default; set an explicit `path` per toolset to isolate them.\n\n## Available Tools\n\n| Tool              | Description                                                                      |\n| ----------------- | -------------------------------------------------------------------------------- |\n| `add_memory`      | Store a new memory with optional category                                        |\n| `get_memories`    | Retrieve all stored memories                                                     |\n| `delete_memory`   | Delete a specific memory by ID                                                   |\n| `search_memories` | Search memories by keywords and/or category (more efficient than `get_memories`) |\n| `update_memory`   | Update an existing memory's content and/or category by ID                        |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: memory\n```\n\n### Options\n\n| Property | Type   | Default                                   | Description                      |\n| -------- | ------ | ----------------------------------------- | -------------------------------- |\n| `path`   | string | `~/.cagent/memory/<config-name>/memory.db` | Path to the SQLite database file |\n\n### Custom Database Path\n\n```yaml\ntoolsets:\n  - type: memory\n    path: ./agent_memory.db\n```\n\n## Categories\n\nMemories support an optional `category` field for organization and filtering. Common categories include:\n\n- `preference` — User preferences and settings\n- `fact` — Factual information about the project or user\n- `project` — Project-specific context\n- `decision` — Past decisions and their rationale\n\n> [!TIP]\n> Memory is especially useful for long-running assistants that need to recall information across conversations — like coding preferences, project conventions, or context discovered during previous sessions.\n","_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md":"---\ntitle: \"Model Picker Tool\"\ndescription: \"Let the agent pick between several models per turn.\"\nkeywords: docker agent, ai agents, tools, toolsets, model picker tool\nlinkTitle: \"Model Picker\"\nweight: 200\ncanonical: https://docs.docker.com/ai/docker-agent/tools/model-picker/\n---\n\n_Let the agent pick between several models per turn._\n\n## Overview\n\nThe model picker tool gives an agent the ability to dynamically choose which model to use for each turn of the conversation. This is useful when you want the agent to route different types of requests to different models — for example, using a fast, inexpensive model for simple queries and a more capable model for complex reasoning tasks.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: model_picker\n    models:\n      - openai/gpt-5-mini\n      - anthropic/claude-sonnet-4-5\n      - openai/gpt-5\n```\n\n### Options\n\n| Property | Type           | Required | Description                                                  |\n| -------- | -------------- | -------- | ------------------------------------------------------------ |\n| `models` | array[string]  | ✓        | List of model references the agent can choose from. Use `provider/model` format. |\n\n## How It Works\n\nWhen the model picker toolset is enabled, the agent gets two tools: `change_model` to switch to one of the configured models, and `revert_model` to return to its default model. The agent decides which model to use based on the complexity of the task, cost considerations, or other factors you describe in its instruction.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini  # Default model\n    instruction: |\n      You are a helpful assistant. For simple questions, use gpt-5-mini.\n      For complex reasoning or coding tasks, switch to claude-sonnet-4-5 or gpt-5.\n    toolsets:\n      - type: model_picker\n        models:\n          - openai/gpt-5-mini\n          - anthropic/claude-sonnet-4-5\n          - openai/gpt-5\n```\n\n> [!TIP]\n> **Cost optimization**\n>\n> The model picker tool is particularly useful for cost optimization: let the agent use a cheap model by default and only escalate to expensive models when necessary.\n\n## Tool Interface\n\nThe toolset exposes two tools:\n\n### `change_model`\n\n| Parameter | Type   | Required | Description                                                                 |\n| --------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `model`   | string | ✓        | The model to switch to. Must be one of the configured models.               |\n\n### `revert_model`\n\nTakes no parameters. Reverts the agent to its original/default model.\n\nThe switch takes effect immediately: the next inference call — including the remainder of the current agentic loop — uses the new model.\n","_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md":"---\ntitle: \"Open URL Tool\"\ndescription: \"Open a fixed URL in the user's default browser.\"\nkeywords: docker agent, ai agents, tools, toolsets, open url tool\nlinkTitle: \"Open URL\"\nweight: 40\ncanonical: https://docs.docker.com/ai/docker-agent/tools/open-url/\n---\n\n_Open a fixed URL in the user's default browser._\n\n## Overview\n\nThe `open_url` toolset exposes a single, argument-less tool that opens a URL\nbaked into the toolset definition in the user's default browser. The model\nnever supplies the URL — it just calls the tool by name. Launching the browser\nis cross-platform: Docker Agent uses `open` on macOS, `xdg-open` on Linux, and\n`rundll32` on Windows.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Letting an agent open a dashboard, documentation page, or deep link on demand\n> - Deep-linking into a desktop app via a custom URI scheme (e.g. `docker-desktop://`)\n> - Any \"take me there\" action where the destination is fixed and known up front\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant that can open the dashboard\n    instruction: When the user asks to see the dashboard, call open_dashboard.\n    toolsets:\n      - type: open_url\n        name: open_dashboard\n        url: https://example.com/dashboard\n```\n\n## Properties\n\n| Property | Type   | Required | Description                                                                                          |\n| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |\n| `url`    | string | ✓        | URL to open. Supports `${env.VAR}` interpolation. Any scheme the OS can dispatch is allowed.         |\n| `name`   | string | ✗        | Tool name the agent references. Defaults to `open_url`. Use a descriptive name when configuring several. |\n\n## Multiple URLs\n\nAdd one toolset entry per destination, each with its own `name`:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_dashboard\n    url: https://example.com/dashboard\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## URL Interpolation\n\nThe `url` field supports `${env.VAR}` placeholders, expanded at call time\nagainst the runtime environment:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## Custom URI Schemes\n\nAny scheme the operating system knows how to dispatch works, including deep\nlinks into desktop applications:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_in_docker_desktop\n    url: docker-desktop://dashboard/apps\n```\n\n## Limitations\n\n- The URL must include a scheme (e.g. `https://`); bare paths are rejected.\n- URLs that look like a command-line flag (starting with `-`) are refused to\n  prevent argument injection into the platform `open` helper.\n- The tool opens the URL on the **host** running Docker Agent; in headless or\n  remote environments where no browser/launcher is available, the call fails\n  gracefully and reports the error to the agent.\n\nSee [`examples/open_url.yaml`](https://github.com/docker/docker-agent/blob/main/examples/open_url.yaml) for a complete configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md":"---\ntitle: \"OpenAPI Tool\"\ndescription: \"Automatically generate tools from an OpenAPI specification.\"\nkeywords: docker agent, ai agents, tools, toolsets, openapi tool\nlinkTitle: \"OpenAPI\"\nweight: 230\ncanonical: https://docs.docker.com/ai/docker-agent/tools/openapi/\n---\n\n_Automatically generate tools from an OpenAPI specification._\n\n## Overview\n\nThe OpenAPI tool fetches an OpenAPI 3.x specification from a URL and creates one tool per API operation. Each endpoint's parameters, request body, and description are translated into a callable tool that the agent can invoke directly.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://petstore3.swagger.io/api/v3/openapi.json\"\n```\n\n### With custom headers\n\nPass custom headers to every HTTP request made by the generated tools (for example, for authentication):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    headers:\n      Authorization: \"Bearer ${env.API_TOKEN}\"\n      X-Custom-Header: \"my-value\"\n```\n\n### Custom timeout\n\nOverride the default 30-second HTTP timeout (applies both to fetching the spec and to the generated tool calls):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    timeout: 60\n```\n\n### Reaching internal services\n\nBy default the OpenAPI tool refuses connections to non-public IP addresses, blocking SSRF attempts even when DNS resolves an otherwise-public host to an internal range. Opt in with `allow_private_ips` when the spec or its `servers` entries legitimately target localhost or your internal network:\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"http://localhost:8080/openapi.json\"\n    allow_private_ips: true\n```\n\n## Properties\n\n| Property            | Type              | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `url`               | string            | ✓        | URL of the OpenAPI specification (JSON format). Supports `${env.VAR}` interpolation.                                                                                                                                                                              |\n| `headers`           | map[string]string | ✗        | Custom HTTP headers sent with every request — both the spec fetch and every generated tool call. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request when docker agent is exposed as a server). |\n| `timeout`           | int               | ✗        | HTTP client timeout in seconds (default: `30`). Applies to both the spec fetch and the generated tools' requests.                                                                                                                                                 |\n| `allow_private_ips` | boolean           | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the spec or its servers legitimately target internal services. By default such addresses are refused at dial time, after DNS resolution, so DNS rebinding cannot bypass the check. |\n\n## How it works\n\n1. The spec is fetched from the configured `url` at startup.\n2. Each operation (GET, POST, PUT, …) becomes a separate tool named after its `operationId` (or `method_path` when no `operationId` is set).\n3. Path and query parameters are exposed as tool parameters. Request body properties are prefixed with `body_`.\n4. Read-only operations (GET, HEAD, OPTIONS) are annotated accordingly.\n5. Responses are returned as text; errors include the HTTP status code.\n\n## Limits\n\n- The OpenAPI spec must be **10 MB or less**.\n- Individual API responses are truncated at **1 MB**.\n\n## Example\n\nSee the full [Pet Store example](https://github.com/docker/docker-agent/blob/main/examples/openapi-petstore.yaml) for a working agent configuration.\n","_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md":"---\ntitle: \"Plan Tool\"\ndescription: \"Shared persistent scratchpad for multi-agent collaboration.\"\nkeywords: docker agent, ai agents, tools, toolsets, plan tool\nlinkTitle: \"Plan\"\nweight: 150\ncanonical: https://docs.docker.com/ai/docker-agent/tools/plan/\n---\n\n_Shared persistent scratchpad for multi-agent collaboration._\n\n## Overview\n\nThe plan tool gives agents a shared, persistent scratchpad of named documents. Any agent in a multi-agent config that loads the `plan` toolset can read and write the same plans, and those plans survive across sessions. This makes it straightforward to wire a planner agent that sketches work and one or more executor agents that consume it without any custom tool wiring.\n\nPlans are stored as JSON files in the Docker Agent data directory (`~/.cagent/plans/` by default). Agents that share a process serialize on a single mutex, and every write or delete additionally holds an advisory lock on a sentinel file in the plans directory, so writers in *separate* Docker Agent processes are serialized too: concurrent edits can never silently overwrite each other, and a stale revision always fails with a deterministic version conflict. Writes are atomic (temp file + rename), so a reader never observes partial content.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: plan\n```\n\nNo additional options are required. All agents that include `type: plan` in their toolsets share the same plans.\n\n## Available Tools\n\n| Tool                    | Description                                                                                       |\n| ----------------------- | ------------------------------------------------------------------------------------------------- |\n| `write_plan`            | Create or update a shared plan by name. Replaces the entire plan content — read it first to preserve what you want to keep. Each write bumps the revision number. |\n| `read_plan`             | Read a shared plan by name, including its title, content, author, status, revision number, and last-updated timestamp. |\n| `list_plans`            | List all shared plans with their name, title, author, status, revision, and last-updated timestamp. |\n| `delete_plan`           | Delete a shared plan by name.                                                                     |\n| `update_plan_from_file` | Create or update a plan, taking the new content from a file on disk instead of inline. Use it with `export_plan_to_file` to edit a large plan without re-sending its whole body. |\n| `export_plan_to_file`   | Write a plan's content to a file. The content goes to disk and is **not** returned as tool output, so materialising a plan costs no tokens. |\n| `set_plan_status`       | Set a plan's free-form status without rewriting its body. The plan must already exist. |\n| `get_plan_status`       | Read a plan's status and current revision without fetching its body.                  |\n\n### Cheap edits with file-based revisions\n\nRe-sending a whole plan on every revision is expensive. The file-based tools let\nan agent edit a plan without paying input-token cost for its body:\n\n1. `export_plan_to_file` writes the current plan content to a path. The content\n   is written to disk and is **not** returned.\n2. The agent edits that file in place with its filesystem tools.\n3. `update_plan_from_file` commits the file's new contents as the next revision.\n\n### Free-form status\n\nEach plan carries a free-form `status` string. There is no fixed vocabulary:\ndefine your own in the system prompt (e.g. `idle`, `in-progress`, `blocked`,\n`done`, `canceled`). Read and write it independently of the body with\n`get_plan_status` and `set_plan_status`, or pass `status` to `write_plan` and\n`update_plan_from_file`. The TUI surfaces the status next to the plan title.\n\n### Optimistic locking\n\nWhen several sessions edit the same plan, concurrent writes could silently\noverwrite each other. Every read returns a `revision` number; pass the value you\nlast read as `last_known_revision` to `write_plan`, `update_plan_from_file`,\n`set_plan_status`, or `delete_plan`. If the plan changed since (its current\nrevision no longer matches), the write is rejected with a version-conflict\nerror and the caller should re-read the plan and retry. The revision check and\nthe write happen under the storage's cross-process file lock, so the conflict\nis detected reliably even when the competing writer runs in a different Docker\nAgent process. Omit `last_known_revision` to write unconditionally (last\nwriter wins).\n\n### Plan Names\n\nPlan names must match the pattern `[a-z0-9][a-z0-9_-]*` (lowercase letters, digits, `-`, `_`). This is enforced structurally so two different inputs can never collapse onto the same file and path-traversal is impossible by construction.\n\n### Plan Fields\n\nEach plan document contains:\n\n| Field      | Description                                               |\n| ---------- | --------------------------------------------------------- |\n| `name`     | The plan's unique slug name                               |\n| `title`    | A short human-readable title (optional)                   |\n| `content`  | The full Markdown or free-form plan text                  |\n| `author`   | Free-form label identifying who last wrote the plan       |\n| `status`   | Free-form lifecycle label (optional), e.g. `in-progress`  |\n| `revision` | Monotonically increasing version counter, bumped on every write |\n| `updatedAt`| ISO 8601 timestamp of the last write                      |\n\n## Example\n\nTwo agents collaborate on a shared plan — the architect drafts it and the builder refines it:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Coordinator\n    instruction: |\n      Route work between the architect and the builder.\n    handoffs: [architect, builder]\n\n  architect:\n    model: anthropic/claude-sonnet-4-5\n    description: Drafts high-level plans\n    instruction: |\n      Use list_plans and read_plan to inspect existing plans, then write_plan\n      to create or revise one. Always read before writing. When done, hand off\n      to the builder.\n    toolsets:\n      - type: plan\n    handoffs: [builder]\n\n  builder:\n    model: openai/gpt-4o\n    description: Adds implementation steps to plans\n    instruction: |\n      Read the architect's plan with read_plan, then use write_plan to append\n      concrete implementation steps. Always read before writing. When done,\n      hand off back to root.\n    toolsets:\n      - type: plan\n    handoffs: [root]\n```\n\nSee [`examples/shared_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_plan` returns a distinct \"not found\" error when a plan does not exist, as opposed to any other I/O error, so callers can tell \"plan missing\" from \"plan unreadable.\"\n- `list_plans` skips corrupt entries but reports them in a `warnings` field so an agent can detect and recover from a bad state (e.g., by calling `delete_plan`).\n- `delete_plan` can remove a corrupt plan to recover from a bad state.\n\n## Managing plans from the host\n\nShared plans can also be inspected and managed outside a session with the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group: list, get, create, update, set status, export, and delete — with the same optimistic-locking semantics as the tools (`--expected-version` guards a write and a stale version fails with exit code 3; `--force` writes unconditionally). Session plans (the per-session \"draft, review, execute\" plan) can be listed, read, and exported through the same commands but stay owned by their session and cannot be mutated from the host.\n\n```bash\n$ docker agent plans list\n$ docker agent plans get release > plan.md\n$ docker agent plans update release --file ./plan.md --expected-version 1\n```\n\n### The `/plans` browser in the TUI\n\nInside the full-screen TUI, the `/plans` slash command (also in the <kbd>Ctrl</kbd>+<kbd>K</kbd> command palette) opens a plan browser over the same store the agents use, so changes made by agents mid-session appear immediately. The list shows every shared plan plus the current session's [session plan](../session_plan/index.md), with each plan's scope, identity (name, or session ID for the session plan), status, version (`-` for the unversioned session plan), last update time, and title.\n\nKeybindings:\n\n| Key | Action |\n| --- | ------ |\n| <kbd>↑</kbd>/<kbd>↓</kbd>, mouse | Navigate; <kbd>Enter</kbd> or double-click opens a detail view with the full metadata and scrollable markdown content |\n| <kbd>/</kbd> | Filter by name, title, status, or scope (<kbd>Esc</kbd> leaves filter mode) |\n| <kbd>r</kbd> | Refresh from storage |\n| <kbd>x</kbd> | Export the selected plan to `<name>.md` (shared) or `session-plan-<short-id>.md` (session) in the session's working directory. An existing file is never overwritten — the export fails with a notification instead |\n| <kbd>s</kbd> | Set a shared plan's free-form status via a small input dialog |\n| <kbd>e</kbd> | Edit a shared plan's content in `$VISUAL`/`$EDITOR` |\n| <kbd>n</kbd> | Create a new shared plan: pick a name, then draft the content in `$VISUAL`/`$EDITOR` (an empty draft aborts) |\n| <kbd>d</kbd> | Delete a shared plan after a confirmation that names the plan and its version |\n| <kbd>Esc</kbd> | Close the detail view / the browser |\n\nEvery mutation is guarded by the version shown on screen (the same optimistic locking as `last_known_revision`): if an agent changed the plan in the meantime, the write is rejected, a notification reports the current version, the newer content is left intact and re-read into the browser, and an edit draft is kept in a temp file so nothing is lost. Session plans are read-only here — status, edit, and delete report why instead of attempting the write. The browser also refreshes live when agents in the same process write, re-status, or delete plans (and when this session's agent updates its session plan); in the lean TUI, which has no overlays, `/plans` is unavailable.\n\n> [!TIP]\n> **Plan vs. Todo vs. Tasks**\n>\n> Use **plan** for shared, free-form documents that multiple agents collaborate on (design docs, requirements, work items). Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md":"---\ntitle: \"RAG Tool\"\ndescription: \"Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search.\"\nkeywords: docker agent, ai agents, tools, toolsets, rag tool\nlinkTitle: \"RAG\"\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/tools/rag/\naliases:\n  - /ai/docker-agent/rag/\n---\n\n_Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search._\n\n## Overview\n\nThe `rag` toolset lets agents search through your documents to find relevant information before responding. Knowledge bases are declared once at the top of the config under `rag:` and then referenced from any agent via `type: rag, ref: <name>`. Docker Agent supports:\n\n- **Background indexing** — Files are indexed automatically and re-indexed on change\n- **Multiple strategies** — Semantic embeddings, BM25 keyword search, and LLM-enhanced search\n- **Hybrid search** — Combine strategies with result fusion for best results\n- **Reranking** — Re-score results with specialized models for improved relevance\n\nRAG is the strategy to reach for when a document collection is too large to inline directly, or gets queried repeatedly across turns/sessions — see [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how it compares to `@`/`/attach` attachments and prompt files.\n\n## Quick Start\n\n```yaml\nrag:\n  my_docs:\n    tool:\n      description: \"Technical documentation\"\n    docs: [./documents, ./some-doc.md]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./docs.db\n        vector_dimensions: 1536\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: |\n      You have access to a knowledge base. Use it to answer questions.\n    toolsets:\n      - type: rag\n        ref: my_docs\n```\n\n## Retrieval Strategies\n\n### Chunked Embeddings (Semantic Search)\n\nUses embedding models to find semantically similar content. Best for understanding intent, synonyms, and paraphrasing.\n\n```yaml\nstrategies:\n  - type: chunked-embeddings\n    embedding_model: openai/text-embedding-3-small\n    database: ./vector.db\n    vector_dimensions: 1536\n    similarity_metric: cosine_similarity\n    threshold: 0.5\n    limit: 10\n    embedding_batch_size: 50\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n### Semantic Embeddings (LLM-Enhanced)\n\nUses an LLM to generate semantic summaries of each chunk before embedding, capturing meaning and intent. Best for code search and understanding implementations.\n\n```yaml\nstrategies:\n  - type: semantic-embeddings\n    embedding_model: openai/text-embedding-3-small\n    vector_dimensions: 1536\n    chat_model: openai/gpt-4o-mini\n    database: ./semantic.db\n    ast_context: true # include AST metadata\n    chunking:\n      size: 1000\n      code_aware: true # AST-aware chunking\n```\n\n> [!NOTE]\n> **Trade-offs**\n>\n> Semantic embeddings provide higher quality retrieval but slower indexing (LLM call per chunk) and additional API costs.\n\n### BM25 (Keyword Search)\n\nTraditional keyword matching using the BM25 algorithm. Best for exact terms, technical jargon, and code identifiers.\n\n```yaml\nstrategies:\n  - type: bm25\n    database: ./bm25.db\n    k1: 1.5 # term frequency saturation\n    b: 0.75 # length normalization\n    threshold: 0.3\n    limit: 10\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n## Hybrid Search\n\nCombine multiple strategies for best results. Strategies run in parallel and results are fused together:\n\n```yaml\nrag:\n  hybrid:\n    docs: [./docs]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./vector.db\n        vector_dimensions: 1536\n        limit: 20\n        chunking: { size: 1000, overlap: 100 }\n      - type: bm25\n        database: ./bm25.db\n        limit: 15\n        chunking: { size: 1000, overlap: 100 }\n    results:\n      fusion:\n        strategy: rrf # Reciprocal Rank Fusion\n        k: 60\n      deduplicate: true\n      limit: 5\n```\n\n## Fusion Strategies\n\n| Strategy   | Best For                          | Description                                                        |\n| ---------- | --------------------------------- | ------------------------------------------------------------------ |\n| `rrf`      | General use (recommended)         | Reciprocal Rank Fusion — rank-based, no score normalization needed |\n| `weighted` | Known performance characteristics | Weight strategies differently (e.g., embeddings: 0.7, BM25: 0.3)   |\n| `max`      | Same scoring scale                | Takes the maximum score from any strategy                          |\n\n## Reranking\n\nRe-score retrieved documents with a specialized model to improve relevance:\n\n```yaml\nresults:\n  reranking:\n    model: openai/gpt-4o-mini\n    top_k: 10 # only rerank top 10\n    threshold: 0.3 # minimum score after reranking\n    criteria: |\n      Prioritize official documentation over blog posts.\n      Prefer recent information and practical examples.\n  limit: 5\n```\n\nSupported reranking providers: **DMR** (native `/rerank` endpoint), **OpenAI**, **Anthropic**, **Gemini**.\n\n## Code-Aware Chunking\n\nFor source code, enable AST-based chunking to keep functions and methods intact:\n\n```yaml\nchunking:\n  size: 2000\n  code_aware: true # Uses tree-sitter for AST-based chunking\n```\n\n> [!NOTE]\n> **Language Support**\n>\n> Currently supports Go (`.go`) files. More languages will be added. Falls back to plain text chunking for unsupported file types.\n\n## Debugging RAG\n\nEnable debug logging to see retrieval details:\n\n```bash\n$ docker agent run config.yaml --debug --log-file debug.log\n```\n\nLook for log tags: `[RAG Manager]`, `[Chunked-Embeddings Strategy]`, `[BM25 Strategy]`, `[RRF Fusion]`, `[Reranker]`.\n\n**Permanent model errors abort early.** If the embedding model, semantic-LLM model, or reranking model returns a permanent error (HTTP 400, 401, 404, or 429 — invalid config, bad auth, unknown model, or rate limit), Docker Agent treats the model configuration as invalid and stops immediately rather than retrying doomed requests:\n\n- **Indexing** — the entire indexing run is aborted after the first permanent failure (including 429). The error is surfaced in the logs so you know immediately if a model name or API key is wrong, rather than silently producing incomplete results.\n- **Reranking** — a permanent error (including 429) permanently disables the reranker for the lifetime of the manager. Subsequent queries fall back to un-reranked results. Only transient errors (5xx, timeouts) fall back and retry on the next query.\n\n> [!TIP]\n> **Examples**\n>\n> See the [RAG examples](https://github.com/docker/docker-agent/tree/main/examples/rag) in the GitHub repo for complete, runnable configurations.\n\n## Configuration Reference\n\n### Top-Level RAG Fields\n\n| Field         | Type     | Default | Description                                                    |\n| ------------- | -------- | ------- | -------------------------------------------------------------- |\n| `docs`        | []string | —       | Document paths/directories (shared across strategies)          |\n| `description` | string   | —       | Human-readable description of this RAG source                  |\n| `respect_vcs` | boolean  | `true`  | Respect `.gitignore` files when indexing documents             |\n| `strategies`  | []object | —       | Array of retrieval strategy configurations                     |\n| `results`     | object   | —       | Post-processing: fusion, reranking, deduplication, final limit |\n\n### Chunked-Embeddings Strategy\n\n| Field                       | Type   | Default             | Description                                                  |\n| --------------------------- | ------ | ------------------- | ------------------------------------------------------------ |\n| `embedding_model`           | string | —                   | **Required.** Embedding model reference                      |\n| `database`                  | string | —                   | Path to local SQLite database                                |\n| `vector_dimensions`         | int    | —                   | Embedding dimensions (e.g., 1536 for text-embedding-3-small) |\n| `similarity_metric`         | string | `cosine_similarity` | Similarity metric                                            |\n| `threshold`                 | float  | `0.5`               | Minimum similarity score (0–1)                               |\n| `limit`                     | int    | `5`                 | Max results from this strategy                               |\n| `embedding_batch_size`      | int    | `50`                | Chunks per embedding request                                 |\n| `max_embedding_concurrency` | int    | `3`                 | Max concurrent embedding requests                            |\n| `chunking.size`             | int    | `1500`              | Chunk size in characters (`4000` when `code_aware` is set)   |\n| `chunking.overlap`          | int    | `75`                | Overlap between chunks in characters                         |\n| `chunking.code_aware`       | bool   | `false`             | AST-based chunking (Go files only)                           |\n\n### Semantic-Embeddings Strategy\n\n| Field                      | Type   | Default    | Description                                                        |\n| -------------------------- | ------ | ---------- | ------------------------------------------------------------------ |\n| `embedding_model`          | string | —          | **Required.** Embedding model reference                            |\n| `chat_model`               | string | —          | **Required.** LLM for generating semantic summaries                |\n| `vector_dimensions`        | int    | —          | **Required.** Embedding dimensions                                 |\n| `database`                 | string | —          | Path to local SQLite database                                      |\n| `semantic_prompt`          | string | (built-in) | Custom prompt template (`${path}`, `${content}`, `${ast_context}`) |\n| `ast_context`              | bool   | `false`    | Include tree-sitter AST metadata in prompts                        |\n| `threshold`                | float  | `0.5`      | Minimum similarity score (0–1)                                     |\n| `limit`                    | int    | `5`        | Max results                                                        |\n| `max_indexing_concurrency` | int    | `3`        | Max concurrent file indexing                                       |\n| `chunking.size`            | int    | `1500`     | Chunk size in characters (`4000` when `code_aware` is set)         |\n| `chunking.overlap`         | int    | `75`       | Overlap between chunks                                             |\n| `chunking.code_aware`      | bool   | `false`    | AST-based chunking                                                 |\n\n### BM25 Strategy\n\n| Field              | Type   | Default | Description                                     |\n| ------------------ | ------ | ------- | ----------------------------------------------- |\n| `database`         | string | —       | Path to local SQLite database                   |\n| `k1`               | float  | `1.5`   | Term frequency saturation (1.2–2.0 recommended) |\n| `b`                | float  | `0.75`  | Length normalization (0–1)                      |\n| `threshold`        | float  | `0.0`   | Minimum BM25 score                              |\n| `limit`            | int    | `5`     | Max results                                     |\n| `chunking.size`    | int    | `1500`  | Chunk size in characters                        |\n| `chunking.overlap` | int    | `75`    | Overlap between chunks                          |\n\n### Results (Post-Processing)\n\n| Field                 | Type   | Default | Description                                                 |\n| --------------------- | ------ | ------- | ----------------------------------------------------------- |\n| `fusion.strategy`     | string | `rrf`   | Fusion method: `rrf`, `weighted`, or `max`                  |\n| `fusion.k`            | int    | `60`    | RRF rank constant                                           |\n| `deduplicate`         | bool   | `true`  | Remove duplicate results                                    |\n| `limit`               | int    | `15`    | Final number of results                                     |\n| `include_score`       | bool   | `false` | Include relevance scores in results                         |\n| `return_full_content` | bool   | `false` | Return full document content instead of just matched chunks |\n| `reranking.model`     | string | —       | Reranking model reference                                   |\n| `reranking.top_k`     | int    | (`limit`) | Only rerank top K results. Defaults to the results `limit` when set.  |\n| `reranking.threshold` | float  | `0.5`   | Minimum relevance score after reranking                     |\n| `reranking.criteria`  | string | —       | Custom relevance guidance for the reranking model           |\n","_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md":"---\ntitle: \"Scheduler Tool\"\ndescription: \"Schedule instructions to run at a time or on a recurring interval.\"\nkeywords: docker agent, ai agents, tools, toolsets, scheduler tool, cron\nlinkTitle: \"Scheduler\"\nweight: 135\ncanonical: https://docs.docker.com/ai/docker-agent/tools/scheduler/\n---\n\n_Schedule instructions to run at a time or on a recurring interval._\n\n## Overview\n\nThe scheduler toolset lets an agent make something happen at a chosen time or on a repeating cadence during a session. You give it an instruction and a schedule; when the schedule is due, the instruction is delivered back to the agent, which then carries out the action with its normal tools (`shell`, `api`, `fetch`, and so on).\n\nThe scheduler does not run shell or API calls itself. When a schedule fires it injects the instruction into the agent loop via the runtime's recall mechanism — the same primitive [`background_jobs`](../background-jobs/index.md) uses to report completed work — and the agent decides how to act. This keeps every action under the agent's normal tools and permissions rather than adding a second, unattended\ncommand runner.\n\n> [!NOTE]\n> Schedules only fire while the session is running (interactive TUI or a server mode) and are not persisted across restarts. Scheduling requires a host that supports recall; if it does not, `create_schedule` returns an error.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: scheduler\n```\n\nNo configuration options.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `create_schedule` | Register an instruction to run at a time or interval. |\n| `list_schedules` | List active schedules with their id, spec, and next fire time. |\n| `cancel_schedule` | Remove a schedule by id. |\n\n### `create_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `prompt` | Yes | The instruction to deliver to the agent when the schedule fires. |\n| `when` | Yes | When to fire (see [Schedule specs](#schedule-specs)). |\n| `name` | No | Optional human-readable label. |\n\nReturns the new schedule's id and its next fire time.\n\n### `cancel_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `id` | Yes | The id of the schedule to cancel (from `create_schedule` or `list_schedules`). |\n\n## Schedule specs\n\nThe `when` argument accepts:\n\n| Form | Meaning | Example |\n| --- | --- | --- |\n| `in:<duration>` | One-shot, after a delay | `in:10m` |\n| `at:<RFC3339>` | One-shot, at an absolute future time | `at:2026-07-14T09:00:00Z` |\n| `every:<duration>` | Recurring, at a fixed interval | `every:1h` |\n| `minutely` / `hourly` / `daily` / `weekly` | Recurring preset intervals | `hourly` |\n\nDurations use Go's duration syntax (`30s`, `15m`, `2h`). Preset and `every:` intervals are measured from the schedule's creation time (for example `hourly` fires every hour after it is created), not aligned to wall-clock slots.\n\n> [!IMPORTANT]\n> **Recurring schedules have a one-minute minimum.** Every fire injects a message into the agent loop and typically costs an LLM turn, so `every:` values below `1m` are rejected — a typo such as `every:1s` in place of `every:1h` would otherwise become a runaway token burn. One-shot schedules (`in:` / `at:`) are not restricted, since they fire once.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A monitoring assistant\n    instruction: |\n      Every 15 minutes, run `git fetch` and tell me if origin/main moved.\n    toolsets:\n      - type: scheduler\n      - type: shell\n```\n\nThe agent calls:\n\n```text\ncreate_schedule(prompt=\"Run git fetch and report if origin/main moved\", when=\"every:15m\")\n```\n\nEvery 15 minutes it is reminded, runs the command with the `shell` tool, and reports back.\n\n> [!TIP]\n> **When to use**\n>\n> Use the scheduler for recurring monitoring, timed one-shots, and unattended housekeeping loops during a long-running session. For work that should run immediately and be awaited, use [`background_jobs`](../background-jobs/index.md) instead.\n","_vendor/github.com/docker/docker-agent/docs/tools/script/index.md":"---\ntitle: \"Script Tool\"\ndescription: \"Define custom shell scripts as named tools with typed parameters.\"\nkeywords: docker agent, ai agents, tools, toolsets, script tool\nlinkTitle: \"Script\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/tools/script/\n---\n\n_Define custom shell scripts as named tools with typed parameters._\n\n## Overview\n\nThe script tool lets you define custom shell scripts as named tools. Unlike the generic [shell tool](../shell/index.md) where the agent writes the command, script tools execute predefined commands — ideal for exposing safe, well-scoped operations with descriptive names.\n\n## Configuration\n\n### Simple Scripts\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      run_tests:\n        cmd: task test\n        description: Run the project test suite\n      lint:\n        cmd: task lint\n        description: Run the linter\n```\n\n### Scripts with Parameters\n\nUse `${param}` interpolation and JSON Schema to define typed arguments:\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      deploy:\n        cmd: ./scripts/deploy.sh ${env}\n        description: Deploy to an environment\n        args:\n          env:\n            type: string\n            enum: [staging, production]\n        required: [env]\n```\n\n## Properties\n\n| Property                          | Type   | Description                                                |\n| --------------------------------- | ------ | ---------------------------------------------------------- |\n| `shell.<name>.cmd`                | string | Shell command to execute (supports `${arg}` interpolation) |\n| `shell.<name>.description`        | string | Description shown to the model                             |\n| `shell.<name>.args`               | object | Parameter definitions (JSON Schema properties)             |\n| `shell.<name>.required`           | array  | Required parameter names                                   |\n| `shell.<name>.env`                | object | Environment variables for this script                      |\n| `shell.<name>.working_dir`        | string | Working directory for script execution                     |\n\n> [!TIP]\n> **Script vs. Shell**\n>\n> Use the [shell tool](../shell/index.md) when the agent needs to run arbitrary commands. Use the script tool when you want to expose specific, predefined operations with clear names and typed parameters — giving the agent less freedom but more safety.\n","_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md":"---\ntitle: \"Session Context Tool\"\ndescription: \"Reference a previous session as context in the current one.\"\nkeywords: docker agent, ai agents, tools, toolsets, session context tool\nlinkTitle: \"Session Context\"\nweight: 210\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_context/\n---\n\n_Reference a previous session as context, without manual export/import._\n\n## Overview\n\nThe `session_context` toolset lets an agent discover earlier sessions and pull one in as context for the current session. It removes the manual workaround of exporting a conversation to HTML and re-attaching it with an `@` mention.\n\nThe tool surface is two read-only tools:\n\n| Tool            | Description                                                                                                  |\n| --------------- | ------------------------------------------------------------------------------------------------------------ |\n| `list_sessions` | List previous sessions (most recent first) with id, title, creation time and message count.                  |\n| `read_session`  | Return the transcript of a previous session, by id or by a relative reference like `-1`.                      |\n\nThe session the agent is currently running in is never listed by `list_sessions` and cannot be read by `read_session` (a circular reference returns an error).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_context\n```\n\nNo configuration options. Both tools are read-only and operate against the same session store the runtime already uses for persistence.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that may browse but never pull a full transcript into context.\ntoolsets:\n  - type: session_context\n    tools:\n      - list_sessions\n```\n\n## Selecting a session\n\n`read_session` accepts either form:\n\n- A concrete id returned by `list_sessions`, e.g. `read_session(\"a1b2c3...\")`.\n- A relative reference: `-1` is the most recent session, `-2` the second most recent, and so on. Relative references resolve against the same ordering `list_sessions` uses (most recent first), excluding sub-sessions.\n\n## Transcript size\n\nA long session could overflow the current context window, so `read_session` caps the rendered transcript. When a transcript is larger than the budget, the oldest messages are dropped (the most recent are usually the most useful for continuing work) and a note records how many were omitted:\n\n```text\n[12 earlier message(s) omitted to fit the context budget; showing the most recent 8]\n```\n\n## Notes\n\n- `list_sessions` defaults to 20 sessions and is capped at 100; pass `limit` to request fewer.\n- `read_session` returns an error when the session is not found, when the reference cannot be resolved, or when it points at the current session.\n- Both tools are read-only: they never modify, branch, or delete sessions.\n\n## Example\n\nSee [`examples/session_context.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_context.yaml) for a complete working example.\n","_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md":"---\ntitle: \"Session Plan Tool\"\ndescription: \"Per-session plan tracker for the draft, review, execute workflow.\"\nkeywords: docker agent, ai agents, tools, toolsets, session plan tool\nlinkTitle: \"Session Plan\"\nweight: 160\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_plan/\n---\n\n_Per-session plan tracker for the \"draft, review, execute\" workflow._\n\n## Overview\n\nThe `session_plan` toolset gives one agent a place to write a plan for the current session, signal that the plan is ready, and let the host route the next turn to an executing agent.\n\nDifferent from the [`plan` toolset](../plan/index.md) — `plan` is for shared, named plans multiple agents collaborate on over many sessions. `session_plan` is for one ephemeral plan per session, scoped to that session by ID.\n\nPlans live as Markdown files under:\n\n```text\n~/.cagent/session_plans/<session-id>.md\n```\n\nThe tool surface is three tools:\n\n| Tool                 | Description                                                                                          |\n| -------------------- | ---------------------------------------------------------------------------------------------------- |\n| `write_session_plan` | Create or replace this session's plan as markdown. There's exactly one plan per session.             |\n| `read_session_plan`  | Read the plan written for the current session and return it as markdown.                             |\n| `exit_plan_mode`     | Signal that the plan is ready for review. Does not switch agents on its own.                         |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_plan\n```\n\nNo configuration options. The plan path is derived from the session ID; the agent does not name plans.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that consumes a plan but should not be able to (re)write or finalize one.\ntoolsets:\n  - type: session_plan\n    tools:\n      - read_session_plan\n```\n\n## When to call exit_plan_mode\n\nCall `exit_plan_mode` once the plan is complete and you do not intend to change it on the next turn. It validates that a plan exists for the session and returns a \"ready for review\" tool result. It does **not** switch agents or solicit user approval on its own — the host application owns the next-turn routing (for example, by reading the tool result, by a UI affordance the user toggles, or by a `handoff` declared on the agent).\n\nThis separation keeps the tool reusable across UIs: a CLI that prints tool results inline, a chat UI with a plan-mode toggle, and a server that auto-routes the next turn through a `handoff` can all consume the same signal without one stepping on another.\n\n## Storage and cleanup\n\n- Plans are markdown files written atomically (temp + rename), so concurrent readers — in this process or another — never observe a partial write.\n- A best-effort sweep on first use of the toolset removes plan files older than 30 days under the plans directory. Stranded plans for long-gone sessions do not accumulate.\n- The session ID identifies the file directly. There is no in-process mutex or revision counter, because two sessions cannot map to the same path.\n\n## Events\n\nA `session_plan_updated` event is emitted whenever `write_session_plan` succeeds:\n\n```json\n{\n  \"type\": \"session_plan_updated\",\n  \"session_id\": \"...\",\n  \"path\": \"/Users/.../.cagent/session_plans/<session-id>.md\",\n  \"content\": \"# my plan\\n...\",\n  \"agent_name\": \"planner\"\n}\n```\n\nEmbedders that render the plan inline can subscribe and update without re-reading the file.\n\n## Managing session plans from the host\n\nA session plan belongs to its session: hosts can read and export it, never change it.\n\n- **CLI** — the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group lists, reads (`get --session <session-id>`), and exports session plans alongside shared plans. Mutations (`update`, `status`, `delete`) are refused with an `unsupported` error explaining the ownership rule.\n- **TUI** — the `/plans` browser (see the [plan toolset docs](../plan/index.md#the-plans-browser-in-the-tui) for the full keybinding table) includes the **current session's** plan as the `session` scope row; plans of other sessions are never enumerated. Its identity is the session ID and its version column shows `-` — session plans have no versions. <kbd>Enter</kbd> opens the detail view (scope, session ID, update time, scrollable markdown) and <kbd>x</kbd> exports to `session-plan-<short-id>.md` in the working directory (refusing to overwrite an existing file). <kbd>e</kbd> opens the plan body in your external editor (`$VISUAL` or `$EDITOR`) for editing — the write is unguarded and last-write-wins by design. Status and delete visibly report that session plans don't support them (session plans belong to their session and carry no shared-plan metadata). The browser refreshes live on the `session_plan_updated` event, so a plan the agent just wrote appears without reopening.\n\n## Example\n\nA two-agent workflow: `root` executes, `planner` plans. `/plan` hands off to the planner; `exit_plan_mode` signals \"ready\", and the host decides what happens next.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Executes approved plans\n    instruction: |\n      You execute plans the planner has handed off. When you see a message\n      that a plan has been approved, read it with read_session_plan and work\n      through its steps in order.\n    toolsets:\n      - type: session_plan\n        tools:\n          - read_session_plan\n      - type: filesystem\n      - type: shell\n    commands:\n      plan:\n        description: \"Switch to the planner\"\n        agent: planner\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Investigates and writes plans for review\n    instruction: |\n      Investigate the user's request, then write the plan with\n      write_session_plan. Iterate with the user until the plan is complete,\n      then call exit_plan_mode to mark it ready for review.\n    toolsets:\n      - type: session_plan\n      - type: filesystem\n        readonly: true\n      - type: user_prompt\n```\n\nSee [`examples/session_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_session_plan` and `exit_plan_mode` return a \"no plan written yet\" error when called before `write_session_plan`.\n- `write_session_plan` validates the session ID and refuses to write anything that could escape the plans directory; in practice the runtime generates UUIDs so this only triggers if an embedder supplies a hand-crafted ID.\n\n> [!TIP]\n> **session_plan vs. plan vs. todo vs. tasks**\n>\n> Use **session_plan** when one agent drafts an approach for the user to review before another agent executes it (ephemeral, one per session). Use [plan](../plan/index.md) for shared, named plans multiple agents collaborate on over many sessions. Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md":"---\ntitle: \"Shell Tool\"\ndescription: \"Execute arbitrary shell commands in the user's environment.\"\nkeywords: docker agent, ai agents, tools, toolsets, shell tool\nlinkTitle: \"Shell\"\nweight: 20\ncanonical: https://docs.docker.com/ai/docker-agent/tools/shell/\n---\n\n_Execute arbitrary shell commands in the user's environment._\n\n## Overview\n\nThe shell tool allows agents to execute arbitrary shell commands synchronously. This is one of the most powerful tools — it lets agents run builds, install dependencies, query APIs, and interact with the system. Each call runs in a fresh, isolated shell session — no state persists between calls.\n\nCommands have a default 30-second timeout and require user confirmation unless `--yolo` is used. For servers, watchers, and other long-running commands, add the [`background_jobs`](../background-jobs/index.md) toolset alongside `shell`.\n\n### Shell interpreter detection\n\nThe shell tool automatically detects and names the resolved shell interpreter (e.g., `bash`, `zsh`, `powershell`, `pwsh`, `cmd`) in its description to the model, along with the operating system (Linux, macOS, Windows). This helps models use the correct shell syntax for the host environment.\n\nFor example:\n\n- On Linux with bash: \"Executes the given shell command with bash on Linux.\"\n- On Windows with PowerShell: \"Executes the given shell command with powershell on Windows. Use Windows PowerShell 5.1 syntax: chain commands with \";\" (not \"&&\"), and avoid POSIX commands/flags like \"ls -la\".\"\n\nThis reduces wasted turns where models assume POSIX syntax on Windows or vice versa.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                          |\n| -------------- | ------- | --------------------------------------------------------------------------------------------------- |\n| `env`          | object  | Environment variables to set for all shell commands                                                 |\n| `safer`        | boolean | Deprecated and ignored — shell commands are always classified now (see [Command classification](#command-classification)). Kept so existing YAMLs still parse. |\n| `sudo_askpass` | boolean | Opt in to prompting for a `sudo` password (see [Sudo support](#sudo-support)). Default `false`.     |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: shell\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Command classification\n\nEvery shell command is classified against an embedded taxonomy before the approval decision — no opt-in required:\n\n- **Destructive matches** (`rm -rf <path>`, `docker volume rm`, `mkfs`, `dd if=… of=/dev/<disk>`, …) are labelled `destructive` with a `blast_radius` (`low` / `medium` / `high`) and a `category` tag. The TUI confirmation dialog renders the blast radius with a color badge.\n- **Known-safe reads** (`ls`, `cat`, `git status`, `git diff`, `docker ps`, `docker logs`, `kubectl get`, …) are labelled `safe`.\n- **Everything else** is labelled `unknown`.\n\nThe session's [safety mode](../../configuration/permissions/index.md#safety-modes) decides what each label means: `strict` asks about everything, `balanced` auto-runs safe commands and asks about destructive/unknown ones, `restricted` auto-runs safe commands and denies destructive/unknown ones without asking (fail-closed for unattended runs), `autonomous` runs everything. Custom permission rules always win over the mode.\n\nCompound shell (`a && b`, `a; b`, `a | b`) is never matched against the safe allowlist; any destructive segment falls through to ask. The full taxonomy lives in [`pkg/safety/safety_patterns.json`](https://github.com/docker/docker-agent/blob/main/pkg/safety/safety_patterns.json).\n\nSee [`examples/safety_modes.yaml`](https://github.com/docker/docker-agent/blob/main/examples/safety_modes.yaml) for a full example. The legacy `safer: true` toolset flag is deprecated and ignored.\n\n### Sudo support\n\nBy default a shell command has no controlling terminal, so a `sudo` command that needs a password hangs until it times out (the agent usually gives up and falls back to printing manual instructions).\n\nSet `sudo_askpass: true` to enable a sudo privilege escalation flow:\n\n```yaml\ntoolsets:\n  - type: shell\n    sudo_askpass: true\n```\n\nWhen enabled, `sudo` commands prompt you for your password through the host UI (the input is masked). The password is handed to `sudo` over a private, per-session socket via the standard `SUDO_ASKPASS` mechanism — it is never written to the command line, the logs, or stored by the agent.\n\nThe bridge environment variables (`SUDO_ASKPASS`, `CAGENT_ASKPASS_SOCKET`, `CAGENT_ASKPASS_TOKEN`) are added only to commands that invoke `sudo`, but within such a command they are visible to every child process, not just `sudo`. They carry a socket path and a session token, not the password; the socket lives in a `0700` directory, so only your own user can reach it.\n\nNotes and limitations:\n\n- Unix only. The flag has no effect on Windows.\n- Interactive UI only. In headless / non-interactive runs the prompt is declined automatically and `sudo` fails as before.\n- Only a bare `sudo ...` invocation in a POSIX shell (`sh`, `bash`, `zsh`, ...) is handled. `sudo` called by absolute path (`/usr/bin/sudo`), via `env sudo`, from inside a nested script, or under a non-POSIX shell (e.g. `fish`) is not intercepted and behaves as before.\n- Caching is `sudo`'s own. Because each shell tool call runs in a fresh shell with no controlling terminal, `sudo`'s credential cache does not persist across separate tool calls: you are prompted once per shell command that uses `sudo`. Within a single command, multiple `sudo` calls (e.g. `sudo a && sudo b`) usually share one prompt, subject to `sudo`'s own timestamp configuration.\n- The prompt must be answered within the command's timeout; raise the `timeout` parameter for `sudo` commands that may wait on input.\n- Prompts are serialized: if a single command runs two `sudo` calls in parallel (e.g. `sudo a & sudo b`), the second waits for the first prompt to be answered rather than opening two dialogs at once.\n\n## Available Tools\n\nThe shell toolset exposes one tool:\n\n| Tool Name | Description                                                                  |\n| --------- | ---------------------------------------------------------------------------- |\n| `shell`   | Run a command synchronously and return its combined output when it finishes. |\n\n### `shell` parameters\n\n| Parameter | Type    | Required | Description                                                               |\n| --------- | ------- | -------- | ------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute.                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                   |\n| `timeout` | integer | ✗        | Per-call execution timeout in seconds (default: `30`).                    |\n\n> [!WARNING]\n> **Safety**\n>\n> The shell tool gives agents full access to the system shell. Always set `max_iterations` on agents that use the shell tool to prevent infinite loops. A value of 20–50 is typical for development agents. Use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing shell commands. Use `--yolo` to auto-approve all tool calls.\n","_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md":"---\ntitle: \"Tasks Tool\"\ndescription: \"Persistent task database with priorities and dependencies, shared across sessions.\"\nkeywords: docker agent, ai agents, tools, toolsets, tasks tool\nlinkTitle: \"Tasks\"\nweight: 180\ncanonical: https://docs.docker.com/ai/docker-agent/tools/tasks/\n---\n\n_Persistent task database with priorities and dependencies, shared across sessions._\n\n## Overview\n\nThe tasks tool provides a persistent task database that survives across agent sessions. Unlike the [Todo tool](../todo/index.md), which maintains an in-memory task list for the current session only, the tasks tool stores tasks in a JSON file on disk so they can be accessed and updated across multiple sessions. Tasks support priorities and dependencies — a task is _blocked_ until every task it depends on is `done`.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: tasks\n    path: ./tasks.json  # Optional: custom database path\n```\n\n### Options\n\n| Property | Type   | Default       | Description                                                                                                                  |\n| -------- | ------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `path`   | string | `tasks.json`  | Path to the JSON task database. Relative paths resolve against the agent config directory (or `--working-dir` when set).     |\n\n## Available Tools\n\nThe tasks toolset exposes these tools:\n\n| Tool Name           | Description                                                                                                              |\n| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `create_task`       | Create a new task with a title, description (or markdown file path), optional priority, and optional dependencies.       |\n| `get_task`          | Get full details of a single task by ID, including its effective status (`blocked` if any dependency is not `done`).     |\n| `update_task`       | Update a task's title, description, priority, status, or dependency list.                                                |\n| `delete_task`       | Delete a task by ID. Also removes it from other tasks' dependency lists.                                                 |\n| `list_tasks`        | List tasks sorted by priority (critical first) with blocked tasks last. Optionally filter by status or priority.         |\n| `next_task`         | Return the highest-priority actionable task — one that is not blocked and not done. Great for \"what should I work on?\". |\n| `add_dependency`    | Add a dependency: a task is blocked until the task it depends on is `done`.                                              |\n| `remove_dependency` | Remove a dependency from a task.                                                                                         |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    toolsets:\n      - type: tasks\n        path: ./project-tasks.json\n```\n\n> [!TIP]\n> **Tasks vs. Todo**\n>\n> Use the **tasks** tool when you need persistence across sessions, priorities, or dependencies (e.g., long-running projects, recurring work). Use the [todo tool](../todo/index.md) for ephemeral, session-scoped task lists.\n","_vendor/github.com/docker/docker-agent/docs/tools/think/index.md":"---\ntitle: \"Think Tool\"\ndescription: \"Step-by-step reasoning scratchpad for planning and decision-making.\"\nkeywords: docker agent, ai agents, tools, toolsets, think tool\nlinkTitle: \"Think\"\nweight: 140\ncanonical: https://docs.docker.com/ai/docker-agent/tools/think/\n---\n\n_Step-by-step reasoning scratchpad for planning and decision-making._\n\n## Overview\n\nThe think tool is a reasoning scratchpad that lets agents think step-by-step before acting. The agent can write its thoughts without producing visible output to the user — ideal for planning complex tasks, breaking down problems, and reasoning through multi-step solutions.\n\nThis is a lightweight tool with no side effects. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: think\n```\n\nNo configuration options.\n\n> [!TIP]\n> **When to use**\n>\n> Use the think tool with models that don't have native reasoning capabilities. If your model already supports a [thinking budget](../../configuration/models/index.md#thinking-budget), you likely don't need this tool.\n","_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md":"---\ntitle: \"Todo Tool\"\ndescription: \"Task list management for complex multi-step workflows.\"\nkeywords: docker agent, ai agents, tools, toolsets, todo tool\nlinkTitle: \"Todo\"\nweight: 170\ncanonical: https://docs.docker.com/ai/docker-agent/tools/todo/\n---\n\n_Task list management for complex multi-step workflows._\n\n## Overview\n\nThe todo tool provides task list management. Agents can create, update, list, and track progress on tasks with status tracking (pending, in-progress, completed). Useful for complex multi-step workflows where the agent needs to stay organized and ensure all steps are completed.\n\n## Available Tools\n\n| Tool           | Description                              |\n| -------------- | ---------------------------------------- |\n| `create_todo`  | Create a new task                        |\n| `create_todos` | Create multiple tasks at once            |\n| `update_todos` | Update status of one or more tasks       |\n| `list_todos`   | List all current tasks with their status |\n\n### Task Statuses\n\n| Status        | Description                  |\n| ------------- | ---------------------------- |\n| `pending`     | Task has not been started    |\n| `in-progress` | Task is currently being done |\n| `completed`   | Task is finished             |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: todo\n```\n\n### Options\n\n| Property | Type    | Default | Description                                                             |\n| -------- | ------- | ------- | ----------------------------------------------------------------------- |\n| `shared` | boolean | `false` | When `true`, todos are shared across all agents in a multi-agent config |\n\n### Shared Todos\n\nIn multi-agent setups, enable shared todos so all agents can see and update the same task list:\n\n```yaml\ntoolsets:\n  - type: todo\n    shared: true\n```\n","_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md":"---\ntitle: \"Transfer Task Tool\"\ndescription: \"Delegate tasks to sub-agents in multi-agent setups.\"\nkeywords: docker agent, ai agents, tools, toolsets, transfer task tool\nlinkTitle: \"Transfer Task\"\nweight: 80\ncanonical: https://docs.docker.com/ai/docker-agent/tools/transfer-task/\n---\n\n_Delegate tasks to sub-agents in multi-agent setups._\n\n## Overview\n\nThe `transfer_task` tool allows an agent to delegate tasks to specialized sub-agents and receive their results. This is the core mechanism for multi-agent orchestration.\n\n**You don't need to add it manually** — it's automatically available when an agent has `sub_agents` configured.\n\n## Configuration\n\nThe tool is enabled implicitly when `sub_agents` is set:\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Coordinates work across specialists\n    instruction: Analyze requests and delegate to the right specialist.\n    sub_agents: [developer, researcher]\n\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: Write clean, production-ready code.\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Search for information online.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\nThe coordinator agent automatically gets a `transfer_task` tool that can delegate to `developer` or `researcher`.\n\n## Tool Interface\n\nThe `transfer_task` tool takes three parameters:\n\n| Parameter         | Type   | Required | Description                                                                                 |\n| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to delegate to. Must be listed under the caller's `sub_agents`.        |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.                        |\n| `expected_output` | string | ✓        | Description of the result/format the caller expects back.                                   |\n\nThe call blocks until the sub-agent returns its result, which becomes the tool's response. For non-blocking parallel delegation, use [`background_agents`](../background-agents/index.md) instead.\n\n## Delegation Limits\n\nSub-agents can have `sub_agents` of their own, so multi-level delegation chains are supported. Two runtime guards keep chains sane, applied to both `transfer_task` and `run_background_agent`:\n\n- **Cycles are rejected.** A delegation targeting an agent that is already part of the active delegation chain (for example `a -> b -> a`) fails with an error naming the cycle.\n- **Depth is capped at 10 nested delegations.** The root agent delegating to its first sub-agent counts as depth 1; a call that would exceed the cap fails with an error stating the attempted depth.\n\nA rejected delegation returns a tool error to the calling agent and never starts the sub-agent.\n\n> [!TIP]\n> **See also**\n>\n> For parallel task delegation, see [Background Agents](../background-agents/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n","_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md":"---\ntitle: \"User Prompt Tool\"\ndescription: \"Ask the user questions and collect interactive input during agent execution.\"\nkeywords: docker agent, ai agents, tools, toolsets, user prompt tool\nlinkTitle: \"User Prompt\"\nweight: 190\ncanonical: https://docs.docker.com/ai/docker-agent/tools/user-prompt/\n---\n\n_Ask the user questions and collect interactive input during agent execution._\n\n## Overview\n\nThe user prompt tool allows agents to ask questions and collect input from users during execution. This enables interactive workflows where the agent needs clarification, confirmation, or additional information before proceeding.\n\n> [!NOTE]\n> **When to Use**\n>\n> - When the agent needs clarification before proceeding\n> - Collecting credentials or configuration values\n> - Presenting choices and getting user decisions\n> - Confirming destructive or important actions\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Interactive assistant\n    instruction: |\n      You are a helpful assistant. When you need information\n      from the user, use the user_prompt tool to ask them.\n    toolsets:\n      - type: user_prompt\n      - type: filesystem\n      - type: shell\n```\n\n## Tool Interface\n\nThe `user_prompt` tool takes these parameters:\n\n| Parameter | Type   | Required | Description                                                                                        |\n| --------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |\n| `message` | string | ✓        | The question or prompt to display.                                                                 |\n| `title`   | string | ✗        | Optional title for the dialog window in the TUI. Defaults to `\"Question\"` when not provided.       |\n| `schema`  | object | ✗        | JSON Schema defining the expected response structure (object or primitive).                        |\n\n## Response Format\n\nThe tool returns a JSON response:\n\n```json\n{\n  \"action\": \"accept\",\n  \"content\": {\n    \"field1\": \"user value\",\n    \"field2\": true\n  }\n}\n```\n\n### Action Values\n\n| Action    | Meaning                                    |\n| --------- | ------------------------------------------ |\n| `accept`  | User provided a response (check `content`) |\n| `decline` | User declined to answer                    |\n| `cancel`  | User cancelled the prompt                  |\n\n## Schema Examples\n\n### Simple String Input\n\n```json\n{\n  \"type\": \"string\",\n  \"title\": \"API Key\",\n  \"description\": \"Enter your API key\"\n}\n```\n\n### Multiple Choice\n\n```json\n{\n  \"type\": \"string\",\n  \"enum\": [\"development\", \"staging\", \"production\"],\n  \"title\": \"Environment\",\n  \"description\": \"Select the target environment\"\n}\n```\n\n### Boolean Confirmation\n\n```json\n{\n  \"type\": \"boolean\",\n  \"title\": \"Confirm\",\n  \"description\": \"Are you sure you want to proceed?\"\n}\n```\n\n### Object with Multiple Fields\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"username\": {\n      \"type\": \"string\",\n      \"description\": \"Your username\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"description\": \"Your password\"\n    },\n    \"remember\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remember credentials\"\n    }\n  },\n  \"required\": [\"username\", \"password\"]\n}\n```\n\n### Number Input\n\n```json\n{\n  \"type\": \"integer\",\n  \"title\": \"Port Number\",\n  \"description\": \"Enter the port number (1024-65535)\",\n  \"minimum\": 1024,\n  \"maximum\": 65535\n}\n```\n\n## Example Usage\n\nHere's how an agent might use the user prompt tool:\n\n```text\nAgent: I need to deploy this application. Let me ask which environment to target.\n\n[Calls user_prompt with message: \"Which environment should I deploy to?\"\n and schema with enum: [\"development\", \"staging\", \"production\"]]\n\nUser selects: \"staging\"\n\nAgent: Great, I'll deploy to staging. Let me confirm this action.\n\n[Calls user_prompt with message: \"Deploy to staging? This will replace the current version.\"\n and schema with type: \"boolean\"]\n\nUser confirms: true\n\nAgent: Deploying to staging...\n```\n\n## UI Presentation\n\nHow the prompt appears depends on the interface:\n\n- **TUI**: Displays an interactive dialog with appropriate input controls\n- **CLI (exec mode)**: Prints the prompt and reads from stdin\n- **API/MCP**: Returns an elicitation request to the client\n\n> [!TIP]\n> **Best Practice**\n>\n> Provide clear, concise messages. Include context about why you're asking and what the information will be used for. Use schemas with descriptions to guide users on expected input format.\n\n## Handling Responses\n\nThe agent should handle all possible actions:\n\n- **accept**: Process the `content` and continue\n- **decline**: Acknowledge and try an alternative approach or explain what's needed\n- **cancel**: Stop the current operation gracefully\n\n> [!WARNING]\n> **Context Requirement**\n>\n> The user prompt tool requires an elicitation handler to be configured. It works in the TUI and CLI modes but may not be available in all contexts (e.g., some MCP client configurations).\n","_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md":"---\ntitle: \"Webhook Tool\"\ndescription: \"Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more.\"\nkeywords: docker agent, ai agents, tools, toolsets, webhook, slack, discord, telegram, ifttt, notifications\nlinkTitle: \"Webhook\"\nweight: 145\ncanonical: https://docs.docker.com/ai/docker-agent/tools/webhook/\n---\n\n_Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more._\n\n## Overview\n\nThe webhook toolset delivers a notification to a destination **you configure**. The\nagent supplies only the message text: it never sees or chooses the URL, because a\nwebhook URL is itself a credential (Slack and Mattermost embed a secret path,\nDiscord a token, IFTTT a key, Telegram a bot token).\n\nThis is not a general HTTP client — that is the [`api`](../api/index.md) toolset.\nThe webhook toolset owns *delivery*:\n\n- **At-least-once delivery.** Transient failures (`429`, `5xx`, network errors) are\n  retried with exponential backoff, honouring the server's `Retry-After`. A `4xx`\n  is permanent and fails immediately without wasting retries.\n- **Non-blocking.** The call returns as soon as the notification is queued, so a\n  slow or retrying endpoint never stalls the agent's turn. The agent is messaged\n  back **only if delivery ultimately fails**.\n- **Storm protection.** An identical message to the same destination inside a short\n  window is suppressed, and notifications are rate limited, so a looping agent\n  cannot flood a channel.\n- **Provider-shaped payloads.** Each service's wire format is applied for you.\n\n## Configuration\n\nThe destination lives in `webhook_config`. Use `${env.VAR}` for anything secret —\nvalues are expanded at call time and never stored in the config file.\n\n```yaml\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n| Field | Required | Description |\n| --- | --- | --- |\n| `url` | Yes | Webhook endpoint. Usually embeds a secret — prefer `${env.VAR}`. |\n| `provider` | No | Payload shape (default `generic`). |\n| `headers` | No | Extra headers, for endpoints authenticating with a token. |\n| `chat_id` | No | Destination chat — required for `provider: telegram`. |\n\n`timeout` on the toolset (seconds) overrides the per-request HTTP timeout.\n\n## Providers\n\n| Provider | Payload sent | Where the secret lives |\n| --- | --- | --- |\n| `slack`, `mattermost`, `rocketchat`, `googlechat`, `teams`, `generic` | `{\"text\": message}` | secret webhook URL |\n| `discord` | `{\"content\": message}` | token in the webhook URL |\n| `ifttt` | `{\"value1\": message, \"value2\": …, \"value3\": …}` | key in the webhook URL |\n| `telegram` | `{\"chat_id\": …, \"text\": message}` | bot token in the URL, plus `chat_id` |\n\nAliases are accepted: `msteams`/`microsoft_teams` → `teams`, `google_chat`/`gchat`\n→ `googlechat`, `rocket.chat` → `rocketchat`.\n\n### Per-service examples\n\n```yaml\n# Slack / Mattermost / Rocket.Chat — the URL is the credential\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n```yaml\n# Discord — the token is part of the webhook URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: discord\n      url: ${env.DISCORD_WEBHOOK_URL}\n```\n\n```yaml\n# Telegram — bot token in the URL, chat_id selects the destination chat\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: telegram\n      url: https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage\n      chat_id: \"123456789\"\n```\n\n```yaml\n# IFTTT — the key is part of the trigger URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: ifttt\n      url: https://maker.ifttt.com/trigger/build_failed/with/key/${env.IFTTT_KEY}\n```\n\n```yaml\n# Generic endpoint authenticating with a bearer token\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: generic\n      url: https://alerts.example.com/notify\n      headers:\n        Authorization: Bearer ${env.ALERTS_TOKEN}\n```\n\n## `send_webhook`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `message` | Yes | The message text to deliver. |\n| `value2`, `value3` | No | Extra IFTTT data fields (`provider: ifttt`). |\n\nReturns immediately once queued. On success nothing further happens; if delivery\nultimately fails, the agent receives a message saying so.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    instruction: If a check fails, notify the team with send_webhook.\n    toolsets:\n      - type: webhook\n        webhook_config:\n          provider: slack\n          url: ${env.SLACK_WEBHOOK_URL}\n```\n\n> [!NOTE]\n> Requests to non-public addresses are refused (the SSRF-safe HTTP client), and the\n> configured URL is never echoed back to the model or into error messages.\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md":"---\ntitle: Build checks\ndescription: |\n  BuildKit has built-in support for analyzing your build configuration based on\n  a set of pre-defined rules for enforcing Dockerfile and building best\n  practices.\nkeywords: buildkit, linting, dockerfile, frontend, rules\n---\n\nBuildKit has built-in support for analyzing your build configuration based on a\nset of pre-defined rules for enforcing Dockerfile and building best practices.\nAdhering to these rules helps avoid errors and ensures good readability of your\nDockerfile.\n\nChecks run as a build invocation, but instead of producing a build output, it\nperforms a series of checks to validate that your build doesn't violate any of\nthe rules. To run a check, use the `--check` flag:\n\n```console\n$ docker build --check .\n```\n\nTo learn more about how to use build checks, see\n[Checking your build configuration](https://docs.docker.com/build/checks/).\n\n<table>\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><a href=\"./stage-name-casing/\">StageNameCasing</a></td>\n      <td>Stage names should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-as-casing/\">FromAsCasing</a></td>\n      <td>The 'as' keyword should match the case of the 'from' keyword</td>\n    </tr>\n    <tr>\n      <td><a href=\"./no-empty-continuation/\">NoEmptyContinuation</a></td>\n      <td>Empty continuation lines will become errors in a future release</td>\n    </tr>\n    <tr>\n      <td><a href=\"./consistent-instruction-casing/\">ConsistentInstructionCasing</a></td>\n      <td>All commands within the Dockerfile should use the same casing (either upper or lower)</td>\n    </tr>\n    <tr>\n      <td><a href=\"./duplicate-stage-name/\">DuplicateStageName</a></td>\n      <td>Stage names should be unique</td>\n    </tr>\n    <tr>\n      <td><a href=\"./reserved-stage-name/\">ReservedStageName</a></td>\n      <td>Reserved words should not be used as stage names</td>\n    </tr>\n    <tr>\n      <td><a href=\"./json-args-recommended/\">JSONArgsRecommended</a></td>\n      <td>JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals</td>\n    </tr>\n    <tr>\n      <td><a href=\"./maintainer-deprecated/\">MaintainerDeprecated</a></td>\n      <td>The MAINTAINER instruction is deprecated, use a label instead to define an image author</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-arg-in-from/\">UndefinedArgInFrom</a></td>\n      <td>FROM command must use declared ARGs</td>\n    </tr>\n    <tr>\n      <td><a href=\"./workdir-relative-path/\">WorkdirRelativePath</a></td>\n      <td>Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-var/\">UndefinedVar</a></td>\n      <td>Variables should be defined before their use</td>\n    </tr>\n    <tr>\n      <td><a href=\"./multiple-instructions-disallowed/\">MultipleInstructionsDisallowed</a></td>\n      <td>Multiple instructions of the same type should not be used in the same stage</td>\n    </tr>\n    <tr>\n      <td><a href=\"./legacy-key-value-format/\">LegacyKeyValueFormat</a></td>\n      <td>Legacy key/value format with whitespace separator should not be used</td>\n    </tr>\n    <tr>\n      <td><a href=\"./redundant-target-platform/\">RedundantTargetPlatform</a></td>\n      <td>Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior</td>\n    </tr>\n    <tr>\n      <td><a href=\"./secrets-used-in-arg-or-env/\">SecretsUsedInArgOrEnv</a></td>\n      <td>Sensitive data should not be used in the ARG or ENV commands</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-default-arg-in-from/\">InvalidDefaultArgInFrom</a></td>\n      <td>Default value for global ARG results in an empty or invalid base image name</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-platform-flag-const-disallowed/\">FromPlatformFlagConstDisallowed</a></td>\n      <td>FROM --platform flag should not use a constant value</td>\n    </tr>\n    <tr>\n      <td><a href=\"./copy-ignored-file/\">CopyIgnoredFile</a></td>\n      <td>Attempting to Copy file that is excluded by .dockerignore</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-definition-description/\">InvalidDefinitionDescription (experimental)</a></td>\n      <td>Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-proto-casing/\">ExposeProtoCasing</a></td>\n      <td>Protocol in EXPOSE instruction should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-invalid-format/\">ExposeInvalidFormat</a></td>\n      <td>IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release</td>\n    </tr>\n  </tbody>\n</table>\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md":"---\ntitle: ConsistentInstructionCasing\ndescription: >-\n  All commands within the Dockerfile should use the same casing (either upper or lower)\naliases:\n  - /go/dockerfile/rule/consistent-instruction-casing/\n---\n\n## Output\n\n```text\nCommand 'EntryPoint' should be consistently cased\n```\n\n## Description\n\nInstruction keywords should use consistent casing (all lowercase or all\nuppercase). Using a case that mixes uppercase and lowercase, such as\n`PascalCase` or `snakeCase`, letters result in poor readability.\n\n## Examples\n\n❌ Bad: don't mix uppercase and lowercase.\n\n```dockerfile\nFrom alpine\nRun echo hello > /greeting.txt\nEntRYpOiNT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all uppercase.\n\n```dockerfile\nFROM alpine\nRUN echo hello > /greeting.txt\nENTRYPOINT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all lowercase.\n\n```dockerfile\nfrom alpine\nrun echo hello > /greeting.txt\nentrypoint [\"cat\", \"/greeting.txt\"]\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md":"---\ntitle: CopyIgnoredFile\ndescription: >-\n  Attempting to Copy file that is excluded by .dockerignore\naliases:\n  - /go/dockerfile/rule/copy-ignored-file/\n---\n\n## Output\n\n```text\nAttempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n```\n\n## Description\n\nWhen you use the Add or Copy instructions from within a Dockerfile, you should\nensure that the files to be copied into the image do not match a pattern\npresent in `.dockerignore`.\n\nFiles which match the patterns in a `.dockerignore` file are not present in the\ncontext of the image when it is built. Trying to copy or add a file which is\nmissing from the context will result in a build error.\n\n## Examples\n\nWith the given `.dockerignore` file:\n\n```text\n*/tmp/*\n```\n\n❌ Bad: Attempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./tmp/helloworld.txt /helloworld.txt\n```\n\n✅ Good: Copying a file which is not excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./forever/helloworld.txt /helloworld.txt\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md":"---\ntitle: DuplicateStageName\ndescription: >-\n  Stage names should be unique\naliases:\n  - /go/dockerfile/rule/duplicate-stage-name/\n---\n\n## Output\n\n```text\nDuplicate stage name 'foo-base', stage names should be unique\n```\n\n## Description\n\nDefining multiple stages with the same name results in an error because the\nbuilder is unable to uniquely resolve the stage name reference.\n\n## Examples\n\n❌ Bad: `builder` is declared as a stage name twice.\n\n```dockerfile\nFROM debian:latest AS builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS builder\n```\n\n✅ Good: stages have unique names.\n\n```dockerfile\nFROM debian:latest AS deb-builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS go-builder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md":"---\ntitle: ExposeInvalidFormat\ndescription: >-\n  IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release\naliases:\n  - /go/dockerfile/rule/expose-invalid-format/\n---\n\n## Output\n\n```text\nEXPOSE instruction should not define an IP address or host-port mapping, found '127.0.0.1:80:80'\n```\n\n## Description\n\nThe [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose) instruction\nin a Dockerfile is used to indicate which ports the container listens on at\nruntime. It should not include an IP address or host-port mapping, as this is\nnot the intended use of the `EXPOSE` instruction. Instead, it should only\nspecify the port number and optionally the protocol (TCP or UDP).\n\n> [!IMPORTANT]\n> This will become an error in a future release.\n\n## Examples\n\n❌ Bad: IP address and host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 127.0.0.1:80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n❌ Bad: Host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md":"---\ntitle: ExposeProtoCasing\ndescription: >-\n  Protocol in EXPOSE instruction should be lowercase\naliases:\n  - /go/dockerfile/rule/expose-proto-casing/\n---\n\n## Output\n\n```text\nDefined protocol '80/TcP' in EXPOSE instruction should be lowercase\n```\n\n## Description\n\nProtocol names in the [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose)\ninstruction should be specified in lowercase to maintain consistency and\nreadability. This rule checks for protocols that are not in lowercase and\nreports them.\n\n## Examples\n\n❌ Bad: protocol is not in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/TcP\n```\n\n✅ Good: protocol is in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/tcp\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md":"---\ntitle: FromAsCasing\ndescription: >-\n  The 'as' keyword should match the case of the 'from' keyword\naliases:\n  - /go/dockerfile/rule/from-as-casing/\n---\n\n## Output\n\n```text\n'as' and 'FROM' keywords' casing do not match\n```\n\n## Description\n\nWhile Dockerfile keywords can be either uppercase or lowercase, mixing case\nstyles is not recommended for readability. This rule reports violations where\nmixed case style occurs for a `FROM` instruction with an `AS` keyword declaring\na stage name.\n\n## Examples\n\n❌ Bad: `FROM` is uppercase, `AS` is lowercase.\n\n```dockerfile\nFROM debian:latest as builder\n```\n\n✅ Good: `FROM` and `AS` are both uppercase\n\n```dockerfile\nFROM debian:latest AS deb-builder\n```\n\n✅ Good: `FROM` and `AS` are both lowercase.\n\n```dockerfile\nfrom debian:latest as deb-builder\n```\n\n## Related errors\n\n- [`FileConsistentCommandCasing`](./consistent-instruction-casing.md)\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md":"---\ntitle: FromPlatformFlagConstDisallowed\ndescription: >-\n  FROM --platform flag should not use a constant value\naliases:\n  - /go/dockerfile/rule/from-platform-flag-const-disallowed/\n---\n\n## Output\n\n```text\nFROM --platform flag should not use constant value \"linux/amd64\"\n```\n\n## Description\n\nSpecifying `--platform` in the Dockerfile `FROM` instruction forces the image to build on only one target platform. This prevents building a multi-platform image from this Dockerfile and you must build on the same platform as specified in `--platform`.\n\nThe recommended approach is to:\n\n* Omit `FROM --platform` in the Dockerfile and use the `--platform` argument on the command line.\n* Use `$BUILDPLATFORM` or some other combination of variables for the `--platform` argument.\n* Stage name should include the platform, OS, or architecture name to indicate that it only contains platform-specific instructions.\n\n## Examples\n\n❌ Bad: using a constant argument for `--platform`\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using the default platform\n\n```dockerfile\nFROM alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using a meta variable\n\n```dockerfile\nFROM --platform=${BUILDPLATFORM} alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: used in a multi-stage build with a target architecture\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS build_amd64\n...\n\nFROM --platform=linux/arm64 alpine AS build_arm64\n...\n\nFROM build_${TARGETARCH} AS build\n...\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md":"---\ntitle: InvalidDefaultArgInFrom\ndescription: >-\n  Default value for global ARG results in an empty or invalid base image name\naliases:\n  - /go/dockerfile/rule/invalid-default-arg-in-from/\n---\n\n## Output\n\n```text\nUsing the global ARGs with default values should produce a valid build.\n```\n\n## Description\n\nAn `ARG` used in an image reference should be valid when no build arguments are used. An image build should not require `--build-arg` to be used to produce a valid build.\n\n## Examples\n\n❌ Bad: don't rely on an ARG being set for an image reference to be valid\n\n```dockerfile\nARG TAG\nFROM busybox:${TAG}\n```\n\n✅ Good: include a default for the ARG\n\n```dockerfile\nARG TAG=latest\nFROM busybox:${TAG}\n```\n\n✅ Good: ARG can be empty if the image would be valid with it empty\n\n```dockerfile\nARG VARIANT\nFROM busybox:stable${VARIANT}\n```\n\n✅ Good: Use a default value if the build arg is not present\n\n```dockerfile\nARG TAG\nFROM alpine:${TAG:-3.14}\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md":"---\ntitle: InvalidDefinitionDescription\ndescription: >-\n  Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\naliases:\n  - /go/dockerfile/rule/invalid-definition-description/\n---\n\n> [!NOTE]\n> This check is experimental and is not enabled by default. To enable it, see\n> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).\n\n## Output\n\n```text\nComment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\n```\n\n## Description\n\nThe [`--call=outline`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nand [`--call=targets`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nflags for the `docker build` command print descriptions for build targets and arguments.\nThe descriptions are generated from [Dockerfile comments](https://docs.docker.com/reference/cli/docker/buildx/build/#descriptions)\nthat immediately precede the `FROM` or `ARG` instruction\nand that begin with the name of the build stage or argument.\nFor example:\n\n```dockerfile\n# build-cli builds the CLI binary\nFROM alpine AS build-cli\n# VERSION controls the version of the program\nARG VERSION=1\n```\n\nIn cases where preceding comments are not meant to be descriptions,\nadd an empty line or comment between the instruction and the preceding comment.\n\n## Examples\n\n❌ Bad: A non-descriptive comment on the line preceding the `FROM` command.\n\n```dockerfile\n# a non-descriptive comment\nFROM scratch AS base\n\n# another non-descriptive comment\nARG VERSION=1\n```\n\n✅ Good: An empty line separating non-descriptive comments.\n\n```dockerfile\n# a non-descriptive comment\n\nFROM scratch AS base\n\n# another non-descriptive comment\n\nARG VERSION=1\n```\n\n✅ Good: Comments describing `ARG` keys and stages immediately proceeding the command.\n\n```dockerfile\n# base is a stage for compiling source\nFROM scratch AS base\n# VERSION This is the version number.\nARG VERSION=1\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md":"---\ntitle: JSONArgsRecommended\ndescription: >-\n  JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\naliases:\n  - /go/dockerfile/rule/json-args-recommended/\n---\n\n## Output\n\n```text\nJSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\n```\n\n## Description\n\n`ENTRYPOINT` and `CMD` instructions both support two different syntaxes for\narguments:\n\n- Shell form: `CMD my-cmd start`\n- Exec form: `CMD [\"my-cmd\", \"start\"]`\n\nWhen you use shell form, the executable runs as a child process to a shell,\nwhich doesn't pass signals. This means that the program running in the\ncontainer can't detect OS signals like `SIGTERM` and `SIGKILL` and respond to\nthem correctly.\n\n## Examples\n\n❌ Bad: the `ENTRYPOINT` command doesn't receive OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT my-program start\n# entrypoint becomes: /bin/sh -c my-program start\n```\n\nTo make sure the executable can receive OS signals, use the exec form for `CMD`\nand `ENTRYPOINT`, which lets you run the executable as the main process (`PID\n1`) in the container, avoiding a shell parent process.\n\n✅ Good: the `ENTRYPOINT` receives OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"my-program\", \"start\"]\n# entrypoint becomes: my-program start\n```\n\nNote that running programs as PID 1 means the program now has the special\nresponsibilities and behaviors associated with PID 1 in Linux, such as reaping\nchild processes.\n\n### Workarounds\n\nThere might still be cases when you want to run your containers under a shell.\nWhen using exec form, shell features such as variable expansion, piping (`|`)\nand command chaining (`&&`, `||`, `;`), are not available. To use such\nfeatures, you need to use shell form.\n\nHere are some ways you can achieve that. Note that this still means that\nexecutables run as child-processes of a shell.\n\n#### Create a wrapper script\n\nYou can create an entrypoint script that wraps your startup commands, and\nexecute that script with a JSON-formatted `ENTRYPOINT` command.\n\n✅ Good: the `ENTRYPOINT` uses JSON format.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nCOPY --chmod=755 <<EOT /entrypoint.sh\n#!/usr/bin/env bash\nset -e\nmy-background-process &\nmy-program start\nEOT\nENTRYPOINT [\"/entrypoint.sh\"]\n```\n\n#### Explicitly specify the shell\n\nYou can use the [`SHELL`](https://docs.docker.com/reference/dockerfile/#shell)\nDockerfile instruction to explicitly specify a shell to use. This will suppress\nthe warning since setting the `SHELL` instruction indicates that using shell\nform is a conscious decision.\n\n✅ Good: shell is explicitly defined.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nSHELL [\"/bin/bash\", \"-c\"]\nENTRYPOINT echo \"hello world\"\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md":"---\ntitle: LegacyKeyValueFormat\ndescription: >-\n  Legacy key/value format with whitespace separator should not be used\naliases:\n  - /go/dockerfile/rule/legacy-key-value-format/\n---\n\n## Output\n\n```text\n\"ENV key=value\" should be used instead of legacy \"ENV key value\" format\n```\n\n## Description\n\nThe correct format for declaring environment variables and build arguments in a\nDockerfile is `ENV key=value` and `ARG key=value`, where the variable name\n(`key`) and value (`value`) are separated by an equals sign (`=`).\nHistorically, Dockerfiles have also supported a space separator between the key\nand the value (for example, `ARG key value`). This legacy format is deprecated,\nand you should only use the format with the equals sign.\n\n## Examples\n\n❌ Bad: using a space separator for variable key and value.\n\n```dockerfile\nFROM alpine\nARG foo bar\n```\n\n✅ Good: use an equals sign to separate key and value.\n\n```dockerfile\nFROM alpine\nARG foo=bar\n```\n\n❌ Bad: multi-line variable declaration with a space separator.\n\n```dockerfile\nENV DEPS \\\n    curl \\\n    git \\\n    make\n```\n\n✅ Good: use an equals sign and wrap the value in quotes.\n\n```dockerfile\nENV DEPS=\"\\\n    curl \\\n    git \\\n    make\"\n```\n\n> [!NOTE]\n> Be aware of leading whitespace when converting multi-line legacy syntax to\n> the modern `key=value` format. In the legacy format, leading whitespace on\n> continuation lines is included in the value. In the modern format with\n> quoted values, leading whitespace inside the quotes is also preserved. If\n> you don't want leading whitespace in the value, make sure to remove it when\n> rewriting to the new format:\n>\n> ```dockerfile\n> ENV DEPS=\"\\\n> curl \\\n> git \\\n> make\"\n> ```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md":"---\ntitle: MaintainerDeprecated\ndescription: >-\n  The MAINTAINER instruction is deprecated, use a label instead to define an image author\naliases:\n  - /go/dockerfile/rule/maintainer-deprecated/\n---\n\n## Output\n\n```text\nMAINTAINER instruction is deprecated in favor of using label\n```\n\n## Description\n\nThe `MAINTAINER` instruction, used historically for specifying the author of\nthe Dockerfile, is deprecated. To set author metadata for an image, use the\n`org.opencontainers.image.authors` [OCI label](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys).\n\n## Examples\n\n❌ Bad: don't use the `MAINTAINER` instruction\n\n```dockerfile\nMAINTAINER moby@example.com\n```\n\n✅ Good: specify the author using the `org.opencontainers.image.authors` label\n\n```dockerfile\nLABEL org.opencontainers.image.authors=\"moby@example.com\"\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md":"---\ntitle: MultipleInstructionsDisallowed\ndescription: >-\n  Multiple instructions of the same type should not be used in the same stage\naliases:\n  - /go/dockerfile/rule/multiple-instructions-disallowed/\n---\n\n## Output\n\n```text\nMultiple CMD instructions should not be used in the same stage because only the last one will be used\n```\n\n## Description\n\nIf you have multiple `CMD`, `HEALTHCHECK`, or `ENTRYPOINT` instructions in your\nDockerfile, only the last occurrence is used. An image can only ever have one\n`CMD`, `HEALTHCHECK`, and `ENTRYPOINT`.\n\n## Examples\n\n❌ Bad: Duplicate instructions.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\"]\nENTRYPOINT [\"echo\", \"Hello, Sweden!\"]\n# Only \"Hello, Sweden!\" will be printed\n```\n\n✅ Good: only one `ENTRYPOINT` instruction.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\\nHello, Sweden!\"]\n```\n\nYou can have both a regular, top-level `CMD`\nand a separate `CMD` for a `HEALTHCHECK` instruction.\n\n✅ Good: only one top-level `CMD` instruction.\n\n```dockerfile\nFROM python:alpine\nRUN apk add curl\nHEALTHCHECK --interval=1s --timeout=3s \\\n  CMD [\"curl\", \"-f\", \"http://localhost:8080\"]\nCMD [\"python\", \"-m\", \"http.server\", \"8080\"]\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md":"---\ntitle: NoEmptyContinuation\ndescription: >-\n  Empty continuation lines will become errors in a future release\naliases:\n  - /go/dockerfile/rule/no-empty-continuation/\n---\n\n## Output\n\n```text\nEmpty continuation line found in: RUN apk add     gnupg     curl\n```\n\n## Description\n\nSupport for empty continuation (`/`) lines have been deprecated and will\ngenerate errors in future versions of the Dockerfile syntax.\n\nEmpty continuation lines are empty lines following a newline escape:\n\n```dockerfile\nFROM alpine\nRUN apk add \\\n\n    gnupg \\\n\n    curl\n```\n\nSupport for such empty lines is deprecated, and a future BuildKit release will\nremove support for this syntax entirely, causing builds to break. To avoid\nfuture errors, remove the empty lines, or add comments, since lines with\ncomments aren't considered empty.\n\n## Examples\n\n❌ Bad: empty continuation line between `EXPOSE` and 80.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n\n80\n```\n\n✅ Good: comments do not count as empty lines.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n# Port\n80\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md":"---\ntitle: RedundantTargetPlatform\ndescription: >-\n  Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\naliases:\n  - /go/dockerfile/rule/redundant-target-platform/\n---\n\n## Output\n\n```text\nSetting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\n```\n\n## Description\n\nA custom platform can be used for a base image. The default platform is the\nsame platform as the target output so setting the platform to `$TARGETPLATFORM`\nis redundant and unnecessary.\n\n## Examples\n\n❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.\n\n```dockerfile\nFROM --platform=$TARGETPLATFORM alpine AS builder\nRUN apk add --no-cache git\n```\n\n✅ Good: omit the `--platform` argument.\n\n```dockerfile\nFROM alpine AS builder\nRUN apk add --no-cache git\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md":"---\ntitle: ReservedStageName\ndescription: >-\n  Reserved words should not be used as stage names\naliases:\n  - /go/dockerfile/rule/reserved-stage-name/\n---\n\n## Output\n\n```text\n'scratch' is reserved and should not be used as a stage name\n```\n\n## Description\n\nReserved words should not be used as names for stages in multi-stage builds.\nThe reserved words are:\n\n- `context`\n- `scratch`\n\n## Examples\n\n❌ Bad: `scratch` and `context` are reserved names.\n\n```dockerfile\nFROM alpine AS scratch\nFROM alpine AS context\n```\n\n✅ Good: the stage name `builder` is not reserved.\n\n```dockerfile\nFROM alpine AS builder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md":"---\ntitle: SecretsUsedInArgOrEnv\ndescription: >-\n  Sensitive data should not be used in the ARG or ENV commands\naliases:\n  - /go/dockerfile/rule/secrets-used-in-arg-or-env/\n---\n\n## Output\n\n```text\nPotentially sensitive data should not be used in the ARG or ENV commands\n```\n\n## Description\n\nWhile it is common to pass secrets to running processes\nthrough environment variables during local development,\nsetting secrets in a Dockerfile using `ENV` or `ARG`\nis insecure because they persist in the final image.\nThis rule reports violations where `ENV` and `ARG` keys\nindicate that they contain sensitive data.\n\nInstead of `ARG` or `ENV`, you should use secret mounts,\nwhich expose secrets to your builds in a secure manner,\nand do not persist in the final image or its metadata.\nSee [Build secrets](https://docs.docker.com/build/building/secrets/).\n\n## Examples\n\n❌ Bad: using ARG to pass AWS credentials.\n\n```dockerfile\nARG AWS_ACCESS_KEY_ID\nARG AWS_SECRET_ACCESS_KEY\nRUN aws s3 cp s3://my-bucket/file .\n```\n\n✅ Good: using secret mounts with environment variables.\n\n```dockerfile\nRUN --mount=type=secret,id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --mount=type=secret,id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY \\\n    aws s3 cp s3://my-bucket/file .\n```\n\nTo build with these secrets:\n\n```console\n$ docker buildx build \\\n    --secret id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --secret id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY .\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md":"---\ntitle: StageNameCasing\ndescription: >-\n  Stage names should be lowercase\naliases:\n  - /go/dockerfile/rule/stage-name-casing/\n---\n\n## Output\n\n```text\nStage name 'BuilderBase' should be lowercase\n```\n\n## Description\n\nTo help distinguish Dockerfile instruction keywords from identifiers, this rule\nforces names of stages in a multi-stage Dockerfile to be all lowercase.\n\n## Examples\n\n❌ Bad: mixing uppercase and lowercase characters in the stage name.\n\n```dockerfile\nFROM alpine AS BuilderBase\n```\n\n✅ Good: stage name is all in lowercase.\n\n```dockerfile\nFROM alpine AS builder-base\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md":"---\ntitle: UndefinedArgInFrom\ndescription: >-\n  FROM command must use declared ARGs\naliases:\n  - /go/dockerfile/rule/undefined-arg-in-from/\n---\n\n## Output\n\n```text\nFROM argument 'VARIANT' is not declared\n```\n\n## Description\n\nThis rule warns for cases where you're consuming an undefined build argument in\n`FROM` instructions.\n\nInterpolating build arguments in `FROM` instructions can be a good way to add\nflexibility to your build, and lets you pass arguments that overriding the base\nimage of a stage. For example, you might use a build argument to specify the\nimage tag:\n\n```dockerfile\nARG ALPINE_VERSION=3.20\n\nFROM alpine:${ALPINE_VERSION}\n```\n\nThis makes it possible to run the build with a different `alpine` version by\nspecifying a build argument:\n\n```console\n$ docker buildx build --build-arg ALPINE_VERSION=edge .\n```\n\nThis check also tries to detect and warn when a `FROM` instruction reference\nmiss-spelled built-in build arguments, like `BUILDPLATFORM`.\n\n## Examples\n\n❌ Bad: the `VARIANT` build argument is undefined.\n\n```dockerfile\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n✅ Good: the `VARIANT` build argument is defined.\n\n```dockerfile\nARG VARIANT=\"-alpine3.20\"\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md":"---\ntitle: UndefinedVar\ndescription: >-\n  Variables should be defined before their use\naliases:\n  - /go/dockerfile/rule/undefined-var/\n---\n\n## Output\n\n```text\nUsage of undefined variable '$foo'\n```\n\n## Description\n\nThis check ensures that environment variables and build arguments are correctly\ndeclared before being used. While undeclared variables might not cause an\nimmediate build failure, they can lead to unexpected behavior or errors later\nin the build process.\n\nThis check does not evaluate undefined variables for `RUN`, `CMD`, and\n`ENTRYPOINT` instructions where you use the [shell form](https://docs.docker.com/reference/dockerfile/#shell-form).\nThat's because when you use shell form, variables are resolved by the command\nshell.\n\nIt also detects common mistakes like typos in variable names. For example, in\nthe following Dockerfile:\n\n```dockerfile\nFROM alpine\nENV PATH=$PAHT:/app/bin\n```\n\nThe check identifies that `$PAHT` is undefined and likely a typo for `$PATH`:\n\n```text\nUsage of undefined variable '$PAHT' (did you mean $PATH?)\n```\n\n## Examples\n\n❌ Bad: `$foo` is an undefined build argument.\n\n```dockerfile\nFROM alpine AS base\nCOPY $foo .\n```\n\n✅ Good: declaring `foo` as a build argument before attempting to access it.\n\n```dockerfile\nFROM alpine AS base\nARG foo\nCOPY $foo .\n```\n\n❌ Bad: `$foo` is undefined.\n\n```dockerfile\nFROM alpine AS base\nARG VERSION=$foo\n```\n\n✅ Good: the base image defines `$PYTHON_VERSION`\n\n```dockerfile\nFROM python AS base\nARG VERSION=$PYTHON_VERSION\n```\n\n","_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md":"---\ntitle: WorkdirRelativePath\ndescription: >-\n  Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes\naliases:\n  - /go/dockerfile/rule/workdir-relative-path/\n---\n\n## Output\n\n```text\nRelative workdir 'app/src' can have unexpected results if the base image changes\n```\n\n## Description\n\nWhen specifying `WORKDIR` in a build stage, you can use an absolute path, like\n`/build`, or a relative path, like `./build`. Using a relative path means that\nthe working directory is relative to whatever the previous working directory\nwas. So if your base image uses `/usr/local/foo` as a working directory, and\nyou specify a relative directory like `WORKDIR build`, the effective working\ndirectory becomes `/usr/local/foo/build`.\n\nThe `WorkdirRelativePath` build rule warns you if you use a `WORKDIR` with a\nrelative path without first specifying an absolute path in the same Dockerfile.\nThe rationale for this rule is that using a relative working directory for base\nimage built externally is prone to breaking, since working directory may change\nupstream without warning, resulting in a completely different directory\nhierarchy for your build.\n\n> [!NOTE]\n>\n> `WORKDIR` does not perform shell expansion. Paths beginning with `~` or\n> `~username` are treated as literal directory names and are not resolved to a\n> user's home directory.\n\n## Examples\n\n❌ Bad: this assumes that `WORKDIR` in the base image is `/`\n(if that changes upstream, the `web` stage is broken).\n\n```dockerfile\nFROM nginx AS web\nWORKDIR usr/share/nginx/html\nCOPY public .\n```\n\n✅ Good: a leading slash ensures that `WORKDIR` always ends up at the desired path.\n\n```dockerfile\nFROM nginx AS web\nWORKDIR /usr/share/nginx/html\nCOPY public .\n```\n\n","content/manuals/ai/sandboxes/agents/_index.md":"---\ntitle: Supported agents\nlinkTitle: Agents\nweight: 40\ndescription: AI coding agents supported by Docker Sandboxes.\nkeywords: docker sandboxes, ai agents, claude code, codex, cursor, gemini\n---\n\nDocker Sandboxes runs the following agents out of the box:\n\n- [Claude Code](claude-code/)\n- [Codex](codex/)\n- [Copilot](copilot/)\n- [Cursor](cursor/)\n- [Docker Agent](docker-agent/)\n- [Droid](droid/)\n- [Gemini](gemini/)\n- [Kiro](kiro/)\n- [OpenCode](opencode/)\n- [Shell](shell/) — agent-less sandbox for manual setup or testing\n\nWant to pre-install tools or customize an agent's environment?\nSee [Customize](../customize/).\n","content/manuals/ai/sandboxes/agents/claude-code.md":"---\ntitle: Claude Code\nweight: 10\ndescription: |\n  Use Claude Code in Docker Sandboxes with authentication, local models,\n  configuration, and YOLO mode for AI-assisted development.\nkeywords: docker sandboxes, claude code, anthropic, ai agent, sbx, local models, llmman, ollama\n---\n\nOfficial documentation: [Claude Code](https://code.claude.com/docs)\n\n## Quick start\n\nLaunch Claude Code in a sandbox by pointing it at a project directory:\n\n```console\n$ sbx run claude ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so `sbx run claude`\nfrom inside your project works too. To start Claude with a specific prompt:\n\n```console\n$ sbx run claude --name my-sandbox -- \"Add error handling to the login function\"\n```\n\nEverything after `--` is passed directly to Claude Code. You can also pipe in a\nprompt from a file with `-- \"$(cat prompt.txt)\"`.\n\n## Authentication\n\nClaude Code requires either an Anthropic API key or a Claude subscription.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set anthropic\n```\n\n**Claude subscription**: If no API key is set, use the `/login` command inside\nClaude Code to authenticate via OAuth.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.claude`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Remote control\n\nTo use Claude Code's `/remote-control` command inside a sandbox, turn on remote\ncontrol:\n\n```console\n$ sbx settings set claude.remoteControl true\n```\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nclaude --dangerously-skip-permissions\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--dangerously-skip-permissions` is\npreserved:\n\n```console\n$ sbx run claude -- -c   # runs claude --dangerously-skip-permissions -c\n```\n\nWhen the first argument is a bare word, such as the `agents` subcommand, it\nreplaces the defaults instead.\n\nSee the [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference)\nfor available options.\n\n## Agents view\n\nClaude Code's [agents view](https://code.claude.com/docs/en/agent-view)\nstarts background sessions that run tasks in parallel. Pair it with\n[clone mode](../workflows/git.md#clone-mode) to keep their changes inside the\nsandbox:\n\n```console\n$ sbx run --clone claude -- agents\n```\n\nThis invocation replaces the\n[default startup command](#default-startup-command), so it doesn't\ninclude `--dangerously-skip-permissions` and you can't switch to\nbypass-permissions mode inside the sandbox. To work around this, either\nuse Claude Code's auto mode or pass the flag explicitly:\n\n```console\n$ sbx run --clone claude -- --dangerously-skip-permissions agents\n```\n\nClaude Code may use branches or worktrees to keep changes from its background\nsessions separate. This depends on the task, Claude Code configuration, and\nproject instructions. The `--clone` flag doesn't control this behavior. Claude\nCode creates any branches and worktrees inside the sandbox, not in your host\ncheckout.\n\nTo review a branch created by a session, fetch the\n`sandbox-<sandbox-name>` remote from the host:\n\n```console\n$ git fetch sandbox-<sandbox-name>\n$ git diff main..sandbox-<sandbox-name>/<branch>\n```\n\nSee [Git workflows](../workflows/git.md) for clone-mode details.\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:claude-code`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n\n## Use a local model\n\nThe `--model` flag routes Claude Code's Anthropic API requests to a model\nserved on your host. This feature is experimental and isn't supported on\nWindows.\n\nEnable the feature:\n\n```console\n$ sbx settings set platform.allowExperimentalFeatures true\n$ sbx settings set feature.model true\n```\n\nTo use the bundled `llmman` model server, pass a GGUF model reference or short\nname:\n\n```console\n$ sbx run --model gemma4 claude\n```\n\nOn first use, `sbx` starts `llmman`, pulls the model, and leaves the server\nrunning on your host. Later sandboxes reuse the server and its model store.\n\nTo use an existing Ollama installation instead, set the provider to `ollama`:\n\n```console\n$ sbx run --model gemma4 --provider ollama claude\n```\n\nOllama must already be installed and running. `sbx` connects to it but doesn't\nstart or manage the Ollama process.\n\nYou can also change the model for an existing sandbox:\n\n```console\n$ sbx run --name <sandbox-name> --model <model-name>\n```\n\nChanging the model recreates the sandbox container. The workspace and\nkit-owned volumes persist.\n\nTo use Docker Model Runner instead, see\n[Run Claude Code in a Docker Sandbox with Docker Model Runner](/guides/claude-code-sandbox-model-runner/).\n","content/manuals/ai/sandboxes/agents/codex.md":"---\ntitle: Codex\nweight: 20\ndescription: |\n  Use OpenAI Codex in Docker Sandboxes with API key authentication and YOLO\n  mode configuration.\nkeywords: docker sandboxes, codex, openai, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Codex in a\nsandboxed environment.\n\nOfficial documentation: [Codex CLI](https://developers.openai.com/codex/cli)\n\n## Quick start\n\nCreate a sandbox and run Codex for a project directory:\n\n```console\n$ sbx run codex ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run codex\n```\n\n## Authentication\n\nIf you haven't stored an OpenAI credential, `sbx run codex` prompts you to\nauthenticate on your host before launching the sandbox. The flow runs on the\nhost, so credentials are never exposed inside the sandbox.\n\nTo set up authentication ahead of time, choose one of the following methods.\n\n**OAuth**: Start the OAuth flow on your host with:\n\n```console\n$ sbx secret set openai --oauth\n```\n\nThis opens a browser window for authentication and stores the resulting tokens\nin your OS keychain. The OAuth flow runs on the host, not inside the sandbox,\nso browser-based authentication works without any extra setup.\n\n**API key**: Store your OpenAI API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n```\n\nSee [Credentials](../configuration/credentials.md) for more details.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.codex`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncodex --dangerously-bypass-approvals-and-sandbox\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). A bare word — such as a prompt — replaces the\ndefaults instead, so lead with the flag to keep bypass mode:\n\n```console\n$ sbx run codex -- --dangerously-bypass-approvals-and-sandbox \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:codex`\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/copilot.md":"---\ntitle: Copilot\nweight: 30\ndescription: |\n  Use GitHub Copilot in Docker Sandboxes with GitHub token authentication and\n  trusted folder configuration.\nkeywords: docker sandboxes, github copilot, ai agent, github token, sbx\n---\n\nThis guide covers authentication, configuration, and usage of GitHub Copilot\nin a sandboxed environment.\n\nOfficial documentation: [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli)\n\n## Quick start\n\nCreate a sandbox and run Copilot for a project directory:\n\n```console\n$ sbx run copilot ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run copilot\n```\n\n## Authentication\n\nCopilot requires a GitHub token with Copilot access. Store your token using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set github --command 'gh auth token'\n```\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCopilot is configured to trust the workspace directory by default, so it\noperates without repeated confirmations for workspace files.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncopilot --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run copilot -- -p \"review this PR\"   # runs copilot --yolo -p \"review this PR\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:copilot`\n\nPreconfigured to trust the workspace directory.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/cursor.md":"---\ntitle: Cursor\nweight: 40\ndescription: |\n  Use Cursor in Docker Sandboxes with API key or proxy-managed OAuth\n  authentication.\nkeywords: docker sandboxes, cursor, cursor agent, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Cursor in a\nsandboxed environment.\n\nOfficial documentation: [Cursor CLI](https://cursor.com/cli)\n\n## Quick start\n\nCreate a sandbox and run Cursor for a project directory:\n\n```console\n$ sbx run cursor ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run cursor\n```\n\n## Authentication\n\nCursor supports two authentication methods: an API key or OAuth.\n\n**API key**: Store your Cursor API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set cursor\n```\n\n**OAuth**: If no API key is set, Cursor prompts you to sign in interactively\non first run. The proxy intercepts the token exchange with\n`api2.cursor.sh/auth/poll`, so credentials are managed by the host and aren't\nstored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.cursor`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCursor reads `AGENTS.md` from the workspace for agent-specific instructions.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncursor-agent --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run cursor -- -p \"refactor this\"   # runs cursor-agent --yolo -p \"refactor this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:cursor-agent-docker`\n\nPreconfigured with HTTP/1.1 and server-sent events for agent traffic so\nrequests flow through the host proxy. Authentication state is persisted across\nsandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/docker-agent.md":"---\ntitle: Docker Agent\nweight: 50\ndescription: |\n  Use Docker Agent in Docker Sandboxes with multi-provider authentication\n  supporting OpenAI, Anthropic, and more.\nkeywords: docker sandboxes, docker agent, openai, anthropic, sbx\n---\n\nOfficial documentation: [Docker Agent](/manuals/ai/docker-agent/_index.md)\n\n## Quick start\n\nCreate a sandbox and run Docker Agent for a project directory:\n\n```console\n$ sbx run docker-agent ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so\n`sbx run docker-agent` from inside your project works too.\n\n## Authentication\n\nDocker Agent supports multiple providers. Store keys for the providers you want\nto use with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set nebius\n$ sbx secret set mistral\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. Docker Agent detects\navailable credentials and routes requests to the appropriate provider.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ndocker-agent run --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). When the first argument is a bare word — such\nas the `run` subcommand or a config file — it replaces the defaults, so include\n`run --yolo` yourself:\n\n```console\n$ sbx run docker-agent -- run --yolo agent.yml\n```\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:docker-agent`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n","content/manuals/ai/sandboxes/agents/droid.md":"---\ntitle: Droid\nweight: 60\ndescription: |\n  Use Droid in Docker Sandboxes with API key or OAuth authentication.\nkeywords: docker sandboxes, droid, factory, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Droid, an AI\ncoding agent by Factory, in a sandboxed environment.\n\nOfficial documentation: [Droid](https://docs.factory.ai/)\n\n## Quick start\n\nCreate a sandbox and run Droid for a project directory:\n\n```console\n$ sbx run droid ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run droid\n```\n\n## Authentication\n\nDroid requires a [Factory account](https://factory.ai). Both authentication\nmethods authenticate you to Factory's service directly — unlike other agents\nwhere you supply a model provider key, Factory manages model access through\nyour Factory account.\n\n**API key**: Store your Factory API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set droid\n```\n\n**OAuth**: If no API key is set, Droid prompts you to authenticate\ninteractively on first run. The proxy handles the OAuth flow, so credentials\naren't stored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nThe sandbox runs `droid` with no implicit flags. Args after `--` are passed\nstraight through:\n\n```console\n$ sbx run droid -- exec \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:droid-docker`\n\nPreconfigured to run without approval prompts. Authentication state is\npersisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/gemini.md":"---\ntitle: Gemini\nweight: 70\ndescription: |\n  Use Google Gemini in Docker Sandboxes with proxy-managed authentication and\n  API key configuration.\nkeywords: docker sandboxes, gemini, google, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Google Gemini in\na sandboxed environment.\n\nOfficial documentation: [Gemini CLI](https://geminicli.com/docs/)\n\n## Quick start\n\nCreate a sandbox and run Gemini for a project directory:\n\n```console\n$ sbx run gemini ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run gemini\n```\n\n## Authentication\n\nGemini requires either a Google API key or a Google account with Gemini access.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set google\n```\n\n**Google account**: If no API key is set, Gemini prompts you to sign in\ninteractively when it starts. Interactive authentication is scoped to the\nsandbox and doesn't persist if you remove and recreate it.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.gemini`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nThe sandbox disables Gemini's built-in sandbox tool (since the sandbox itself\nprovides isolation).\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ngemini --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run gemini -- -p \"explain this\"   # runs gemini --yolo -p \"explain this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:gemini`\n\nGemini is configured to disable its built-in OAuth flow. Authentication is\nmanaged through the proxy with API keys.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/kiro.md":"---\ntitle: Kiro\nweight: 80\ndescription: |\n  Use Kiro in Docker Sandboxes with device flow authentication for interactive\n  AI-assisted development.\nkeywords: docker sandboxes, kiro, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Kiro in a\nsandboxed environment.\n\nOfficial documentation: [Kiro CLI](https://kiro.dev/docs/cli/)\n\n## Quick start\n\nCreate a sandbox and run Kiro for a project directory:\n\n```console\n$ sbx run kiro ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run kiro\n```\n\nOn first run, Kiro prompts you to authenticate using device flow.\n\n## Authentication\n\nKiro uses device flow authentication, which requires interactive login through\na web browser. This method provides secure authentication without storing API\nkeys directly.\n\n### Device flow login\n\nWhen you first run Kiro, it prompts you to authenticate:\n\n1. Kiro displays a URL and a verification code\n2. Open the URL in your web browser\n3. Enter the verification code\n4. Complete the authentication flow in your browser\n5. Return to the terminal - Kiro proceeds automatically\n\nThe authentication session is persisted in the sandbox and doesn't require\nrepeated login unless you destroy and recreate the sandbox.\n\n### Manual login\n\nYou can trigger the login flow manually:\n\n```console\n$ sbx run kiro --name <sandbox-name> -- login --use-device-flow\n```\n\nThis command initiates device flow authentication without starting a coding\nsession.\n\n### Authentication persistence\n\nKiro stores authentication state in `~/.local/share/kiro-cli/data.sqlite3`\ninside the sandbox. This database persists as long as the sandbox exists. If\nyou destroy the sandbox, you'll need to authenticate again when you recreate\nit.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nKiro requires minimal configuration. The agent runs with trust-all-tools mode\nby default, which lets it execute commands without repeated approval prompts.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nkiro chat --trust-all-tools\n```\n\nWhen the first argument after `--` is a flag (begins with `-`), it's added\nafter the defaults — for example, `sbx run kiro -- --resume` runs\n`kiro chat --trust-all-tools --resume`. When the first argument is a bare word,\nit replaces the defaults, which is why `sbx run kiro -- login --use-device-flow`\nruns the login subcommand on its own. To run `chat` with extra arguments of\nyour own, include the subcommand:\n\n```console\n$ sbx run kiro -- chat --trust-all-tools --resume\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:kiro`\n\nAuthentication state is persisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/opencode.md":"---\ntitle: OpenCode\nweight: 90\ndescription: |\n  Use OpenCode in Docker Sandboxes with multi-provider authentication and TUI\n  interface for AI development.\nkeywords: docker sandboxes, opencode, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of OpenCode in a\nsandboxed environment.\n\nOfficial documentation: [OpenCode](https://opencode.ai/docs)\n\n## Quick start\n\nCreate a sandbox and run OpenCode for a project directory:\n\n```console\n$ sbx run opencode ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run opencode\n```\n\nOpenCode launches a TUI (text user interface) where you can select your\npreferred LLM provider and interact with the agent.\n\n## Authentication\n\nOpenCode supports multiple providers. Store keys for the providers you want to\nuse with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set groq\n$ sbx secret set aws\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. OpenCode detects\navailable credentials and offers those providers in the TUI.\n\n### OpenCode Zen API keys\n\nOpenCode Zen API keys aren't part of the built-in OpenCode credentials that\n`sbx secret set` supports. To use an OpenCode Zen API key, store it as a\n[custom secret](../configuration/credentials.md#custom-secrets):\n\nSet the `OPENCODE_API_KEY` environment variable on the host, then store it:\n\n```console\n$ sbx secret set-custom \\\n    --host opencode.ai \\\n    --env OPENCODE_API_KEY \\\n    --value \"$OPENCODE_API_KEY\"\n```\n\nCustom secrets keep the real key in the host secret store. The sandbox receives\n`OPENCODE_API_KEY` as a placeholder, and the host-side proxy replaces that\nplaceholder with the real key on requests to `opencode.ai`.\n\nOpenCode Zen also requires network access to `opencode.ai`:\n\n```console\n$ sbx policy allow network opencode.ai:443\n```\n\nIf you add a global custom secret, recreate existing OpenCode sandboxes so the\nnew environment variable is available inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nOpenCode uses a TUI interface and doesn't require extensive configuration\nfiles. The agent prompts you to select a provider when it starts, and you can\nswitch providers during a session.\n\n### Default startup command\n\nThe sandbox runs `opencode` with no implicit flags. Args after `--` are passed\nstraight through. For example, to resume an existing session:\n\n```console\n$ sbx run opencode -- -s <session-id>\n```\n\n### TUI mode\n\nOpenCode launches in TUI mode by default. The interface shows:\n\n- Available LLM providers (based on configured credentials)\n- Current conversation history\n- File operations and tool usage\n- Real-time agent responses\n\nUse keyboard shortcuts to navigate the interface and interact with the agent.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:opencode`\n\nOpenCode supports multiple LLM providers with automatic credential injection\nthrough the sandbox proxy.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","content/manuals/ai/sandboxes/agents/shell.md":"---\ntitle: Shell\nweight: 100\ndescription: Run an agent-less sandbox with a Bash login shell for manual setup, testing custom agent implementations, or inspecting a running environment.\nkeywords: sandboxes, sbx, shell, agent, manual setup, testing\n---\n\n`sbx run shell` drops you into a Bash login shell inside a sandbox with no\npre-installed agent binary. It's useful for installing and configuring\nagents manually, testing custom implementations, or inspecting a running\nenvironment.\n\n```console\n$ sbx run shell ~/my-project\n```\n\nThe workspace path defaults to the current directory. To run a one-off\ncommand instead of an interactive shell, pass it after `--`:\n\n```console\n$ sbx run shell -- -c \"echo 'Hello from sandbox'\"\n```\n\n## Default startup command\n\nWithout extra args, the sandbox runs `bash -l`. When the first argument after\n`--` is a flag (begins with `-`), it's added after `-l`, so login-shell\nbehavior is preserved:\n\n```console\n$ sbx run shell -- -c \"echo hi\"   # runs bash -l -c \"echo hi\"\n```\n\nWhen the first argument is a bare word, it replaces `-l` instead.\n\nStore credentials using [stored secrets](../configuration/credentials.md#stored-secrets)\nbefore running the sandbox. The proxy injects them into outbound API requests;\ncredentials are never stored inside the VM:\n\n```console\n$ sbx secret set anthropic\n$ sbx secret set openai\n```\n\nOnce inside the shell, you can install agents using their standard methods,\nfor example `npm install -g @continuedev/cli`. For complex setups, build a\n[custom template](../customize/templates.md) instead of installing\ninteractively each time.\n\n## Base image\n\nThe shell sandbox uses the `shell` base image — the common base environment\nwithout a pre-installed agent.\n","content/manuals/dhi/tools/_index.md":"---\ntitle: Tools\ndescription: Interfaces and tools for browsing, managing, and automating Docker Hardened Images.\nweight: 25\nparams:\n  grid_tools:\n    - title: Use Docker Hub\n      description: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\n      icon: squares-2x2\n      link: /dhi/tools/hub/\n    - title: CLI\n      description: Install and use the `docker dhi` command-line interface to browse the catalog, inspect images, and manage mirrors from your terminal.\n      icon: command-line\n      link: /dhi/tools/cli/\n    - title: MCP server\n      description: Connect an AI assistant to the DHI catalog to search repositories, inspect images, retrieve SBOMs, and check CVEs using plain language.\n      icon: cpu-chip\n      link: /dhi/tools/mcp/\n    - title: Use the DHI Terraform provider\n      description: Use the DHI Terraform provider to manage mirrors and automate DHI configuration as infrastructure as code.\n      icon: wrench-screwdriver\n      link: /dhi/tools/terraform/\n    - title: Use the DHI API\n      description: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\n      icon: code-bracket\n      link: /dhi/tools/api/\n---\n\nDocker Hardened Images can be accessed and managed through several interfaces.\nChoose the tool that fits your workflow.\n\n{{< grid items=\"grid_tools\" >}}\n","content/manuals/dhi/tools/api.md":"---\ntitle: Use the DHI API\nlinktitle: API\ndescription: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\nweight: 50\nkeywords: dhi api, docker hardened images api, graphql api, dhi endpoint, dhi authentication\n---\n\nThe DHI API is a GraphQL API for querying Docker Hardened Images data\nprogrammatically, for use cases like building automation or dashboards on\ntop of DHI data.\n\n## Endpoint\n\nSend requests as `POST` requests to:\n\n```text\nhttps://api.dso.docker.com/v1/graphql\n```\n\n## Request format\n\nThe API accepts standard GraphQL requests: a JSON body with a `query` and,\noptionally, `variables`.\n\n```console\n$ curl https://api.dso.docker.com/v1/graphql \\\n  -H \"Authorization: Bearer <token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"...\", \"variables\": { ... }}'\n```\n\nEvery query takes a `Context` argument (conventionally named `ctx` in the\n`variables` object) alongside its query-specific arguments:\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `ctx` | `Context` | Yes | Scopes the request to an organization. |\n| `ctx.organization` | `String` | Yes | The Docker organization the token belongs to. |\n\n## Authentication\n\nAn [organization access token](/manuals/enterprise/security/access-tokens.md)\n(OAT) or personal access token (PAT) isn't used directly as the bearer\ntoken. Exchange it first for an access token:\n\n```console\n$ curl -X POST https://hub.docker.com/v2/auth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"identifier\": \"<identifier>\", \"secret\": \"<token>\"}'\n```\n\nFor `identifier`, use your Docker Hub username with a PAT, or the\norganization name with an OAT. The response contains the access token:\n\n```json\n{ \"access_token\": \"...\" }\n```\n\nPass that `access_token` as `Authorization: Bearer <access_token>`. Also set\n`ctx.organization` in `variables` to the organization the token belongs to\n(see [Request format](#request-format)).\n\n## Response format\n\nResponses follow the standard GraphQL envelope:\n\n| Key | Description |\n|---|---|\n| `data` | The requested fields. A field is `null` if it couldn't be resolved, for example due to an authorization failure. |\n| `errors` | Present when a field failed to resolve. Includes a `message` and a `path` identifying which field failed. |\n| `extensions` | Metadata such as a `correlation_id`, useful when reporting an issue. |\n\nFor example, an unauthenticated request, or a request for data your token\ncan't access, returns a `null` result under `data` alongside an authorization\nerror in `errors`, rather than an HTTP-level failure:\n\n```json\n{\n  \"errors\": [\n    {\n      \"message\": \"You are not allowed to read data for this team\",\n      \"path\": [\"someQuery\"],\n      \"extensions\": { \"code\": \"DOWNSTREAM_SERVICE_ERROR\", \"status\": 403 }\n    }\n  ],\n  \"data\": { \"someQuery\": null },\n  \"extensions\": { \"correlation_id\": \"...\" }\n}\n```\n\n## Queries\n\n### `imagePackagesForImageCoords`\n\nFetches every package in an image, every CVE reported against it, and\nwhether Docker suppresses that CVE, by digest. See [Query VEX for a Docker\nHardened Image](/manuals/dhi/how-to/vex-api.md) for a guided example.\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `digest` | `String` | Yes | The image's platform manifest digest, not the multi-arch index digest. |\n| `hostName` | `String` | Yes | `hub.docker.com` or `docker.io`. |\n| `repoName` | `String` | Yes | Repository name, with or without the namespace prefix. |\n| `includeExcepted` | `Boolean` | No | Include suppressed CVEs in the response alongside the reason for suppression. Without it, the response only shows the netted list, with no visibility into what was suppressed. |\n| `includeNodsa` | `Boolean` | No | Include Debian NODSA exclusions, which make up most suppressions on a Debian-based image. |\n| `includePublic` | `Boolean` | No | Also include public images when `ctx.organization` scopes the request to an organization. Not needed for a typical lookup. |\n\nKeep the requested response fields limited to what you plan to render.\nFields such as `locations`, `description`, `vulnerableRange`, and `epss`\nincrease response size substantially and aren't needed for a CVE-count or\nsuppressed-CVE view.\n\n#### Response fields\n\n`vulnerabilityExceptions` only contains records that actually suppress a\nCVE, so it always lines up with `isExcepted`: an empty array means the CVE\nis live. Use `isExcepted` as your filter for \"is this CVE suppressed.\"\n\n| Field | Meaning |\n|---|---|\n| `isExcepted` | Docker suppresses this CVE for this image. Use this to filter. |\n| `sourceType` | `EXTERNAL` (Debian NODSA), `MANUAL_EXCEPTION` (Docker analyst exception), or `VEX_STATEMENT` (an ingested VEX document). |\n| `type` | `FALSE_POSITIVE` and `ACCEPTED_RISK` suppress the CVE. `UNDER_INVESTIGATION` and `AFFECTED` don't. |\n| `justification` | The OpenVEX justification value. Always `null` for NODSA exclusions. |\n| `additionalDetails` | Free-text rationale for the suppression. |\n| `isDhiStatement` | Whether the statement is inherited from the DHI base image. |\n| `id` | Stable identifier for the statement. |\n\n#### Mapping to OpenVEX\n\nIf your pipeline consumes OpenVEX documents (for example, Trivy's `--vex`\nflag), each suppressed record maps as follows:\n\n| OpenVEX field | Source |\n|---|---|\n| `vulnerability.name` | `sourceId` |\n| `products[].@id` | The parent package's `purl` |\n| `status` | `not_affected` (from `type: FALSE_POSITIVE`) |\n| `justification` | `justification`, defaulting to `vulnerable_code_cannot_be_controlled_by_adversary` for NODSA exclusions |\n| `status_notes` | `additionalDetails` |\n| `@id` | `id` |\n","content/manuals/dhi/tools/cli.md":"---\ntitle: Use the DHI CLI\nlinkTitle: CLI\nweight: 20\nkeywords: docker dhi, CLI, command line, docker hardened images\ndescription: Learn how to install and use docker dhi, the command-line interface for managing Docker Hardened Images.\naliases:\n  - /dhi/how-to/cli/\n---\n\nThe `docker dhi` command-line interface (CLI) is a tool for managing Docker Hardened Images:\n- Browse the catalog of available DHI images and their metadata\n- View attestations for DHI images, including SBOMs and provenance\n- Mirror DHI images to your Docker Hub organization\n- Create and manage customizations of DHI images\n- Generate authentication for enterprise package repositories\n- Monitor customization builds\n\n## Installation\n\nThe `docker dhi` CLI is available in [Docker Desktop](https://docs.docker.com/desktop/) version 4.65 and later.\nYou can also install the standalone `dhictl` binary.\n\n### Docker Desktop\n\nThe `docker dhi` command is included in Docker Desktop 4.65 and later. No additional installation is required.\n\n### Standalone binary\n\n1. Download the `dhictl` binary for your platform from the\n   [releases](https://github.com/docker-hardened-images/dhictl/releases) page.\n2. Move it to a directory in your `PATH`:\n    - `mv dhictl /usr/local/bin/` on _Linux_ and _macOS_\n    - Move `dhictl.exe` to a directory in your `PATH` on _Windows_\n\n## Usage\n\nEvery command has built-in help accessible with the `--help` flag:\n\n```console\n$ docker dhi --help\n$ docker dhi catalog list --help\n```\n\n### Browse the DHI catalog\n\nList all available DHI images:\n\n```console\n$ docker dhi catalog list\n```\n\nFilter by type, name, or compliance:\n\n```console\n$ docker dhi catalog list --type image\n$ docker dhi catalog list --filter golang\n$ docker dhi catalog list --fips\n$ docker dhi catalog list --stig\n```\n\nGet details of a specific image, including available tags and CVE counts:\n\n```console\n$ docker dhi catalog get <image-name>\n```\n\n### View attestations\n\nList all attestations attached to a DHI image:\n\n```console\n$ docker dhi attestation list dhi/nginx:1.27\n$ docker dhi attestation list dhi/nginx:1.27 --platform linux/amd64\n$ docker dhi attestation list dhi/nginx:1.27 --predicate-type https://slsa.dev/provenance/v1\n$ docker dhi attestation list dhi/nginx:1.27 --json\n```\n\nGet a specific attestation by its referrer digest:\n\n```console\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest>\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest> -o provenance.json\n```\n\nDisplay the SPDX SBOM for an image:\n\n```console\n$ docker dhi attestation sbom dhi/nginx:1.27\n$ docker dhi attestation sbom dhi/nginx:1.27 --platform linux/amd64\n```\n\n### Mirror DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nStart mirroring one or more DHI images to your Docker Hub organization:\n\n```console\n$ docker dhi mirror start --org my-org \\\n  dhi/golang,my-org/dhi-golang \\\n  dhi/nginx,my-org/dhi-nginx \\\n  dhi/prometheus-chart,my-org/dhi-prometheus-chart\n```\n\nMirror with dependencies:\n\n```console\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --dependencies\n```\n\nList mirrored images in your organization:\n\n```console\n$ docker dhi mirror list --org my-org\n```\n\nFilter mirrored images by name or type:\n\n```console\n$ docker dhi mirror list --org my-org --filter python\n$ docker dhi mirror list --org my-org --type image\n$ docker dhi mirror list --org my-org --type helm-chart\n```\n\nStop mirroring one or more images:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org\n$ docker dhi mirror stop dhi-python dhi-golang --org my-org\n```\n\nStop mirroring and delete the repositories:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org --delete\n$ docker dhi mirror stop dhi-golang --org my-org --delete --force\n```\n\n### Customize DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nThe CLI can be used to create and manage DHI image customizations. For detailed\ninstructions on creating customizations using the GUI, see [Customize a Docker\nHardened Image](../how-to/customize.md).\n\nThe following is a quick reference for CLI commands. For complete details on all\noptions and flags, see the\n[CLI reference](/reference/cli/docker/dhi/).\n\n```console\n# Prepare a single customization scaffold\n$ docker dhi customization prepare golang 1.25 \\\n  --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\" \\\n  > my-customization.yaml\n\n# Prepare a bulk customization scaffold (pipe JSON array via stdin)\n$ echo '[{\"destination\":\"my-org/dhi-golang\",\"tag-definition-id\":\"golang/alpine-3.23/1.24-dev\"}]' \\\n  | docker dhi customization prepare --name \"golang with git\" --org my-org \\\n  > my-customization.yaml\n\n# Create a customization\n$ docker dhi customization create my-customization.yaml --org my-org\n\n# Create with flag overrides (flags take precedence over the YAML file)\n$ docker dhi customization create my-customization.yaml --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\"\n\n# List customizations\n$ docker dhi customization list --org my-org\n\n# Filter customizations by name, repository, or source\n$ docker dhi customization list --org my-org --filter git\n$ docker dhi customization list --org my-org --repo dhi-golang\n$ docker dhi customization list --org my-org --source golang\n\n# Get a customization by ID\n$ docker dhi customization get <id> --org my-org\n\n# Update a customization\n# The YAML file must include the 'id' field to identify the customization to update\n$ docker dhi customization edit my-customization.yaml --org my-org\n\n# Delete a customization by ID\n$ docker dhi customization delete <id> --org my-org\n\n# Delete multiple customizations\n$ docker dhi customization delete <id1> <id2> --org my-org\n\n# Delete without confirmation prompt\n$ docker dhi customization delete <id> --org my-org --force\n```\n\nFor a complete reference of all YAML fields, see\n[Image customization YAML file](/dhi/how-to/customize/#image-customization-yaml-file).\n\n### Enterprise package authentication\n\n{{< summary-bar feature_name=\"Docker Hardened Images Enterprise\" >}}\n\nGenerate authentication credentials for accessing the enterprise hardened\npackage repository. These credentials are used when configuring your package\nmanager to install compliance and security-patched packages in your own images. For detailed\ninstructions, see [Enterprise\nrepository](../how-to/hardened-packages.md#enterprise-repository).\n\nFor Alpine-based images:\n\n```console\n$ docker dhi auth apk\n```\n\nFor Debian-based images:\n\n```console\n$ docker dhi auth deb\n```\n\n### Monitor customization builds\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nList builds for a customization:\n\n```console\n$ docker dhi customization build list <customization-id> --org my-org\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\nGet details of a specific build:\n\n```console\n$ docker dhi customization build get <customization-id> <build-id> --org my-org\n$ docker dhi customization build get <customization-id> <build-id> --org my-org --json\n```\n\nView build logs:\n\n```console\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org --json\n```\n\n### JSON output\n\nMost list and get commands support a `--json` flag for machine-readable output:\n\n```console\n$ docker dhi catalog list --json\n$ docker dhi catalog get golang --json\n$ docker dhi attestation list dhi/nginx:1.27 --json\n$ docker dhi mirror list --org my-org --json\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --json\n$ docker dhi customization list --org my-org --json\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\n## Configuration\n\nThe `docker dhi` CLI can be configured with a YAML file located at:\n- `$HOME/.config/dhictl/config.yaml` on _Linux_ and _macOS_\n- `%USERPROFILE%\\.config\\dhictl\\config.yaml` on _Windows_\n\nIf `$XDG_CONFIG_HOME` is set, the configuration file is located at `$XDG_CONFIG_HOME/dhictl/config.yaml`.\n\nAvailable configuration options:\n\n| Option      | Environment Variable | Description                                                                                                               |\n|-------------|----------------------|---------------------------------------------------------------------------------------------------------------------------|\n| `org`       | `DHI_ORG`            | Default Docker Hub organization for mirror and customization commands.                                                    |\n| `api_token` | `DHI_API_TOKEN`      | Docker token for authentication. You can generate a token in your [Docker Hub account settings](https://hub.docker.com/). |\n\nEnvironment variables take precedence over configuration file values.\n","content/manuals/dhi/tools/hub.md":"---\ntitle: Use Docker Hub\nlinktitle: Docker Hub\ndescription: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\nweight: 10\nkeywords: docker hub dhi catalog, hardened images hub, dhi repository details, image variants hub\n---\n\nThe [Docker Hardened Images catalog](https://hub.docker.com/hardened-images/catalog)\non Docker Hub is the primary web interface for browsing, searching, and inspecting\nDHI repositories and their metadata.\n\n## Catalog page\n\nThe catalog lists all available DHI repositories. You can filter by name,\nimage type, or compliance requirements (FIPS, STIG) to find the image you need.\n\n## Repository details page\n\nWhen you select a repository from the catalog, the repository details page\nprovides the following:\n\n- Overview: A brief explanation of the image.\n- Guides: Several guides on how to use the image and migrate your existing application.\n- Images: Select this option to [view image variants](#images-page).\n- Security summary: Select a tag name to view a quick security summary,\n  including package count and total known vulnerabilities.\n- Recently pushed tags: A list of recently updated image variants and when they\n  were last updated.\n- Use this image: After selecting an image variant, you can select this option to\n  view instructions on how to pull and use the image variant, or select **Mirror\n  repository** to mirror it to your organization.\n\n## Images page\n\nFrom the repository details page, select **Images** to see all available image\nvariants for that repository. The table includes:\n\n- Image version: The image name with its base distribution (for example, `debian\n  13`) and associated tags.\n- Type: The support lifecycle status of the variant.\n- Compliance: Relevant compliance designations, for example `CIS`, `FIPS`, or\n  `STIG (100%)`.\n- Package manager: Whether a package manager is available. A checkmark indicates\n  a package manager is present (for example, `apt` or `apk`), a dash indicates\n  none.\n- Shell: Whether a shell is available. A checkmark indicates a shell is present\n  (for example, `bash` or `busybox`), a dash indicates none.\n- User: The user that the container runs as, for example `root` or `nonroot\n  (65532)`.\n- Last pushed: When the image variant was last updated.\n- Vulnerabilities: Vulnerability counts by severity level.\n\n## Image variant details page\n\nSelect an image version from the Images table to view detailed information about\nthat specific variant:\n\n- Packages: A list of all packages included in the image variant, with each\n  package's name, version, distribution, and licensing information.\n- Specifications:\n  - Source and build information: The Dockerfile and Git commit used to build the image.\n  - Build parameters, entrypoint, CMD, user, working directory, environment\n    variables, labels, and platform.\n- Vulnerabilities: A list of known CVEs for the image variant, including CVE ID,\n  severity, affected package, fix version, last detected date, status, and\n  suppressed CVEs.\n- Attestations: Signed security attestations covering the image's build process,\n  contents, and security posture. For the full list, see\n  [Attestations](/dhi/explore/security-concepts/attestations/).\n\n## Manage page\n\nThe Manage page (**My Hub** > **Hardened Images** > **Manage**) is the central\nplace for administering your organization's mirrored DHI repositories. It has\ntwo tabs:\n\n- Mirrored Images: Lists all image repositories currently mirrored to your\n  organization, with their source DHI repository, destination repository name,\n  and mirroring status. From here you can stop mirroring or open a repository's\n  settings.\n- Mirrored Helm charts: The same view for Helm chart repositories.\n\nSelecting a mirrored repository opens its settings, where you can enable or\ndisable Extended Lifecycle Support (ELS) and access customizations.\n\nFor step-by-step instructions, see [Mirror a Docker Hardened Image\nrepository](/dhi/how-to/mirror/).\n\n## Customizations\n\nCustomizations are accessible from **My Hub** > **Hardened Images** > **Manage** > **Mirrored Images**.\nSelect the menu icon next to a mirrored repository and\nthen **Customize**. Each customization defines\nadditional packages, OCI artifacts, environment variables, or labels to layer\nonto the base DHI during a rebuild.\n\nThe customizations view shows each customization's name, status, and last build\ntime. Selecting a customization opens its configuration, where you can edit the\ndefinition, trigger a rebuild, or delete it.\n\nFor step-by-step instructions, see [Customize a Docker Hardened\nImage](/dhi/how-to/customize/).\n","content/manuals/dhi/tools/mcp.md":"---\ntitle: Use the DHI MCP server\nlinktitle: MCP server\ndescription: Connect an AI assistant to the Docker Hardened Images catalog using the DHI MCP server to search repositories, inspect images, view SBOMs, and check CVEs.\nweight: 30\nkeywords: docker hardened images mcp, ai assistant dhi, mcp server docker, dhi catalog ai, claude cursor docker images, sbom mcp, cve mcp\naliases:\n  - /dhi/how-to/mcp/\n---\n\nThe Docker Hardened Images (DHI) MCP server exposes the DHI catalog through the\nModel Context Protocol (MCP), letting you query repositories, inspect image\nmetadata, retrieve SBOMs, and check CVEs directly from your AI assistant in\nplain language.\n\nThe MCP server is:\n\n- Remote. No local binary to install. Your AI assistant connects directly to\n  `https://dhi.io/mcp`.\n- Compatible with any MCP-capable AI assistant, including Claude,\n  Cursor, and others.\n\nMost tools are public and require no credentials. The mirror management tools\n(`dhi_list_mirrors`, `dhi_create_mirror`, `dhi_remove_mirror`) require a Docker\nHub username and personal access token (PAT) with owner access to the target\norganization. Credentials are passed as an HTTP Basic auth header in the MCP\nclient configuration — they are never passed as tool arguments.\n\n## Connect your AI assistant\n\nConfiguration varies by client. Select the tab for your AI assistant.\n\n{{< tabs >}}\n{{< tab name=\"Claude Desktop\" >}}\n\nAdd the following to your Claude Desktop configuration file:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\nThe configuration file is located at:\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n{{< /tab >}}\n{{< tab name=\"Cursor\" >}}\n\nAdd the following to `.cursor/mcp.json` in your project, or\n`~/.cursor/mcp.json` globally:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Claude Code\" >}}\n\nRun the following command to add the DHI MCP server:\n\n```console\n$ claude mcp add dhi --url https://dhi.io/mcp\n```\n\nOr add it manually to `.claude/mcp.json` in your project:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Docker Agent\" >}}\n\nIn your [Docker Agent](/manuals/ai/docker-agent/_index.md) YAML configuration, add the\nDHI MCP server as a remote toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://dhi.io/mcp\"\n      transport_type: streamable\n```\n\nFor example, to create an agent that can answer questions about the DHI catalog:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: DHI catalog assistant\n    instruction: |\n      Help me find and evaluate Docker Hardened Images.\n      Search the DHI catalog, inspect image details, check CVEs,\n      and retrieve SBOMs and attestations as needed.\n    toolsets:\n      - type: mcp\n        remote:\n          url: \"https://dhi.io/mcp\"\n          transport_type: streamable\n```\n\nRun the agent with:\n\n```console\n$ docker agent run dhi-agent.yaml\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Available tools\n\nThe DHI MCP server provides ten tools that your AI assistant calls automatically\nbased on what you ask:\n\n| Tool | What it does |\n|------|-------------|\n| `dhi_list_repositories` | Search and filter the DHI catalog by name, type, category, FIPS, or STIG compliance |\n| `dhi_get_repository` | Get full details for a repository: tag definitions, build config, platforms, and per-manifest vulnerability counts |\n| `dhi_get_tag_definition` | Get the deep view of a single tag definition |\n| `dhi_get_image_details` | Get per-digest details: tags, platform, size, layer and package counts, vulnerability severity counts, and attestation types |\n| `dhi_get_image_packages` | Retrieve the full software bill of materials (SBOM): package name, version, type, purl, licenses, and file locations |\n| `dhi_get_image_cves` | List CVEs with severity, CVSS score, fix version, EPSS score, and CISA-exploited flag; filter by minimum severity or fixable-only |\n| `dhi_get_image_attestations` | List SBOM, provenance, signature, and other attestations for a specific image digest |\n| `dhi_list_mirrors` | List mirrored DHI repositories for a Docker Hub organization — requires authentication |\n| `dhi_create_mirror` | Start mirroring a DHI repository into a Docker Hub organization — requires authentication |\n| `dhi_remove_mirror` | Stop mirroring a repository by its mirror ID — requires authentication |\n\n## Authenticate for mirror tools\n\nThe mirror tools require a Docker Hub username and [personal access token\n(PAT)](/security/access-tokens/) with owner access to the target organization,\npassed as an HTTP Basic auth header. Generate the value with:\n\n```console\n$ printf 'USERNAME:dckr_pat_...' | base64 | tr -d '\\n'\n```\n\nThen add it to your MCP client configuration:\n\n> [!WARNING]\n> Base64 encoding is not encryption. The value in your configuration file\n> is effectively a plaintext password. Do not commit this file to version\n> control or share it.\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Basic <base64-value>\"\n      }\n    }\n  }\n}\n```\n\nWithout credentials, the read-only catalog tools work normally and the mirror\ntools return an authentication error.\n\n## What the tools return\n\nEach tool returns structured data that your AI assistant can summarize,\ncompare, or act on:\n\n- `dhi_list_repositories` returns a list of repositories with display\n  name, distributions, platforms, FIPS/STIG flags, included tools, and category.\n- `dhi_get_repository` returns the full repository record, including all tag\n  definitions with their tags, build configuration, image indexes, and\n  per-platform manifest digests with vulnerability counts.\n- `dhi_get_tag_definition` returns tags, build parameters, entrypoint,\n  environment variables, run-as user, and per-platform manifests for a single\n  tag definition.\n- `dhi_get_image_details` returns the image platform, compressed size, layer\n  count, package count, vulnerability severity counts by level, labels, and\n  a list of attestation predicate types.\n- `dhi_get_image_packages` returns each package in the image with its name,\n  version, type (`deb`, `rpm`, `apk`, etc.), purl, licenses, and the file paths where\n  it was found.\n- `dhi_get_image_cves` returns each CVE affecting the image with its\n  severity, CVSS score and vector, affected package, fix version (if any), EPSS\n  probability score, and a flag indicating whether CISA lists it as\n  actively exploited.\n- `dhi_get_image_attestations` returns the predicate type and OCI reference\n  for each attestation attached to the image digest.\n- `dhi_list_mirrors` returns each mirror's ID, source DHI repository,\n  destination repository, and mirroring status for the given organization.\n- `dhi_create_mirror` starts mirroring a DHI source repository into the\n  specified organization and destination repository name.\n- `dhi_remove_mirror` stops mirroring for the given mirror ID. It does not\n  delete the destination repository — only stops new images from being synced.\n","content/manuals/dhi/tools/terraform.md":"---\ntitle: Use the DHI Terraform provider\nlinktitle: Terraform\ndescription: Use the DHI Terraform provider to manage mirrors and customizations as infrastructure as code.\nweight: 40\nkeywords: dhi terraform, docker hardened images terraform, infrastructure as code, dhi mirror terraform, dhi provider\n---\n\nThe [DHI Terraform provider](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs)\nlets you manage Docker Hardened Image mirrors and customizations as\ninfrastructure as code.\n\n## Install and configure the provider\n\nAdd the provider to your Terraform configuration:\n\n```hcl\nterraform {\n  required_providers {\n    dhi = {\n      source = \"docker-hardened-images/dhi\"\n    }\n  }\n}\n\nprovider \"dhi\" {\n  docker_hub_username = var.docker_username\n  docker_hub_password = var.docker_password\n  organization        = var.org_name\n}\n```\n\nInstead of specifying credentials in the provider block, you can set environment\nvariables:\n\n| Variable | Description |\n|----------|-------------|\n| `DOCKER_USERNAME` | Docker Hub username or organization namespace |\n| `DOCKER_PASSWORD` | Docker Hub password or personal/organization access token |\n| `DHI_ORG` | Target organization namespace |\n\nYou can authenticate using a personal access token (PAT) or an organization\naccess token (OAT) in place of a password. When using an OAT, permission scopes\napply:\n\n- Read (pull) access is required to list mirrors.\n- Push access is required to create or delete mirrors.\n\n## Resources\n\n### `dhi_mirror`\n\nManages a mirrored DHI repository in your organization. See [Mirror a Docker\nHardened Image repository](/dhi/how-to/mirror/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/mirror).\n\n### `dhi_customization`\n\nManages image customizations applied to a mirrored repository. See [Customize a\nDocker Hardened Image](/dhi/how-to/customize/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/customization).\n","content/manuals/extensions/_index.md":"---\ntitle: Docker Extensions\nweight: 60\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows\nparams:\n  sidebar:\n    group: Application development\naliases:\n - /desktop/extensions/\n---\n\nDocker Extensions let you use third-party tools within Docker Desktop to extend its functionality.\n\nYou can seamlessly connect your favorite development tools to your application development and deployment workflows. Augment Docker Desktop with debugging, testing, security, and networking functionalities, and create custom add-ons using the Extensions [SDK](extensions-sdk/_index.md).\n\nAnyone can use Docker Extensions and there is no limit to the number of extensions you can install.\n\n![Extensions Marketplace](/assets/images/extensions.webp)\n\n## What extensions are available?\n\nThere is a mix of partner and community-built extensions and Docker-built extensions.\nYou can explore the list of available extensions in [Docker Hub](https://hub.docker.com/search?q=&type=extension) or in the Extensions Marketplace within Docker Desktop.\n\n## Security and trust\n\nDocker Extensions run with elevated privileges on your host machine. They have direct access to the Docker Engine, can read and write files on your filesystem, and can install and run native binaries. \n\nDocker reviews extensions submitted to the Marketplace, but does not guarantee the security of any extension. Extensions installed outside the Marketplace have not been reviewed at all. Only install extensions from publishers you trust. \n\nIf you're an organization admin, see [Configure a private marketplace](private-marketplace.md) to control which extensions your team can install.","content/manuals/extensions/extensions-sdk/_index.md":"---\ntitle: Overview of the Extensions SDK\nlinkTitle: Extensions SDK\ndescription: Overall index for Docker Extensions SDK documentation\nkeywords: Docker, Extensions, sdk\naliases:\n - /desktop/extensions-sdk/dev/overview/\n - /desktop/extensions-sdk/\ngrid:\n  - title: \"The build and publish process\"\n    description: Understand the process for building and publishing an extension.\n    icon: clipboard-document-check\n    link: \"/extensions/extensions-sdk/process/\"\n  - title: \"Quickstart guide\"\n    description: Follow the quickstart guide to create a basic Docker extension quickly.\n    icon: magnifying-glass-plus\n    link: \"/extensions/extensions-sdk/quickstart/\"\n  - title: \"View the design guidelines\"\n    description: Ensure your extension aligns to Docker's design guidelines and principles.\n    icon: paint-brush\n    link: \"/extensions/extensions-sdk/design/design-guidelines/\"\n  - title: \"Publish your extension\"\n    description: Understand how to publish your extension to the Marketplace.\n    icon: arrow-up-tray\n    link: \"/extensions/extensions-sdk/extensions/\"\n  - title: \"Interacting with Kubernetes\"\n    description: Find information on how to interact indirectly with a Kubernetes cluster from your Docker extension.\n    icon: arrows-right-left\n    link: \"/extensions/extensions-sdk/guides/kubernetes/\"\n  - title: \"Multi-arch extensions\"\n    description: Build your extension for multiple architectures.\n    icon: document-duplicate\n    link: \"/extensions/extensions-sdk/extensions/multi-arch/\"\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nThe resources in this section help you create your own Docker extension.\n\nThe Docker CLI tool provides a set of commands to help you build and publish your extension, packaged as a \nspecially formatted Docker image.\n\nAt the root of the image filesystem is a `metadata.json` file which describes the content of the extension. \nIt's a fundamental element of a Docker extension.\n\nAn extension can contain a UI part and backend parts that run either on the host or in the Desktop virtual machine.\nFor further information, see [Architecture](architecture/_index.md).\n\nYou distribute extensions through Docker Hub. However, you can develop them locally without the need to push \nthe extension to Docker Hub. See [Extensions distribution](extensions/DISTRIBUTION.md) for further details.\n\n{{% include \"extensions-form.md\" %}}\n\n{{< grid >}}\n","content/manuals/extensions/extensions-sdk/architecture/_index.md":"---\ntitle: Extension architecture\nlinkTitle: Architecture\ndescription: Docker extension architecture\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/architecture/\nweight: 50\n---\n\nExtensions are applications that run inside the Docker Desktop. They're packaged as Docker images, distributed\nthrough Docker Hub, and installed by users either through the Marketplace within the Docker Desktop Dashboard or the\nDocker Extensions CLI.\n\nExtensions can be composed of three (optional) components:\n- A frontend (or User Interface): A web application displayed in a tab of the dashboard in Docker Desktop\n- A backend: One or many containerized services running in the Docker Desktop VM\n- Executables: Shell scripts or binaries that Docker Desktop copies on the host when installing the extension\n\n![Overview of the three components of an extension](images/extensions-architecture.png?w=600h=400)\n\nAn extension doesn't necessarily need to have all these components, but at least one of them depending on the extension features. \nTo configure and run those components, Docker Desktop uses a `metadata.json` file. See the\n[metadata](metadata) section for more details.\n\n## The frontend\n\nThe frontend is basically a web application made from HTML, Javascript, and CSS. It can be built with a simple HTML\nfile, some vanilla Javascript or any frontend framework, such as React or Vue.js.\n\nWhen Docker Desktop installs the extension, it extracts the UI folder from the extension image, as defined by the \n`ui` section in the `metadata.json`. See the [ui metadata section](metadata.md#ui-section) for more details.\n\nEvery time users click on the **Extensions** tab, Docker Desktop initializes the extension's UI as if it was the first time. When they navigate away from the tab, both the UI itself and all the sub-processes started by it (if any) are terminated.\n\nThe frontend can invoke `docker` commands, communicate with the extension backend, or invoke extension executables\ndeployed on the host, through the [Extensions SDK](https://www.npmjs.com/package/@docker/extension-api-client).\n\n> [!TIP]\n>\n> The `docker extension init` generates a React based extension. But you can still use it as a starting point for\n> your own extension and use any other frontend framework, like Vue, Angular, Svelte, etc. or event stay with\n> vanilla Javascript.\n\nLearn more about [building a frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) for your extension.\n\n## The backend\n\nAlongside a frontend application, extensions can also contain one or many backend services. In most cases, the Extension does not need a backend, and features can be implemented just by invoking docker commands through the SDK. However, there are some cases when an extension requires a backend\n\tservice, for example:\n- To run long-running processes that must outlive the frontend\n- To store data in a local database and serve them back with a REST API\n- To store the extension state, like when a button starts a long-running process, so that if you navigate away\n  from the extension and come back, the frontend can pick up where it left off\n- To access specific resources in the Docker Desktop VM, for example by mounting folders in the compose\nfile\n\n> [!TIP]\n>\n> The `docker extension init` generates a Go backend. But you can still use it as a starting point for\n> your own extension and use any other language like Node.js, Python, Java, .Net, or any other language and framework.\n\nUsually, the backend is made of one container that runs within the Docker Desktop VM. Internally, Docker Desktop creates\na Docker Compose project, creates the container from the `image` option of the `vm` section of the `metadata.json`, and\nattaches it to the Compose project. See the [`vm` metadata section](metadata.md#vm-section) for more details.\n\nIn some cases, a `compose.yaml` file can be used instead of an `image`. This is useful when the backend container\nneeds more specific options, such as mounting volumes or requesting [capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)\nthat can't be expressed just with a Docker image. The `compose.yaml` file can also be used to add multiple containers\nneeded by the extension, like a database or a message broker. \nNote that, if the Compose file defines many services, the SDK can only contact the first of them.\n\n> [!NOTE]\n>\n> In some cases, it is useful to also interact with the Docker engine from the backend.\n> See [How to use the Docker socket](../guides/use-docker-socket-from-backend.md) from the backend.\n\nTo communicate with the backend, the Extension SDK provides [functions](../dev/api/backend.md#get) to make `GET`,\n`POST`, `PUT`, `HEAD`, and `DELETE` requests from the frontend. Under the hood, the communication is done through a socket\nor named pipe, depending on the operating system. If the backend was listening to a port, it would be difficult to\nprevent collision with other applications running on the host or in a container already. Also, some users are\nrunning Docker Desktop in constrained environments where they can't open ports on their machines.\n\n![Backend and frontend communication](images/extensions-arch-2.png?w=500h=300)\n\nFinally, the backend can be built with any technology, as long as it can run in a container and listen on a socket.\n\nLearn more about [adding a backend](/manuals/extensions/extensions-sdk/build/backend-extension-tutorial.md) to your extension.\n\n## Executables\n\nIn addition to the frontend and the backend, extensions can also contain executables. Executables are binaries or shell scripts\nthat are installed on the host when the extension is installed. The frontend can invoke them with [the extension SDK](../dev/api/backend.md#invoke-an-extension-binary-on-the-host).\n\nThese executables are useful when the extension needs to interact with a third-party CLI tool, like AWS, `kubectl`, etc.\nShipping those executables with the extension ensure that the CLI tool is always available, at the right version, on\nthe users' machine.\n\nWhen Docker Desktop installs the extension, it copies the executables on the host as defined by the `host` section in\nthe `metadata.json`. See the [`host` metadata section](metadata.md#host-section) for more details.\n\n![Executable and frontend communication](images/extensions-arch-3.png?w=250h=300)\n\nHowever, since they're executed on the users' machine, they have to be available to the platform they're running on.\nFor example, if you want to ship the `kubectl` executable, you need to provide a different version for Windows, Mac,\nand Linux. Multi arch images will also need to include binaries built for the right arch (AMD / ARM)\n\n\nSee the [host metadata section](metadata.md#host-section) for more details.\n\nLearn how to [invoke host binaries](../guides/invoke-host-binaries.md).\n","content/manuals/extensions/extensions-sdk/architecture/metadata.md":"---\ntitle: Extension metadata\nlinkTitle: Metadata\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases:\n - /desktop/extensions-sdk/extensions/METADATA\n - /desktop/extensions-sdk/architecture/metadata/\n---\n\n## The metadata.json file\n\nThe `metadata.json` file is the entry point for your extension. It contains the metadata for your extension, such as the\nname, version, and description. It also contains the information needed to build and run your extension. The image for\na Docker extension must include a `metadata.json` file at the root of its filesystem.\n\nThe format of the `metadata.json` file must be:\n\n```json\n{\n    \"icon\": \"extension-icon.svg\",\n    \"ui\": ...\n    \"vm\": ...\n    \"host\": ...\n}\n```\n\nThe `ui`, `vm`, and `host` sections are optional and depend on what a given extension provides. They describe the extension content to be installed.\n\n### UI section\n\nThe `ui` section defines a new tab that's added to the dashboard in Docker Desktop. It follows the form:\n\n```json\n\"ui\":{\n    \"dashboard-tab\":\n    {\n        \"title\":\"MyTitle\",\n        \"root\":\"/ui\",\n        \"src\":\"index.html\"\n    }\n}\n```\n\n`root` specifies the folder where the UI code is within the extension image filesystem.\n`src` specifies the entrypoint that should be loaded in the extension tab.\n\nOther UI extension points will be available in the future.\n\n### VM section\n\nThe `vm` section defines a backend service that runs inside the Desktop VM. It must define either an `image` or a\n`compose.yaml` file that specifies what service to run in the Desktop VM.\n\n```json\n\"vm\": {\n    \"image\":\"${DESKTOP_PLUGIN_IMAGE}\"\n},\n```\n\nWhen you use `image`, a default compose file is generated for the extension.\n\n> `${DESKTOP_PLUGIN_IMAGE}` is a specific keyword that allows an easy way to refer to the image packaging the extension.\n> It is also possible to specify any other full image name here. However, in many cases using the same image makes\n> things easier for extension development.\n\n```json\n\"vm\": {\n    \"composefile\": \"compose.yaml\"\n},\n```\n\nThe Compose file, with a volume definition for example, would look like:\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /host/path:/container/path\n```\n\n### Host section\n\nThe `host` section defines executables that Docker Desktop copies on the host.\n\n```json\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/myBinary\"\n          },\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/myBinary.exe\"\n          },\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/myBinary\"\n          },\n        ]\n      }\n    ]\n  }\n```\n\n`binaries` defines a list of binaries Docker Desktop copies from the extension image to the host.\n\n`path` specifies the binary path in the image filesystem. Docker Desktop is responsible for copying these files in its own location, and the JavaScript API allows invokes these binaries.\n\nLearn how to [invoke executables](../guides/invoke-host-binaries.md).\n","content/manuals/extensions/extensions-sdk/architecture/security.md":"---\ntitle: Extension security\nlinkTitle: Security\ndescription: Aspects of the security model of extensions\nkeywords: Docker, extensions, sdk, security\naliases:\n - /desktop/extensions-sdk/guides/security/\n - /desktop/extensions-sdk/architecture/security/\n---\n\n## Extension capabilities\n\nAn extension can have the following optional parts: \n* A user interface in HTML or JavaScript, displayed in Docker Desktop Dashboard\n* A backend part that runs as a container\n* Executables deployed on the host machine.\n\nExtensions are executed with the same permissions as the Docker Desktop user. Extension capabilities include running any Docker commands (including running containers and mounting folders), running extension binaries, and accessing files on your machine that are accessible by the user running Docker Desktop.\nNote that extensions are not restricted to execute binaries that they list in the [host section](../architecture/metadata.md#host-section) of the extension metadata: since these binaries can contain any code running as user, they can in turn execute any other commands as long as the user has rights to execute them.\n\nThe Extensions SDK provides a set of JavaScript APIs to invoke commands or invoke these binaries from the extension UI code. Extensions can also provide a backend part that starts a long-lived running container in the background.\n\n> [!IMPORTANT]\n>\n> Make sure you trust the publisher or author of the extension when you install it, as the extension has the same access rights as the user running Docker Desktop.\n","content/manuals/extensions/extensions-sdk/design/_index.md":"---\ntitle: UI styling overview for Docker extensions\nlinkTitle: Design and UI styling\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases:\n - /desktop/extensions-sdk/design/design-overview/\n - /desktop/extensions-sdk/design/overview/\n - /desktop/extensions-sdk/design/\nweight: 60\n---\n\nOur Design System is a constantly evolving set of specifications that aim to ensure visual consistency across Docker products, and meet [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance). We've opened parts of it to extension authors, documenting basic styles (color, typography) and components. See: [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771).\n\nWe require extensions to match the wider Docker Desktop UI to a certain degree, and reserve the right to make this stricter in the future.\n\nTo get started on your UI, follow the steps below.\n\n## Step one: Choose your framework\n\n### Recommended: React+MUI, using our theme\n\nDocker Desktop's UI is written in React and [MUI](https://mui.com/) (using Material UI specifically). This is the only officially supported framework for building extensions, and the one that the `init` command automatically configures for you. Using it brings significant benefits to authors:\n\n- You can use our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme) to automatically replicate Docker Desktop's look and feel.\n- In future, we'll release utilities and components specifically targeting this combination (e.g. custom MUI components, or React hooks for interacting with Docker).\n\nRead our [MUI best practices](mui-best-practices.md) guide to learn future-proof ways to use MUI with Docker Desktop.\n\n### Not recommended: Some other framework\n\nYou may prefer to use another framework, perhaps because you or your team are more familiar with it or because you have existing assets you want to reuse. This is possible, but highly discouraged. It means that:\n\n- You'll need to manually replicate the look and feel of Docker Desktop. This takes a lot of effort, and if you don't match our theme closely enough, users will find your extension jarring and we may ask you to make changes during a review process.\n- You'll have a higher maintenance burden. Whenever Docker Desktop's theme changes (which could happen in any release), you'll need to manually change your extension to match it.\n- If your extension is open-source, deliberately avoiding common conventions will make it harder for the community to contribute to it.\n\n## Step two: Follow the below recommendations\n\n### Follow our MUI best practices (if applicable)\n\nSee our [MUI best practices](mui-best-practices.md) article.\n\n### Only use colors from our palette\n\nWith minor exceptions, displaying your logo for example, you should only use colors from our palette. These can be found in our [style guide document](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771), and will also soon be available in our MUI theme and via CSS variables.\n\n### Use counterpart colors in light/dark mode\n\nOur colors have been chosen so that the counterpart colors in each variant of the palette should have the same essential characteristics. Anywhere you use `red-300` in light mode, you should use `red-300` in dark mode too.\n\n## What's next?\n\n- Take a look at our [MUI best practices](mui-best-practices.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/design-guidelines.md":"---\ntitle: Design guidelines for Docker extensions\nlinkTitle: Guidelines\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-guidelines/\nweight: 10\n---\n\nAt Docker, we aim to build tools that integrate into a user's existing workflows rather than requiring them to adopt new ones. We strongly recommend that you follow these guidelines when creating extensions. We review and approve your Marketplace publication based on these requirements.\n\nHere is a simple checklist to go through when creating your extension:\n- Is it easy to get started?\n- Is it easy to use?\n- Is it easy to get help when needed?\n\n\n## Create a consistent experience with Docker Desktop\n\nUse the [Docker Material UI Theme](https://www.npmjs.com/package/@docker/docker-mui-theme) and the [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771) to ensure that your extension feels like it is part of Docker Desktop to create a seamless experience for users.\n\n- Ensure the extension has both a light and dark theme. Using the components and styles as per the Docker style guide ensures that your extension meets the [level AA accessibility standard.](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n  ![Light and dark mode](images/light_dark_mode.webp)\n\n- Ensure that your extension icon is visible both in light and dark mode.\n\n  ![Icon colors in light and dark mode](images/icon_colors.webp)\n\n- Ensure that the navigational behavior is consistent with the rest of Docker Desktop. Add a header to set the context for the extension.\n\n  ![Header that sets the context](images/header.webp)\n\n- Avoid embedding terminal windows. The advantage we have with Docker Desktop over the CLI is that we have the opportunity to provide rich information to users. Make use of this interface as much as possible. \n\n  ![Terminal window used incorrectly](images/terminal_window_dont.webp)\n\n  ![Terminal window used correctly](images/terminal_window_do.webp)\n\n## Build features natively\n\n- In order not to disrupt the flow of users, avoid scenarios where the user has to navigate outside Docker Desktop, to the CLI or a webpage for example, in order to carry out certain functionalities. Instead, build features that are native to Docker Desktop.\n\n  ![Incorrect way to switch context](images/switch_context_dont.webp)\n\n  ![Correct way to switch context](images/switch_context_do.webp)\n\n## Break down complicated user flows\n\n- If a flow is too complicated or the concept is abstract, break down the flow into multiple steps with one simple call-to-action in each step. This helps when onboarding novice users to your extension\n\n  ![A complicated flow](images/complicated_flows.webp)\n\n- Where there are multiple call-to-actions, ensure you use the primary (filled button style) and secondary buttons (outline button style) to convey the importance of each action.\n\n  ![Call to action](images/cta.webp)\n\n## Onboarding new users\n\nWhen creating your extension, ensure that first time users of the extension and your product can understand its value-add and adopt it easily. Ensure you include contextual help within the extension.\n\n- Ensure that all necessary information is added to the extensions Marketplace as well as the extensions detail page. This should include:\n  - Screenshots of the extension. Note that the recommended size for screenshots is 2400x1600 pixels. \n  - A detailed description that covers what the purpose of the extension is, who would find it useful and how it works.\n  - Link to necessary resources such as documentation.\n- If your extension has particularly complex functionality, add a demo or video to the start page. This helps onboard a first time user quickly.\n\n  ![start page](images/start_page.webp)\n\n## What's next?\n\n- Explore our [design principles](design-principles.md).\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/design-principles.md":"---\ntitle: Docker design principles\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-principles/\nweight: 20\n---\n\n## Provide actionable guidance\n\nWe anticipate needs and provide simple explanations with clear actions so people are never lost and always know what to do next. Recommendations lead users to functionality that enhances the experience and extends their knowledge.\n\n## Create value through confidence\n\nPeople from all levels of experience should feel they know how to use our product. Experiences are familiar, unified, and easy to use so all users feel like experts.\n\n## Infuse productivity with delight\n\nWe seek out moments of purposeful delight that elevate rather than distract, making work easier and more gratifying. Simple tasks are automated and users are left with more time for innovation.\n\n## Build trust through transparency\n\nWe always provide clarity on what is happening and why. No amount of detail is withheld; the right information is shown at the right time and is always accessible.\n\n## Scale with intention\n\nOur products focus on inclusive growth and are continuously useful and adapt to match changing individual needs. We support all levels of expertise by meeting users where they are with conscious personalization.\n\n## What's next?\n\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/design/mui-best-practices.md":"---\ntitle: MUI best practices\ndescription: Guidelines for using MUI to maximize compatibility with Docker Desktop\nkeywords: Docker, extensions, mui, theme, theming, material-ui, material\naliases: \n - /desktop/extensions-sdk/design/mui-best-practices/\n---\n\nThis article assumes you're following our recommended practice by using our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme).\nFollowing the steps below maximizes compatibility with Docker Desktop and minimizes the work you need to do as an\nextension author. They should be considered supplementary to the non-MUI-specific guidelines found in the\n[UI Styling overview](index.md).\n\n## Assume the theme can change at any time\n\nResist the temptation to fine-tune your UI with precise colors, offsets and font sizings to make it look as attractive as possible. Any specializations you make today will be relative to the current MUI theme, and may look worse when the theme changes. Any part of the theme might change without warning, including (but not limited to):\n\n-  The font, or font sizes\n-  Border thicknesses or styles\n-  Colors:\n   -  Our palette members (e.g. `red-100`) could change their RGB values\n   -  The semantic colors (e.g. `error`, `primary`, `textPrimary`, etc) could be changed to use a different member of our palette\n   -  Background colors (e.g. those of the page, or of dialogs) could change\n-  Spacings:\n   -  The size of the basic unit of spacing,(exposed via `theme.spacing`. For instance, we may allow users to customize the density of the UI\n   -  The default spacing between paragraphs or grid items\n\nThe best way to build your UI, so that it’s robust against future theming changes, is to:\n\n-  Override the default styling as little as possible.\n-  Use semantic typography. e.g. use `Typography`s or `Link`s with appropriate `variant`s instead of using typographical HTML elements (`<a>`, `<p>`, `<h1>`, etc) directly.\n-  Use canned sizes. e.g. use `size=\"small\"` on buttons, or `fontSize=\"small\"` on icons, instead of specifying sizes in pixels.\n-  Prefer semantic colors. e.g. use `error` or `primary` over explicit color codes.\n-  Write as little CSS as possible. Write semantic markup instead. For example, if you want to space out paragraphs of text, use the `paragraph` prop on your `Typography` instances. If you want to space out something else, use a `Stack` or `Grid` with the default spacing.\n-  Use visual idioms you’ve seen in the Docker Desktop UI, since these are the main ones we’ll test any theme changes against.\n\n## When you go custom, centralize it\n\nSometimes you’ll need a piece of UI that doesn’t exist in our design system. If so, we recommend that you first reach out to us. We may already have something in our internal design system, or we may be able to expand our design system to accommodate your use case.\n\nIf you still decide to build it yourself after contacting us, try and define the new UI in a reusable fashion. If you define your custom UI in just one place, it’ll make it easier to change in the future if our core theme changes. You could use:\n\n-  A new `variant` of an existing component - see [MUI docs](https://mui.com/material-ui/customization/theme-components/#creating-new-component-variants)\n-  A MUI mixin (a freeform bundle of reusable styling rules defined inside a theme)\n-  A new [reusable component](https://mui.com/material-ui/customization/how-to-customize/#2-reusable-component)\n\nSome of the above options require you to extend our MUI theme. See the MUI documentation on [theme composition](https://mui.com/material-ui/customization/theming/#nesting-the-theme).\n\n## What's next?\n\n- Take a look at our [UI styling guide](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/_index.md":"---\nbuild:\n  render: never\ntitle: Developer SDK tools\n---\n","content/manuals/extensions/extensions-sdk/dev/api/_index.md":"---\nbuild:\n  render: never\ntitle: Extension APIs\n---\n","content/manuals/extensions/extensions-sdk/dev/api/backend.md":"---\ntitle: Extension Backend\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/backend/\n---\n\nThe `ddClient.extension.vm` object can be used to communicate with the backend defined in the [vm section](../../architecture/metadata.md#vm-section) of the extension metadata.\n\n## get\n\n▸ **get**(`url`): `Promise`<`unknown`\\>\n\nPerforms an HTTP GET request to a backend service.\n\n```typescript\nddClient.extension.vm.service\n .get(\"/some/service\")\n .then((value: any) => console.log(value)\n```\n\nSee [Service API Reference](/reference/api/extensions-sdk/HttpService.md) for other HTTP methods.\n\n> Deprecated extension backend communication\n>\n> The methods below that use `window.ddClient.backend` are deprecated and will be removed in a future version. Use the methods specified above.\n\nThe `window.ddClient.backend` object can be used to communicate with the backend\ndefined in the [vm section](../../architecture/metadata.md#vm-section) of the\nextension metadata. The client is already connected to the backend.\n\nExample usages:\n\n```typescript\nwindow.ddClient.backend\n  .get(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .post(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .put(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .patch(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .delete(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .head(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .request({ url: \"/url\", method: \"GET\", headers: { 'header-key': 'header-value' }, data: { ... }})\n  .then((value: any) => console.log(value));\n```\n\n## Run a command in the extension backend container\n\nFor example, execute the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"]);\n```\n\nStream the output of the command executed in the backend container. For example, spawn the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nFor more details, refer to the [Extension VM API Reference](/reference/api/extensions-sdk/ExtensionVM.md)\n\n> Deprecated extension backend command execution\n>\n> This method is deprecated and will be removed in a future version. Use the specified method above.\n\nIf your extension ships with additional binaries that should be run inside the\nbackend container, you can use the `execInVMExtension` function:\n\n```typescript\nconst output = await window.ddClient.backend.execInVMExtension(\n  `cliShippedInTheVm xxx`\n);\nconsole.log(output);\n```\n\n## Invoke an extension binary on the host\n\nInvoke a binary on the host. The binary is typically shipped with your extension using the [host section](../../architecture/metadata.md#host-section) in the extension metadata. Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nFor example, execute the shipped binary `kubectl -h` command in the host:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"]);\n```\n\nAs long as the `kubectl` binary is shipped as part of your extension, you can spawn the `kubectl -h` command in the host and get the output stream:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"], {\n  stream: {\n    onOutput(data: { stdout: string } | { stderr: string }): void {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error: any): void {\n      console.error(error);\n    },\n    onClose(exitCode: number): void {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nYou can stream the output of the command executed in the backend container or in the host.\n\nFor more details, refer to the [Extension Host API Reference](/reference/api/extensions-sdk/ExtensionHost.md)\n\n> Deprecated invocation of extension binary\n>\n> This method is deprecated and will be removed in a future version. Use the method specified above.\n\nTo execute a command in the host:\n\n```typescript\nwindow.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((cmdResult: any) => {\n  console.log(cmdResult);\n});\n```\n\nTo stream the output of the command executed in the backend container or in the host:\n\n```typescript\nwindow.ddClient.spawnHostCmd(\n  `cliShippedOnHost`,\n  [`arg1`, `arg2`],\n  (data: any, err: any) => {\n    console.log(data.stdout, data.stderr);\n    // Once the command exits we get the status code\n    if (data.code) {\n      console.log(data.code);\n    }\n  }\n);\n```\n\n> [!NOTE]\n> \n>You cannot use this to chain commands in a single `exec()` invocation (like `cmd1 $(cmd2)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n","content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md":"---\ntitle: Navigation\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard-routes-navigation/\n---\n\n`ddClient.desktopUI.navigate` enables navigation to specific screens of Docker Desktop such as the containers tab, the images tab, or a specific container's logs.\n\nFor example, navigate to a given container logs:\n\n```typescript\nconst id = '8c7881e6a107';\ntry {\n  await ddClient.desktopUI.navigate.viewContainerLogs(id);\n} catch (e) {\n  console.error(e);\n  ddClient.desktopUI.toast.error(\n    `Failed to navigate to logs for container \"${id}\".`\n  );\n}\n```\n\n#### Parameters\n\n| Name | Type     | Description                                                                                                                                                                                            |\n| :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id` | `string` | The full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`. You can use the `--no-trunc` flag as part of the `docker ps` command to display the full container id. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\nA promise that fails if the container doesn't exist.\n\nFor more details about all navigation methods, see the [Navigation API reference](/reference/api/extensions-sdk/NavigationIntents.md).\n\n> Deprecated navigation methods\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.navigateToContainers();\n// id - the full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`\nwindow.ddClient.navigateToContainer(id);\nwindow.ddClient.navigateToContainerLogs(id);\nwindow.ddClient.navigateToContainerInspect(id);\nwindow.ddClient.navigateToContainerStats(id);\n\nwindow.ddClient.navigateToImages();\nwindow.ddClient.navigateToImage(id, tag);\n\nwindow.ddClient.navigateToVolumes();\nwindow.ddClient.navigateToVolume(volume);\n\nwindow.ddClient.navigateToDevEnvironments();\n```\n","content/manuals/extensions/extensions-sdk/dev/api/dashboard.md":"---\ntitle: Dashboard\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard/\n---\n\n## User notifications\n\nToasts provide a brief notification to the user. They appear temporarily and\nshouldn't interrupt the user experience. They also don't require user input to disappear.\n\n### success\n\n▸ **success**(`msg`): `void`\n\nUse to display a toast message of type success.\n\n```typescript\nddClient.desktopUI.toast.success(\"message\");\n```\n\n### warning\n\n▸ **warning**(`msg`): `void`\n\nUse to display a toast message of type warning.\n\n```typescript\nddClient.desktopUI.toast.warning(\"message\");\n```\n\n### error\n\n▸ **error**(`msg`): `void`\n\nUse to display a toast message of type error.\n\n```typescript\nddClient.desktopUI.toast.error(\"message\");\n```\n\nFor more details about method parameters and the return types available, see [Toast API reference](/reference/api/extensions-sdk/Toast.md).\n\n> Deprecated user notifications\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.toastSuccess(\"message\");\nwindow.ddClient.toastWarning(\"message\");\nwindow.ddClient.toastError(\"message\");\n```\n\n## Open a file selection dialog\n\nThis function opens a file selector dialog that asks the user to select a file or folder.\n\n▸ **showOpenDialog**(`dialogProperties`): `Promise`<[`OpenDialogResult`](/reference/api/extensions-sdk/OpenDialogResult.md)\\>:\n\nThe `dialogProperties` parameter is a list of flags passed to Electron to customize the dialog's behaviour. For example, you can pass `multiSelections` to allow a user to select multiple files. See [Electron's documentation](https://www.electronjs.org/docs/latest/api/dialog) for a full list.\n\n```typescript\nconst result = await ddClient.desktopUI.dialog.showOpenDialog({\n  properties: [\"openDirectory\"],\n});\nif (!result.canceled) {\n  console.log(result.paths);\n}\n```\n\n## Open a URL\n\nThis function opens an external URL with the system default browser.\n\n▸ **openExternal**(`url`): `void`\n\n```typescript\nddClient.host.openExternal(\"https://docker.com\");\n```\n\n> The URL must have the protocol `http` or `https`.\n\nFor more details about method parameters and the return types available, see [Desktop host API reference](/reference/api/extensions-sdk/Host.md).\n\n> Deprecated external URL opening\n>\n> This method is deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.openExternal(\"https://docker.com\");\n```\n\n## Navigation to Dashboard routes\n\nFrom your extension, you can also [navigate](dashboard-routes-navigation.md) to other parts of the Docker Desktop Dashboard.\n","content/manuals/extensions/extensions-sdk/dev/api/docker.md":"---\ntitle: Docker\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/docker/\n---\n\n## Docker objects\n\n▸ **listContainers**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of containers:\n\n```typescript\nconst containers = await ddClient.docker.listContainers();\n```\n\n▸ **listImages**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of local container images:\n\n```typescript\nconst images = await ddClient.docker.listImages();\n```\n\nSee the [Docker API reference](/reference/api/extensions-sdk/Docker.md) for details about these methods.\n\n> Deprecated access to Docker objects\n>\n> The methods below are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nconst containers = await window.ddClient.listContainers();\n\nconst images = await window.ddClient.listImages();\n```\n\n## Docker commands\n\nExtensions can also directly execute the `docker` command line.\n\n▸ **exec**(`cmd`, `args`): `Promise`<[`ExecResult`](/reference/api/extensions-sdk/ExecResult.md)\\>\n\n```typescript\nconst result = await ddClient.docker.cli.exec(\"info\", [\n  \"--format\",\n  '\"{{ json . }}\"',\n]);\n```\n\nThe result contains both the standard output and the standard error of the executed command:\n\n```json\n{\n  \"stderr\": \"...\",\n  \"stdout\": \"...\"\n}\n```\n\nIn this example, the command output is JSON.\nFor convenience, the command result object also has methods to easily parse it:\n\n- `result.lines(): string[]` splits output lines.\n- `result.parseJsonObject(): any` parses a well-formed json output.\n- `result.parseJsonLines(): any[]` parses each output line as a json object.\n\n▸ **exec**(`cmd`, `args`, `options`): `void`\n\nThe command above streams the output as a result of the execution of a Docker command.\nThis is useful if you need to get the output as a stream or the output of the command is too long.\n\n```typescript\nawait ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n    splitOutputLines: true,\n  },\n});\n```\n\nThe child process created by the extension is killed (`SIGTERM`) automatically when you close the dashboard in Docker Desktop or when you exit the extension UI.\nIf needed, you can also use the result of the `exec(streamOptions)` call in order to kill (`SIGTERM`) the process.\n\n```typescript\nconst logListener = await ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    // ...\n  },\n});\n\n// when done listening to logs or before starting a new one, kill the process\nlogListener.close();\n```\n\nThis `exec(streamOptions)` API can also be used to listen to docker events:\n\n```typescript\nawait ddClient.docker.cli.exec(\n  \"events\",\n  [\"--format\", \"{{ json . }}\", \"--filter\", \"container=my-container\"],\n  {\n    stream: {\n      onOutput(data) {\n        if (data.stdout) {\n          const event = JSON.parse(data.stdout);\n          console.log(event);\n        } else {\n          console.log(data.stderr);\n        }\n      },\n      onClose(exitCode) {\n        console.log(\"onClose with exit code \" + exitCode);\n      },\n      splitOutputLines: true,\n    },\n  }\n);\n```\n\n> [!NOTE]\n>\n>You cannot use this to chain commands in a single `exec()` invocation (like `docker kill $(docker ps -q)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n\nSee the [Exec API reference](/reference/api/extensions-sdk/Exec.md) for details about these methods.\n\n> Deprecated execution of Docker commands\n>\n> This method is deprecated and will be removed in a future version. Use the one specified just below.\n\n```typescript\nconst output = await window.ddClient.execDockerCmd(\n  \"info\",\n  \"--format\",\n  '\"{{ json . }}\"'\n);\n\nwindow.ddClient.spawnDockerCmd(\"logs\", [\"-f\", \"...\"], (data, error) => {\n  console.log(data.stdout);\n});\n```\n","content/manuals/extensions/extensions-sdk/dev/api/overview.md":"---\ntitle: Extension UI API\ndescription: Docker extension development overview\nkeywords: Docker, extensions, sdk, development\naliases:\n - /desktop/extensions-sdk/dev/api/overview/\n---\n\nThe extensions UI runs in a sandboxed environment and doesn't have access to any\nelectron or nodejs APIs.\n\nThe extension UI API provides a way for the frontend to perform different actions\nand communicate with the Docker Desktop dashboard or the underlying system.\n\nJavaScript API libraries, with Typescript support, are available in order to get all the API definitions in to your extension code.\n\n- [@docker/extension-api-client](https://www.npmjs.com/package/@docker/extension-api-client) gives access to the extension API entrypoint `DockerDesktopClient`.\n- [@docker/extension-api-client-types](https://www.npmjs.com/package/@docker/extension-api-client-types) can be added as a dev dependency in order to get types auto-completion in your IDE.\n\n```Typescript\nimport { createDockerDesktopClient } from '@docker/extension-api-client';\n\nexport function App() {\n  // obtain Docker Desktop client\n  const ddClient = createDockerDesktopClient();\n  // use ddClient to perform extension actions\n}\n```\n\nThe `ddClient` object gives access to various APIs:\n\n- [Extension Backend](backend.md)\n- [Docker](docker.md)\n- [Dashboard](dashboard.md)\n- [Navigation](dashboard-routes-navigation.md)\n\nSee also the [Extensions API reference](/reference/api/extensions-sdk/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/continuous-integration.md":"---\ntitle: Continuous Integration (CI)\ndescription: Automatically test and validate your extension.\nkeywords: Docker, Extensions, sdk, CI, test, regression\naliases: \n - /desktop/extensions-sdk/dev/continuous-integration/\nweight: 20\n---\n\nIn order to help validate your extension and ensure it's functional, the Extension SDK provides tools to help you setup continuous integration for your extension.\n\n> [!IMPORTANT]\n>\n> The [Docker Desktop Action](https://github.com/docker/desktop-action) and the [extension-test-helper library](https://www.npmjs.com/package/@docker/extension-test-helper) are both [experimental](https://docs.docker.com/release-lifecycle/#experimental).\n\n## Setup CI environment with GitHub Actions\n\nYou need Docker Desktop to be able to install and validate your extension.\nYou can start Docker Desktop in GitHub Actions using the [Docker Desktop Action](https://github.com/docker/desktop-action), by adding the following to a workflow file:\n\n```yaml\nsteps:\n  - id: start_desktop\n    uses: docker/desktop-action/start@v0.1.0\n```\n\n> [!NOTE]\n>\n> This action supports only GitHub Actions macOS runners at the moment. You need to specify `runs-on: macOS-latest` for your end to end tests.\n\nOnce the step has executed, the next steps use Docker Desktop and the Docker CLI to install and test the extension.\n\n## Validating your extension with Puppeteer\n\nOnce Docker Desktop starts in CI, you can build, install, and validate your extension with Jest and Puppeteer.\n\nFirst, build and install the extension from your test:\n\n```ts\nimport { DesktopUI } from \"@docker/extension-test-helper\";\nimport { exec as originalExec } from \"child_process\";\nimport * as util from \"util\";\n\nexport const exec = util.promisify(originalExec);\n\n// keep a handle on the app to stop it at the end of tests\nlet dashboard: DesktopUI;\n\nbeforeAll(async () => {\n  await exec(`docker build -t my/extension:latest .`, {\n    cwd: \"my-extension-src-root\",\n  });\n\n  await exec(`docker extension install -f my/extension:latest`);\n});\n```\n\nThen open the Docker Desktop Dashboard and run some tests in your extension's UI:\n\n```ts\ndescribe(\"Test my extension\", () => {\n  test(\"should be functional\", async () => {\n    dashboard = await DesktopUI.start();\n\n    const eFrame = await dashboard.navigateToExtension(\"my/extension\");\n\n    // use puppeteer APIs to manipulate the UI, click on buttons, expect visual display and validate your extension\n    await eFrame.waitForSelector(\"#someElementId\");\n  });\n});\n```\n\nFinally, close the Docker Desktop Dashboard and uninstall your extension:\n\n```ts\nafterAll(async () => {\n  dashboard?.stop();\n  await exec(`docker extension uninstall my/extension`);\n});\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","content/manuals/extensions/extensions-sdk/dev/test-debug.md":"---\ntitle: Test and debug\ndescription: Test and debug your extension.\nkeywords: Docker, Extensions, sdk, preview, update, Chrome DevTools\naliases:\n - /desktop/extensions-sdk/build/test-debug/\n - /desktop/extensions-sdk/dev/test-debug/\nweight: 10\n---\n\nIn order to improve the developer experience, Docker Desktop provides a set of tools to help you test and debug your extension.\n\n### Open Chrome DevTools\n\nIn order to open the Chrome DevTools for your extension when you select the **Extensions** tab, run:\n\n```console\n$ docker extension dev debug <name-of-your-extensions>\n```\n\nEach subsequent click on the extension tab also opens Chrome DevTools. To stop this behaviour, run:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\nAfter an extension is deployed, it is also possible to open Chrome DevTools from the UI extension part using a variation of the [Konami Code](https://en.wikipedia.org/wiki/Konami_Code). Select the **Extensions** tab, and then hit the key sequence `up, up, down, down, left, right, left, right, p, d, t`.\n\n### Hot reloading whilst developing the UI\n\nDuring UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\nextension. To do this, you can configure Docker Desktop to load your UI from a development server, such as the one\n[Vite](https://vitejs.dev/) starts when invoked with `npm start`.\n\nAssuming your app runs on the default port, start your UI app and then run:\n\n```console\n$ cd ui\n$ npm run dev\n```\n\nThis starts a development server that listens on port 3000.\n\nYou can now tell Docker Desktop to use this as the frontend source. In another terminal run:\n\n```console\n$ docker extension dev ui-source <name-of-your-extensions> http://localhost:3000\n```\n\nClose and reopen the Docker Desktop dashboard and go to your extension. All the changes to the frontend code are immediately visible.\n\nOnce finished, you can reset the extension configuration to the original settings. This will also reset opening Chrome DevTools if you used `docker extension dev debug <name-of-your-extensions>`:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\n## Show the extension containers\n\nIf your extension is composed of one or more services running as containers in the Docker Desktop VM, you can access them easily from the dashboard in Docker Desktop.\n\n1. In Docker Desktop, navigate to **Settings**.\n2. Under the **Extensions** tab, select the **Show Docker Desktop Extensions system containers** option. You can now view your extension containers and their logs.\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Explore our [design principles](../design/design-principles.md).\n- Take a look at our [UI styling guidelines](../design/_index.md).\n- Learn how to [setup CI for your extension](continuous-integration.md).\n","content/manuals/extensions/extensions-sdk/dev/usage.md":"---\ntitle: CLI reference\ndescription: Docker extension CLI\nkeywords: Docker, extensions, sdk, CLI\naliases:\n - /desktop/extensions-sdk/dev/cli/usage/\n - /desktop/extensions-sdk/dev/usage/\nweight: 30\n---\n\nThe Extensions CLI is an extension development tool that is used to manage Docker extensions. Actions include install, list, remove, and validate extensions.\n\n- `docker extension enable` turns on Docker extensions.\n- `docker extension dev` commands for extension development.\n- `docker extension disable` turns off Docker extensions.\n- `docker extension init` creates a new Docker extension.\n- `docker extension install` installs a Docker extension with the specified image.\n- `docker extension ls` list installed Docker extensions.\n- `docker extension rm` removes a Docker extension.\n- `docker extension update` removes and re-installs a Docker extension.\n- `docker extension validate` validates the extension metadata file against the JSON schema.\n","content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md":"---\ntitle: Package and release your extension\ndescription: Docker extension distribution\nkeywords: Docker, extensions, sdk, distribution\naliases: \n - /desktop/extensions-sdk/extensions/DISTRIBUTION/\nweight: 30\n---\n\nThis page contains additional information on how to package and distribute extensions.\n\n## Package your extension\n\nDocker extensions are packaged as Docker images. The entire extension runtime including the UI, backend services (host or VM), and any necessary binary must be included in the extension image.\nEvery extension image must contain a `metadata.json` file at the root of its filesystem that defines the [contents of the extension](../architecture/metadata.md).\n\nThe Docker image must have several [image labels](labels.md), providing information about the extension. See how to use [extension labels](labels.md) to provide extension overview information.\n\nTo package and release an extension, you must build a Docker image (`docker build`), and push the image to [Docker Hub](https://hub.docker.com/) (`docker push`) with a specific tag that lets you manage versions of the extension.\n\n## Release your extension\n\nDocker image tags must follow semver conventions in order to allow fetching the latest version of the extension, and to know if there are updates available. See [semver.org](https://semver.org/) to learn more about semantic versioning.\n\nExtension images must be multi-arch images so that users can install extensions on ARM/AMD hardware. These multi-arch images can include ARM/AMD specific binaries. Mac users will automatically use the right image based on their architecture.\nExtensions that install binaries on the host must also provide Windows binaries in the same extension image. See how to [build a multi-arch image](multi-arch.md) for your extension.\n\nYou can implement extensions without any constraints on the code repository. Docker doesn't need access to the code repository in order to use the extension. Also, you can manage new releases of your extension, without any dependency on Docker Desktop releases.\n\n## New releases and updates\n\nYou can release a new version of your Docker extension by pushing a new image with a new tag to Docker Hub.\n\nAny new image pushed to an image repository corresponding to an extension defines a new version of that extension. Image tags are used to identify version numbers. Extension versions must follow semver to make it easy to understand and compare versions.\n\nDocker Desktop scans the list of extensions published in the marketplace for new versions, and provides notifications to users when they can upgrade a specific extension. Extensions that aren't part of the Marketplace don't have automatic update notifications at the moment.\n\nUsers can download and install the newer version of any extension without updating Docker Desktop itself.\n\n## Extension API dependencies\n\nExtensions must specify the Extension API version they rely on. Docker Desktop checks the extension's required version, and only proposes to install extensions that are compatible with the current Docker Desktop version installed. Users might need to update Docker Desktop in order to install the latest extensions available.\n\nExtension image labels must specify the API version that the extension relies upon. This allows Docker Desktop to inspect newer versions of extension images without downloading the full extension image upfront.\n\n## License on extensions and the extension SDK\n\nThe [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use.\n\nThere is no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n","content/manuals/extensions/extensions-sdk/extensions/_index.md":"---\ntitle: \"Part two: Publish\"\ndescription: General steps in how to publish an extension\nkeywords: Docker, Extensions, sdk, publish\naliases: \n - /desktop/extensions-sdk/extensions/\nweight: 40\n---\n\nThis section describes how to make your extension available and more visible, so users can discover it and install it with a single click.\n\n## Release your extension\n\nAfter you have developed your extension and tested it locally, you are ready to release the extension and make it available for others to install and use (either internally with your team, or more publicly).\n\nReleasing your extension consists of:\n\n- Providing information about your extension: description, screenshots, etc. so users can decide to install your extension\n- [Validating](validate.md) that the extension is built in the right format and includes the required information\n- Making the extension image available on [Docker Hub](https://hub.docker.com/)\n\nSee [Package and release your extension](DISTRIBUTION.md) for more details about the release process.\n\n## Promote your extension\n\nOnce your extension is available on Docker Hub, users who have access to the extension image can install it using the Docker CLI.\n\n### Use a share extension link\n\nYou can also [generate a share URL](share.md) in order to share your extension within your team, or promote your extension on the internet. The share link lets users view the extension description and screenshots.\n\n### Publish your extension in the Marketplace\n\nYou can publish your extension in the Extensions Marketplace to make it more discoverable. You must [submit your extension](publish.md) if you want to have it published in the Marketplace.\n\n## What happens next\n\n### New releases\n\nOnce you have released your extension, you can push a new release just by pushing a new version of the extension image, with an incremented tag (still using `semver` conventions).\nExtensions published in the Marketplace benefit from update notifications to all Desktop users that have installed the extension. For more details, see [new releases and updates](DISTRIBUTION.md#new-releases-and-updates).\n\n### Extension support and user feedback\n\nIn addition to providing a description of your extension's features and screenshots, you should also specify additional URLs using [extension labels](labels.md). This direct users to your website for reporting bugs and feedback, and accessing documentation and support.\n\n{{% include \"extensions-form.md\" %}}\n","content/manuals/extensions/extensions-sdk/extensions/labels.md":"---\ntitle: Extension image labels\nlinkTitle: Add labels\ndescription: Docker extension labels\nkeywords: Docker, extensions, sdk, labels\naliases: \n - /desktop/extensions-sdk/extensions/labels/\nweight: 10\n---\n\nExtensions use image labels to provide additional information such as a title, description, screenshots, and more.\n\nThis information is then displayed as an overview of the extension, so users can choose to install it.\n\n![An extension overview, generated from labels](images/marketplace-details.png)\n\nYou can define [image labels](/reference/dockerfile.md#label) in the extension's `Dockerfile`.\n\n> [!IMPORTANT]\n>\n> If any of the **required** labels are missing in the `Dockerfile`, Docker Desktop considers the extension invalid and doesn't list it in the Marketplace.\n\n\nHere is the list of labels you can or need to specify when building your extension:\n\n| Label                                       | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Example                                                                                                                                                                                                                                                         |\n| ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `org.opencontainers.image.title`            | Yes      | Human-readable title of the image (string). This appears in the UI for Docker Desktop.                                                                                                                                                                                                                                                                                                                                                                                                                | my-extension                                                                                                                                                                                                                                                    |\n| `org.opencontainers.image.description`      | Yes      | Human-readable description of the software packaged in the image (string)                                                                                                                                                                                                                                                                                                                                                                                                                             | This extension is cool.                                                                                                                                                                                                                                         |\n| `org.opencontainers.image.vendor`           | Yes      | Name of the distributing entity, organization, or individual.                                                                                                                                                                                                                                                                                                                                                                                                                                         | Acme, Inc.                                                                                                                                                                                                                                                      |\n| `com.docker.desktop.extension.api.version`  | Yes      | Version of the Docker Extension manager that the extension is compatible with. It must follow [semantic versioning](https://semver.org/).                                                                                                                                                                                                                                                                                                                                                             | A specific version like `0.1.0` or, a constraint expression: `>= 0.1.0`, `>= 1.4.7, < 2.0` . For your first extension, you can use `docker extension version` to know the SDK API version and specify `>= <SDK_API_VERSION>`.                                   |\n| `com.docker.desktop.extension.icon`         | Yes      | The extension icon (format: .svg .png .jpg)                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com/assets/image.svg`                                                                                                                                                                                                                          |\n| `com.docker.extension.screenshots`          | Yes      | A JSON array of image URLs and an alternative text displayed to users (in the order they appear in your metadata) in your extension's details page. **Note:** The recommended size for screenshots is 2400x1600 pixels.                                                                                                                                                                                                                                                                               | `[{\"alt\":\"alternative text for image 1\",` `\"url\":\"https://example.com/image1.png\"},` `{\"alt\":\"alternative text for image2\",` `\"url\":\"https://example.com/image2.jpg\"}]`                                                                                         |\n| `com.docker.extension.detailed-description` | Yes      | Additional information in plain text or HTML about the extension to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                    | `My detailed description` or `<h1>My detailed description</h1>`                                                                                                                                                                                                 |\n| `com.docker.extension.publisher-url`        | Yes      | The publisher website URL to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com`                                                                                                                                                                                                                                           |\n| `com.docker.extension.additional-urls`      | No       | A JSON array of titles and additional URLs displayed to users (in the order they appear in your metadata) in your extension's details page. Docker recommends you display the following links if they apply: documentation, support, terms of service, and privacy policy links.                                                                                                                                                                                                                      | `[{\"title\":\"Documentation\",\"url\":\"https://example.com/docs\"},` `{\"title\":\"Support\",\"url\":\"https://example.com/bar/support\"},` `{\"title\":\"Terms of Service\",\"url\":\"https://example.com/tos\"},` `{\"title\":\"Privacy policy\",\"url\":\"https://example.com/privacy\"}]` |\n| `com.docker.extension.changelog`            | Yes      | Changelog in plain text or HTML containing the change for the current version only.                                                                                                                                                                                                                                                                                                                                                                                                                   | `Extension changelog` or `<p>Extension changelog<ul>` `<li>New feature A</li>` `<li>Bug fix on feature B</li></ul></p>`                                                                                                                                         |\n| `com.docker.extension.account-info`         | No       | Whether the user needs to register to a SaaS platform to use some features of the extension.                                                                                                                                                                                                                                                                                                                                                                                                          | `required` in case it does, leave it empty otherwise.                                                                                                                                                                                                           |\n| `com.docker.extension.categories`           | No       | The list of Marketplace categories that your extension belongs to: `ci-cd`, `container-orchestration`, `cloud-deployment`, `cloud-development`, `database`, `kubernetes`, `networking`, `image-registry`, `security`, `testing-tools`, `utility-tools`,`volumes`. If you don't specify this label, users won't be able to find your extension in the Extensions Marketplace when filtering by a category. Extensions published to the Marketplace before the 22nd of September 2022 have been auto-categorized by Docker. | Specified as comma separated values in case of having multiple categories e.g: `kubernetes,security` or a single value e.g. `kubernetes`.                                                                                                   |\n\n> [!TIP]\n>\n> Docker Desktop applies CSS styles to the provided HTML content. You can make sure that it renders correctly \n> [within the Marketplace](#preview-the-extension-in-the-marketplace). It is recommended that you follow the \n> [styling guidelines](../design/_index.md).\n\n## Preview the extension in the Marketplace\n\nYou can validate that the image labels render as you expect.\n\nWhen you create and install your unpublished extension, you can preview the extension in the Marketplace's **Managed** tab. You can see how the extension labels render in the list and in the details page of the extension.\n\n> Preview extensions already listed in Marketplace\n>\n> When you install a local image of an extension already published in the Marketplace, for example with the tag `latest`, your local image is not detected as \"unpublished\".\n>\n> You can re-tag your image in order to have a different image name that's not listed as a published extension.\n> Use `docker tag org/published-extension unpublished-extension` and then `docker extension install unpublished-extension`.\n\n![List preview](images/list-preview.png)\n","content/manuals/extensions/extensions-sdk/extensions/multi-arch.md":"---\ntitle: Build multi-arch extensions\ndescription: Step three in creating an extension.\nkeywords: Docker, Extensions, sdk, build, multi-arch\naliases: \n - /desktop/extensions-sdk/extensions/multi-arch/\n---\n\nIt is highly recommended that, at a minimum, your extension is supported for the following architectures:\n\n- `linux/amd64`\n- `linux/arm64`\n\nDocker Desktop retrieves the extension image according to the user’s system architecture. If the extension does not provide an image that matches the user’s system architecture, Docker Desktop is not able to install the extension. As a result, users can’t run the extension in Docker Desktop.\n\n## Build and push for multiple architectures\n\nIf you created an extension from the `docker extension init` command, the\n`Makefile` at the root of the directory includes a target with name\n`push-extension`.\n\nYou can run `make push-extension` to build your extension against both\n`linux/amd64` and `linux/arm64` platforms, and push them to Docker Hub.\n\nFor example:\n\n```console\n$ make push-extension\n```\n\nAlternatively, if you started from an empty directory, use the command below\nto build your extension for multiple architectures:\n\n```console\n$ docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=username/my-extension:0.0.1 .\n```\n\nYou can then check the image manifest to see if the image is available for both\narchitectures using the [`docker buildx imagetools` command](/reference/cli/docker/buildx/imagetools/):\n\n```console\n$ docker buildx imagetools inspect username/my-extension:0.0.1\nName:      docker.io/username/my-extension:0.0.1\nMediaType: application/vnd.docker.distribution.manifest.list.v2+json\nDigest:    sha256:f3b552e65508d9203b46db507bb121f1b644e53a22f851185d8e53d873417c48\n\nManifests:\n  Name:      docker.io/username/my-extension:0.0.1@sha256:71d7ecf3cd12d9a99e73ef448bf63ae12751fe3a436a007cb0969f0dc4184c8c\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/amd64\n\n  Name:      docker.io/username/my-extension:0.0.1@sha256:5ba4ceea65579fdd1181dfa103cc437d8e19d87239683cf5040e633211387ccf\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/arm64\n```\n\n> [!TIP]\n>\n> If you're having trouble pushing the image, make sure you're signed in to Docker Hub. Otherwise, run `docker login` to authenticate.\n\nFor more information, see [Multi-platform images](/manuals/build/building/multi-platform.md) page.\n\n## Adding multi-arch binaries\n\nIf your extension includes some binaries that deploy to the host, it’s important that they also have the right architecture when building the extension against multiple architectures.\n\nCurrently, Docker does not provide a way to explicitly specify multiple binaries for every architecture in the `metadata.json` file. However, you can add architecture-specific binaries depending on the `TARGETARCH` in the extension’s `Dockerfile`.\n\nThe following example shows an extension that uses a binary as part of its operations. The extension needs to run both in Docker Desktop for Mac and Windows.\n\nIn the `Dockerfile`, download the binary depending on the target architecture:\n\n```Dockerfile\n#syntax=docker/dockerfile:1.3-labs\n\nFROM alpine AS dl\nWORKDIR /tmp\nRUN apk add --no-cache curl tar\nARG TARGETARCH\nRUN <<EOT ash\n    mkdir -p /out/darwin\n    curl -fSsLo /out/darwin/kubectl \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/darwin/${TARGETARCH}/kubectl\"\n    chmod a+x /out/darwin/kubectl\nEOT\nRUN <<EOT ash\n    if [ \"amd64\" = \"$TARGETARCH\" ]; then\n        mkdir -p /out/windows\n        curl -fSsLo /out/windows/kubectl.exe \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe\"\n    fi\nEOT\n\nFROM alpine\nLABEL org.opencontainers.image.title=\"example-extension\" \\\n    org.opencontainers.image.description=\"My Example Extension\" \\\n    org.opencontainers.image.vendor=\"Docker Inc.\" \\\n    com.docker.desktop.extension.api.version=\">= 0.3.3\"\n\nCOPY --from=dl /out /\n```\n\nIn the `metadata.json` file, specify the path for every binary on every platform:\n\n```json\n{\n  \"icon\": \"docker.svg\",\n  \"ui\": {\n    \"dashboard-tab\": {\n      \"title\": \"Example Extension\",\n      \"src\": \"index.html\",\n      \"root\": \"ui\"\n    }\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/kubectl\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/kubectl.exe\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nAs a result, when `TARGETARCH` equals:\n\n- `arm64`, the `kubectl` binary fetched corresponds to the `arm64` architecture, and is copied to `/darwin/kubectl` in the final stage.\n- `amd64`, two `kubectl` binaries are fetched. One for Darwin and another for Windows. They are copied to `/darwin/kubectl` and `/windows/kubectl.exe` respectively, in the final stage.\n\n> [!NOTE]\n>\n> The binary destination path for Darwin is `darwin/kubectl` in both cases. The only change is the architecture-specific binary that is downloaded.\n\nWhen the extension is installed, the extension framework copies the binaries from the extension image at `/darwin/kubectl` for Darwin, or `/windows/kubectl.exe` for Windows, to a specific location in the user’s host filesystem.\n\n## Can I develop extensions that run Windows containers?\n\nAlthough Docker Extensions is supported on Docker Desktop for Windows, Mac, and Linux, the extension framework only supports Linux containers. Therefore, you must target `linux` as the OS when you build your extension image.\n","content/manuals/extensions/extensions-sdk/extensions/publish.md":"---\ntitle: Publish in the Marketplace\ndescription: Docker extension distribution\nkeywords: Docker, extensions, publish\naliases: \n - /desktop/extensions-sdk/extensions/publish/\nweight: 50\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\n## Submit your extension to the Marketplace\n\nDocker Desktop displays published extensions in the Extensions Marketplace on [Docker Desktop](https://open.docker.com/extensions/marketplace) and [Docker Hub](https://hub.docker.com/search?q=&type=extension).\nThe Extensions Marketplace is a space where developers can discover extensions to improve their developer experience and propose their own extension to be available for all Desktop users.\n\nWhenever you are [ready to publish](DISTRIBUTION.md) your extension in the Marketplace, you can [self-publish your extension](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n> [!NOTE]\n>\n> As the Extension Marketplace continues to add new features for both Extension users and publishers, you are expected\n> to maintain your extension over time to ensure it stays available in the Marketplace.\n\n> [!IMPORTANT]\n>\n> The Docker manual review process for extensions is paused at the moment. Submit your extension through the [automated submission process](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n### Before you submit\n\nBefore you submit your extension, it must pass the [validation](validate.md) checks.\n\nIt is highly recommended that your extension follows the guidelines outlined in this section before submitting your\nextension. If you request a review from the Docker Extensions team and have not followed the guidelines, the review process may take longer. \n\nThese guidelines don't replace Docker's terms of service or guarantee approval:\n- Review the [design guidelines](../design/design-guidelines.md)\n- Ensure the [UI styling](../design/_index.md) is in line with Docker Desktop guidelines\n- Ensure your extensions support both light and dark mode\n- Consider the needs of both new and existing users of your extension\n- Test your extension with potential users\n- Test your extension for crashes, bugs, and performance issues\n- Test your extension on various platforms (Mac, Windows, Linux)\n- Read the [Terms of Service](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)\n\n#### Validation process\n\nSubmitted extensions go through an automated validation process. If all the validation checks pass successfully, the extension is\npublished on the Marketplace and accessible to all users within a few hours.\nIt is the fastest way to get developers the tools they need and to get feedback from them as you work to\nevolve/polish your extension.\n\n> [!IMPORTANT]\n>\n> Docker Desktop caches the list of extensions available in the Marketplace for 12 hours. If you don't see your\n> extension in the Marketplace, you can restart Docker Desktop to force the cache to refresh.\n","content/manuals/extensions/extensions-sdk/extensions/share.md":"---\ntitle: Share your extension\ndescription: Share your extension with a share link\nkeywords: Docker, extensions, share\naliases: \n - /desktop/extensions-sdk/extensions/share/\nweight: 40\n---\n\nOnce your extension image is accessible on Docker Hub, anyone with access to the image can install the extension.\n\nPeople can install your extension by typing `docker extension install my/awesome-extension:latest` in to the terminal.\n\nHowever, this option doesn't provide a preview of the extension before it's installed.\n\n## Create a share URL\n\nDocker lets you share your extensions using a URL.\n\nWhen people navigate to this URL, it opens Docker Desktop and displays a preview of your extension in the same way as an extension in the Marketplace. From the preview, users can then select **Install**.\n\n![Navigate to extension link](images/open-share.png)\n\nTo generate this link you can either:\n\n- Run the following command:\n\n  ```console\n  $ docker extension share my/awesome-extension:0.0.1\n  ```\n\n- Once you have installed your extension locally, navigate to the **Manage** tab and select **Share**.\n\n  ![Share button](images/list-preview.png)\n\n> [!NOTE]\n>\n> Previews of the extension description or screenshots, for example, are created using [extension labels](labels.md).\n","content/manuals/extensions/extensions-sdk/extensions/validate.md":"---\ntitle: Validate your extension\nlinkTitle: Validate\ndescription: Step three in the extension creation process\nkeywords: Docker, Extensions, sdk, validate, install\naliases:\n - /desktop/extensions-sdk/extensions/validation/\n - /desktop/extensions-sdk/build/build-install/\n - /desktop/extensions-sdk/dev/cli/build-test-install-extension/\n - /desktop/extensions-sdk/extensions/validate/\nweight: 20\n---\n\nValidate your extension before you share or publish it. Validating the extension ensures that the extension:\n\n- Is built with the [image labels](labels.md) it requires to display correctly in the marketplace\n- Installs and runs without problems\n\nThe Extensions CLI lets you validate your extension before installing and running it locally.\n\nThe validation checks if the extension’s `Dockerfile` specifies all the required labels and if the metadata file is valid against the JSON schema file.\n\nTo validate, run:\n\n```console\n$ docker extension validate <name-of-your-extension>\n```\n\nIf your extension is valid, the following message displays:\n\n```console\nThe extension image \"name-of-your-extension\" is valid\n```\n\nBefore the image is built, it's also possible to validate only the `metadata.json` file:\n\n```console\n$ docker extension validate /path/to/metadata.json\n```\n\nThe JSON schema used to validate the `metadata.json` file against can be found under the [releases page](https://github.com/docker/extensions-sdk/releases/latest).\n","content/manuals/extensions/extensions-sdk/guides/_index.md":"---\nbuild:\n  render: never\ntitle: Developer Guides\n---\n","content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md":"---\ntitle: Invoke host binaries\ndescription: Add invocations to host binaries from the frontend with the extension\n  SDK.\nkeywords: Docker, extensions, sdk, build\naliases:\n - /desktop/extensions-sdk/guides/invoke-host-binaries/\n---\n\nIn some cases, your extension may need to invoke some command from the host. For example, you\nmight want to invoke the CLI of your cloud provider to create a new resource, or the CLI of a tool your extension\nprovides, or even a shell script that you want to run on the host. \n\nYou could do that by executing the CLI from a container with the extension SDK. But this CLI needs to access the host's filesystem, which isn't easy nor fast if it runs in a container.\n\nThis page describes how to run executables on the host (binaries, shell scripts) that are shipped as part of your extension and deployed to the host. As extensions can run on multiple platforms, this\nmeans that you need to ship the executables for all the platforms you want to support.\n\nLearn more about extensions [architecture](../architecture/_index.md).\n\n> [!NOTE]\n>\n>  Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nIn this example, the CLI is a simple `Hello world` script that must be invoked with a parameter and returns a \nstring.\n\n## Add the executables to the extension\n\n{{< tabs >}}\n{{< tab name=\"Mac and Linux\" >}}\n\nCreate a `bash` script for macOS and Linux, in the file `binaries/unix/hello.sh` with the following content:\n\n```bash\n#!/bin/sh\necho \"Hello, $1!\"\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\nCreate a `batch script` for Windows in another file `binaries/windows/hello.cmd` with the following content:\n\n```bash\n@echo off\necho \"Hello, %1!\"\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThen update the `Dockerfile` to copy the `binaries` folder into the extension's container filesystem and make the\nfiles executable.\n\n```dockerfile\n# Copy the binaries into the right folder\nCOPY --chmod=0755 binaries/windows/hello.cmd /windows/hello.cmd\nCOPY --chmod=0755 binaries/unix/hello.sh /linux/hello.sh\nCOPY --chmod=0755 binaries/unix/hello.sh /darwin/hello.sh\n```\n\n## Invoke the executable from the UI\n\nIn your extension, use the Docker Desktop Client object to [invoke the shell script](../dev/api/backend.md#invoke-an-extension-binary-on-the-host)\nprovided by the extension with the `ddClient.extension.host.cli.exec()` function.\nIn this example, the binary returns a string as result, obtained by `result?.stdout`, as soon as the extension view is rendered.\n\n{{< tabs group=\"framework\" >}}\n{{< tab name=\"React\" >}}\n\n```typescript\nexport function App() {\n  const ddClient = createDockerDesktopClient();\n  const [hello, setHello] = useState(\"\");\n\n  useEffect(() => {\n    const run = async () => {\n      let binary = \"hello.sh\";\n      if (ddClient.host.platform === 'win32') {\n        binary = \"hello.cmd\";\n      }\n\n      const result = await ddClient.extension.host?.cli.exec(binary, [\"world\"]);\n      setHello(result?.stdout);\n\n    };\n    run();\n  }, [ddClient]);\n    \n  return (\n    <div>\n      {hello}\n    </div>\n  );\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Vue\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Vue yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Vue)\n> and let us know if you'd like a sample with Vue.\n\n{{< /tab >}}\n{{< tab name=\"Angular\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Angular yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Angular)\n> and let us know if you'd like a sample with Angular.\n\n{{< /tab >}}\n{{< tab name=\"Svelte\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Svelte yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Svelte)\n> and let us know if you'd like a sample with Svelte.\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Configure the metadata file\n\nThe host binaries must be specified in the `metadata.json` file so that Docker Desktop copies them on to the host when installing\nthe extension. Once the extension is uninstalled, the binaries that were copied are removed as well.\n\n```json\n{\n  \"vm\": {\n    ...\n  },\n  \"ui\": {\n    ...\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/hello.sh\"\n          }\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/hello.sh\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/hello.cmd\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nThe `path` must reference the path of the binary inside the container.\n","content/manuals/extensions/extensions-sdk/guides/kubernetes.md":"---\ntitle: Interacting with Kubernetes from an extension\nlinkTitle: Interacting with Kubernetes\ndescription: How to connect to a Kubernetes cluster from an extension\nkeywords: Docker, Extensions, sdk, Kubernetes\naliases:\n - /desktop/extensions-sdk/dev/kubernetes/\n - /desktop/extensions-sdk/guides/kubernetes/\n---\n\nThe Extensions SDK does not provide any API methods to directly interact with the Docker Desktop managed Kubernetes cluster or any other created using other tools such as KinD. However, this page provides a way for you to use other SDK APIs to interact indirectly with a Kubernetes cluster from your extension.\n\nTo request an API that directly interacts with Docker Desktop-managed Kubernetes, you can upvote [this issue](https://github.com/docker/extensions-sdk/issues/181) in the Extensions SDK GitHub repository.\n\n## Prerequisites\n\n### Turn on Kubernetes\n\nYou can use the built-in Kubernetes in Docker Desktop to start a Kubernetes single-node cluster.\nA `kubeconfig` file is used to configure access to Kubernetes when used in conjunction with the `kubectl` command-line tool, or other clients.\nDocker Desktop conveniently provides the user with a local preconfigured `kubeconfig` file and `kubectl` command within the user’s home area. It is a convenient way to fast-tracking access for those looking to leverage Kubernetes from Docker Desktop.\n\n## Ship the `kubectl` as part of the extension\n\nIf your extension needs to interact with Kubernetes clusters, it is recommended that you include the `kubectl` command line tool as part of your extension. By doing this, users who install your extension get `kubectl` installed on their host.\n\nTo find out how to ship the `kubectl` command line tool for multiple platforms as part of your Docker Extension image, see [Build multi-arch extensions](../extensions/multi-arch.md#adding-multi-arch-binaries).\n\n## Examples\n\nThe following code snippets have been put together in the [Kubernetes Sample Extension](https://github.com/docker/extensions-sdk/tree/main/samples/kubernetes-sample-extension). It shows how to interact with a Kubernetes cluster by shipping the `kubectl` command-line tool.\n\n### Check the Kubernetes API server is reachable\n\nOnce the `kubectl` command-line tool is added to the extension image in the `Dockerfile`, and defined in the `metadata.json`, the Extensions framework deploys `kubectl` to the users' host when the extension is installed.\n\nYou can use the JS API `ddClient.extension.host?.cli.exec` to issue `kubectl` commands to, for instance, check whether the Kubernetes API server is reachable given a specific context:\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"cluster-info\",\n  \"--request-timeout\",\n  \"2s\",\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n### List Kubernetes contexts\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"-o\",\n  \"jsonpath='{.contexts}'\",\n]);\n```\n\n### List Kubernetes namespaces\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"get\",\n  \"namespaces\",\n  \"--no-headers\",\n  \"-o\",\n  'custom-columns=\":metadata.name\"',\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n## Persisting the kubeconfig file\n\nBelow there are different ways to persist and read the `kubeconfig` file from the host filesystem. Users can add, edit, or remove Kubernetes context to the `kubeconfig` file at any time.\n\n> Warning\n>\n> The `kubeconfig` file is very sensitive and if found can give an attacker administrative access to the Kubernetes Cluster.\n\n### Extension's backend container\n\nIf you need your extension to persist the `kubeconfig` file after it's been read, you can have a backend container that exposes an HTTP POST endpoint to store the content of the file either in memory or somewhere within the container filesystem. This way, if the user navigates out of the extension to another part of Docker Desktop and then comes back, you don't need to read the `kubeconfig` file again.\n\n```typescript\nexport const updateKubeconfig = async () => {\n  const kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n    \"config\",\n    \"view\",\n    \"--raw\",\n    \"--minify\",\n    \"--context\",\n    \"docker-desktop\",\n  ]);\n  if (kubeConfig?.stderr) {\n    console.log(\"error\", kubeConfig?.stderr);\n    return false;\n  }\n\n  // call backend container to store the kubeconfig retrieved into the container's memory or filesystem\n  try {\n    await ddClient.extension.vm?.service?.post(\"/store-kube-config\", {\n      data: kubeConfig?.stdout,\n    });\n  } catch (err) {\n    console.log(\"error\", JSON.stringify(err));\n  }\n};\n```\n\n### Docker volume\n\nVolumes are the preferred mechanism for persisting data generated by and used by Docker containers. You can make use of them to persist the `kubeconfig` file.\nBy persisting the `kubeconfig` in a volume you won't need to read the `kubeconfig` file again when the extension pane closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nconst kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"--raw\",\n  \"--minify\",\n  \"--context\",\n  \"docker-desktop\",\n]);\nif (kubeConfig?.stderr) {\n  console.log(\"error\", kubeConfig?.stderr);\n  return false;\n}\n\nawait ddClient.docker.cli.exec(\"run\", [\n  \"--rm\",\n  \"-v\",\n  \"my-vol:/tmp\",\n  \"alpine\",\n  \"/bin/sh\",\n  \"-c\",\n  `\"touch /tmp/.kube/config && echo '${kubeConfig?.stdout}' > /tmp/.kube/config\"`,\n]);\n```\n\n### Extension's `localStorage`\n\n`localStorage` is one of the mechanisms of a browser's web storage. It allows users to save data as key-value pairs in the browser for later use.\n`localStorage` does not clear data when the browser (the extension pane) closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nlocalStorage.setItem(\"kubeconfig\", kubeConfig);\n```\n\n```typescript\nlocalStorage.getItem(\"kubeconfig\");\n```\n","content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md":"---\ntitle: Authentication\ndescription: Docker extension OAuth 2.0 flow\nkeywords: Docker, extensions, sdk, OAuth 2.0\naliases:\n - /desktop/extensions-sdk/dev/oauth2-flow/\n - /desktop/extensions-sdk/guides/oauth2-flow/\n---\n\n> [!NOTE]\n>\n> This page assumes that you already have an Identity Provider (IdP), such as Google, Entra ID (formerly Azure AD) or Okta, which handles the authentication process and returns an access token.\n\nLearn how you can let users authenticate from your extension using OAuth 2.0 via a web browser, and return to your extension.\n\nIn OAuth 2.0, the term \"grant type\" refers to the way an application gets an access token. Although OAuth 2.0 defines several grant types, this page only describes how to authorize users from your extension using the Authorization Code grant type.\n\n## Authorization code grant flow\n\nThe Authorization Code grant type is used by confidential and public clients to exchange an authorization code for an access token.\n\nAfter the user returns to the client via the redirect URL, the application gets the authorization code from the URL and uses it to request an access token.\n\n![Flow for OAuth 2.0](images/oauth.png)\n\nThe image above shows that:\n\n- The Docker extension asks the user to authorize access to their data.\n- If the user grants access, the extension then requests an access token from the service provider, passing the access grant from the user and authentication details to identify the client.\n- The service provider then validates these details and returns an access token.\n- The extension uses the access token to request the user data with the service provider.\n\n### OAuth 2.0 terminology\n\n- Auth URL: The endpoint for the API provider authorization server, to retrieve the auth code.\n- Redirect URI: The client application callback URL to redirect to after auth. This must be registered with the API provider.\n\nOnce the user enters the username and password, they're successfully authenticated.\n\n## Open a browser page to authenticate the user\n\nFrom the extension UI, you can provide a button that, when selected, opens a new window in a browser to authenticate the user.\n\nUse the [ddClient.host.openExternal](../dev/api/dashboard.md#open-a-url) API to open a browser to the auth URL. For\nexample:\n\n```typescript\nwindow.ddClient.openExternal(\"https://authorization-server.com/authorize?\n  response_type=code\n  &client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n  &redirect_uri=${REDIRECT_URI});\n```\n\n## Get the authorization code and access token\n\nYou can get the authorization code from the extension UI by listing `docker-desktop://dashboard/extension-tab?extensionId=awesome/my-extension` as the `redirect_uri` in the OAuth app you're using and concatenating the authorization code as a query parameter. The extension UI code will then be able to read the corresponding code query-param.\n\n> [!IMPORTANT]\n>\n> Using this feature requires the extension SDK 0.3.3 in Docker Desktop. You need to ensure that the required SDK version for your extension set with `com.docker.desktop.extension.api.version` in [image labels](../extensions/labels.md) is higher than 0.3.3.\n\n#### Authorization\n\nThis step is where the user enters their credentials in the browser. After the authorization is complete, the user is redirected back to your extension user interface, and the extension UI code can consume the authorization code that's part of the query parameters in the URL.\n\n#### Exchange the Authorization Code\n\nNext, you exchange the authorization code for an access token.\n\nThe extension must send a `POST` request to the 0Auth authorization server with the following parameters:\n\n```text\nPOST https://authorization-server.com/token\n&client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n&client_secret=YABbyHQShPeO1T3NDQZP8q5m3Jpb_UPNmIzqhLDCScSnRyVG\n&redirect_uri=${REDIRECT_URI}\n&code=N949tDLuf9ai_DaOKyuFBXStCNMQzuQbtC1QbvLv-AXqPJ_f\n```\n\n> [!NOTE]\n>\n> The client's credentials are included in the `POST` query params in this example. OAuth authorization servers may require that the credentials are sent as a HTTP Basic Authentication header or might support different formats. See your OAuth provider docs for details.\n\n### Store the access token\n\nThe Docker Extensions SDK doesn't provide a specific mechanism to store secrets.\n\nIt's highly recommended that you use an external source of storage to store the access token.\n\n> [!NOTE]\n>\n> The user interface Local Storage is isolated between extensions (an extension can't access another extension's local storage), and each extension's local storage gets deleted when users uninstall an extension.\n\n## What's next\n\nLearn how to [publish and distribute your extension](../extensions/_index.md)\n","content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md":"---\ntitle: Use the Docker socket from the extension backend\nlinkTitle: Use the Docker socket\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/guides/use-docker-socket-from-backend/\n---\n\nExtensions can invoke Docker commands directly from the frontend with the SDK. \n\nIn some cases, it is useful to also interact with Docker Engine from the backend. \n\nExtension backend containers can mount the Docker socket and use it to\ninteract with Docker Engine from the extension backend logic. Learn more about the [Docker Engine socket](/reference/cli/dockerd/#examples)\n\nHowever, when mounting the Docker socket from an extension container that lives in the Desktop virtual machine, you want\nto mount the Docker socket from inside the VM, and not mount `/var/run/docker.sock` from the host filesystem (using\nthe Docker socket from the host can lead to permission issues in containers).\n\nIn order to do so, you can use `/var/run/docker.sock.raw`. Docker Desktop mounts the socket that lives in the Desktop VM, and not from the host.\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /var/run/docker.sock.raw:/var/run/docker.sock\n```\n","content/manuals/extensions/extensions-sdk/process.md":"---\ndescription: Understand the process of creating an extension.\ntitle: The build and publish process\nkeyword: Docker Extensions, sdk, build, create, publish\naliases:\n - /desktop/extensions-sdk/process/\nweight: 10\n---\n\nThis documentation is structured so that it matches the steps you need to take when creating your extension. \n\nThere are two main parts to creating a Docker extension:\n\n1. Build the foundations\n2. Publish the extension\n\n> [!NOTE]\n>\n> You do not need to pay to create a Docker extension. The [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use. Anyone can create new extensions and share them without constraints.\n> \n> There is also no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n\n## Part one: Build the foundations\n\nThe build process consists of:\n\n- Installing the latest version of Docker Desktop.\n- Setting up the directory with files, including the extension’s source code and the required extension-specific files.\n- Creating the `Dockerfile` to build, publish, and run your extension in Docker Desktop.\n- Configuring the metadata file which is required at the root of the image filesystem.\n- Building and installing the extension.\n\nFor further inspiration, see the other examples in the [samples folder](https://github.com/docker/extensions-sdk/tree/main/samples).\n\n> [!TIP]\n>\n> Whilst creating your extension, make sure you follow the [design](design/design-guidelines.md) and [UI styling](design/_index.md) guidelines to ensure visual consistency and [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n## Part two: Publish and distribute your extension\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nDocker Desktop displays published extensions in the Extensions Marketplace. The Extensions Marketplace is a curated space where developers can discover extensions to improve their developer experience and upload their own extension to share with the world.\n\nIf you want your extension published in the Marketplace, read the [publish documentation](extensions/publish.md).\n\n{{% include \"extensions-form.md\" %}}\n\n## What’s next?\n\nIf you want to get up and running with creating a Docker Extension, see the [Quickstart guide](quickstart.md).\n\nAlternatively, get started with reading the \"Part one: Build\" section for more in-depth information about each step of the extension creation process.\n\nFor an in-depth tutorial of the entire build process, we recommend the following video walkthrough from DockerCon 2022.\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Yv7OG-EGJsg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n","content/manuals/extensions/extensions-sdk/quickstart.md":"---\ntitle: Quickstart\ndescription: Guide on how to build an extension quickly\nkeywords: quickstart, extensions\naliases:\n - desktop/extensions-sdk/tutorials/initialize/\n - /desktop/extensions-sdk/quickstart/\nweight: 20\n---\n\nFollow this guide to get started with creating a basic Docker extension. The Quickstart guide automatically generates boilerplate files for you.\n\n## Prerequisites\n\n- [Docker Desktop](/manuals/desktop/release-notes.md)\n- [NodeJS](https://nodejs.org/)\n- [Go](https://go.dev/dl/)\n\n> [!NOTE]\n>\n> NodeJS and Go are only required when you follow the quickstart guide to create an extension. It uses the `docker extension init` command to automatically generate boilerplate files. This command uses a template based on a ReactJS and Go application.\n\nIn Docker Desktop settings, ensure you can install the extension you're developing. You may need to navigate to the **Extensions** tab in Docker Desktop settings and deselect **Allow only extensions distributed through the Docker Marketplace**.\n\n## Step one: Set up your directory\n\nTo set up your directory, use the `init` subcommand and provide a name for your extension.\n\n```console\n$ docker extension init <my-extension>\n```\n\nThe command asks a series of questions about your extension, such as its name, a description, and the name of your Hub repository. This helps the CLI generate a set of boilerplate files for you to get started. It stores the boilerplate files in the `my-extension` directory.\n\nThe automatically generated extension contains:\n\n- A Go backend service in the `backend` folder that listens on a socket. It has one endpoint `/hello` that returns a JSON payload.\n- A React frontend in the `frontend` folder that can call the backend and output the backend’s response.\n\nFor more information and guidelines on building the UI, see the [Design and UI styling section](design/design-guidelines.md).\n\n## Step two: Build the extension\n\nTo build the extension, move into the newly created directory and run:\n\n```console\n$ docker build -t <name-of-your-extension> .\n```\n\n`docker build` builds the extension and generates an image named the same as the chosen hub repository. For example, if you typed `john/my-extension` as the answer to the following question:\n\n```console\n? Hub repository (eg. namespace/repository on hub): john/my-extension`\n```\n\nThe `docker build` generates an image with name `john/my-extension`.\n\n## Step three: Install and preview the extension\n\nTo install the extension in Docker Desktop, run:\n\n```console\n$ docker extension install <name-of-your-extension>\n```\n\nTo preview the extension in Docker Desktop, once the installation is complete and you should\nsee a **Quickstart** item underneath the **Extensions** menu. Selecting this item opens the extension's frontend.\n\n> [!TIP]\n>\n> During UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\n> extension. See [Preview whilst developing the UI](dev/test-debug.md#hot-reloading-whilst-developing-the-ui) for more information.\n\nYou may also want to inspect the containers that belong to the extension. By default, extension containers are\nhidden from the Docker Dashboard. You can change this in **Settings**, see\n[how to show extension containers](dev/test-debug.md#show-the-extension-containers) for more information.\n\n## Step four: Submit and publish your extension to the Marketplace\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nIf you want to make your extension available to all Docker Desktop users, you can submit it for publication in the Marketplace. For more information, see [Publish](extensions/_index.md).\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build a more [advanced frontend](build/frontend-extension-tutorial.md) for your extension.\n- Learn how to [test and debug](dev/test-debug.md) your extension.\n- Learn how to [setup CI for your extension](dev/continuous-integration.md).\n- Learn more about extensions [architecture](architecture/_index.md).\n- Learn more about [designing the UI](design/design-guidelines.md).\n","content/manuals/extensions/marketplace.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace\ntitle: Marketplace extensions\nweight: 10\naliases:\n - /desktop/extensions/marketplace/\n---\n\nThere are two types of extensions available in the Extensions Marketplace:\n- Docker-reviewed extensions\n- Self-published extensions\n\nDocker-reviewed extensions are manually reviewed by the Docker Extensions team to ensure an extra level of trust\nand quality. They appear as **Reviewed** in the Marketplace.\n\nSelf-published extensions are autonomously published by extension developers and go through an automated validation process. They appear as **Not reviewed** in the Marketplace.\n\n> [!IMPORTANT]\n>\n> Marketplace extensions are reviewed by Docker, but are not subject to a full security audit. Extensions run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Only install extensions from publishers you trust.\n\n## Install an extension\n\n> [!NOTE]\n>\n> For some extensions, a separate account needs to be created before use.\n\nTo install an extension:\n\n1. Open Docker Desktop.\n2. From the Docker Desktop Dashboard, select the **Extensions** tab.\n   The Extensions Marketplace opens on the **Browse** tab.\n3. Browse the available extensions.\n   You can sort the list of extensions by **Recently added**, **Most installed**, or alphabetically. Alternatively, use the **Content** or **Categories** drop-down menu to search for extensions by whether they have been reviewed or not, or by category.\n4. Choose an extension and select **Install**.\n\nFrom here, you can select **Open** to access the extension or install additional extensions. The extension also appears in the left-hand menu and in the **Manage** tab.\n\n## Update an extension\n\nYou can update any extension outside of Docker Desktop releases. To update an extension to the latest version, navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n\nThe **Manage** tab displays with all your installed extensions. If an extension has a new version available, it displays an **Update** button.\n\n\n## Uninstall an extension\n\nYou can uninstall an extension at any time.\n\n> [!NOTE]\n>\n> Any data used by the extension that's stored in a volume must be manually deleted.\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the ellipsis to the right of extension you want to uninstall.\n3. Select **Uninstall**.\n","content/manuals/extensions/non-marketplace.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows,\ntitle: Non-marketplace extensions\nweight: 20\n---\n\n## Install an extension not available in the Marketplace\n\n> [!WARNING]\n>\n> Extensions installed outside the Marketplace have not gone through Docker's review process. Like all Docker extensions, they run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Install only if you trust the publisher and have verified the source.\n\nThe Extensions Marketplace is the trusted and official place to install extensions from within Docker Desktop. These extensions have gone through a review process by Docker. However, other extensions can also be installed in Docker Desktop if you trust the extension author.\n\nGiven the nature of a Docker Extension (i.e. a Docker image) you can find other places where users have their extension's source code published. For example on GitHub, GitLab or even hosted in image registries like DockerHub or GHCR.\nYou can install an extension that has been developed by the community or internally at your company from a teammate. You are not limited to installing extensions just from the Marketplace.\n\n> [!NOTE]\n>\n> Ensure the option **Allow only extensions distributed through the Docker Marketplace** is disabled. Otherwise, this prevents any extension not listed in the Marketplace, via the Extension SDK tools from, being installed.\n> You can change this option in **Settings**. \n\nTo install an extension which is not present in the Marketplace, you can use the Extensions CLI that is bundled with Docker Desktop.\n\nIn a terminal, type `docker extension install IMAGE[:TAG]` to install an extension by its image reference and optionally a tag. Use the `-f` or `--force` flag to avoid interactive confirmation.\n\nGo to the Docker Desktop Dashboard to see the new extension installed.\n\n## List installed extensions\n\nRegardless whether the extension was installed from the Marketplace or manually by using the Extensions CLI, you can use the `docker extension ls` command to display the list of extensions installed.\nAs part of the output you'll see the extension ID, the provider, version, the title and whether it runs a backend container or has deployed binaries to the host, for example:\n\n```console\n$ docker extension ls\nID                  PROVIDER            VERSION             UI                    VM                  HOST\njohn/my-extension   John                latest              1 tab(My-Extension)   Running(1)          -\n```\n\nGo to the Docker Desktop Dashboard, select **Add Extensions** and on the **Managed** tab to see the new extension installed.\nNotice that an `UNPUBLISHED` label displays which indicates that the extension has not been installed from the Marketplace.\n\n## Update an extension \n\nTo update an extension which isn't present in the Marketplace, in a terminal type `docker extension update IMAGE[:TAG]` where the `TAG` should be different from the extension that's already installed.\n\nFor instance, if you installed an extension with `docker extension install john/my-extension:0.0.1`, you can update it by running `docker extension update john/my-extension:0.0.2`.\nGo to the Docker Desktop Dashboard to see the new extension updated.\n\n> [!NOTE]\n>\n> Extensions that aren't installed through the Marketplace don't receive update notifications from Docker Desktop.\n\n## Uninstall an extension\n\nTo uninstall an extension which is not present in the Marketplace, you can either navigate to the **Managed** tab in the Marketplace and select the **Uninstall** button, or from a terminal type `docker extension uninstall IMAGE[:TAG]`.\n","content/manuals/extensions/private-marketplace.md":"---\ndescription: How to configure and use Docker Extensions' private marketplace\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace, private, security, admin\ntitle: Configure a private marketplace for extensions\ntags: [admin]\nlinkTitle: Configure a private marketplace\nweight: 30\n---\n\n{{< summary-bar feature_name=\"Private marketplace\" >}}\n\nLearn how to configure and set up a private marketplace with a curated list of extensions for your Docker Desktop users.\n\nDocker Extensions' private marketplace is designed specifically for organizations who don’t give developers root access to their machines. It makes use of [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) so administrators have complete control over the private marketplace.\n\n## Prerequisites\n\n- [Download and install Docker Desktop](https://docs.docker.com/desktop/release-notes/).\n- You must be an administrator for your organization.\n- You have the ability to push the `extension-marketplace` folder and `admin-settings.json` file to the locations specified below through device management software such as [Jamf](https://www.jamf.com/).\n\n## Step one: Initialize the private marketplace\n\n1. Create a folder locally for the content that will be deployed to your developers’ machines:\n\n   ```console\n   $ mkdir my-marketplace\n   $ cd my-marketplace\n   ```\n\n2. Initialize the configuration files for your marketplace:\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ /Applications/Docker.app/Contents/Resources/bin/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin init\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ /opt/docker-desktop/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\nThis creates 2 files:\n\n- `admin-settings.json`, which activates the private marketplace feature once it’s applied to Docker Desktop on your developers’ machines.\n- `extensions.txt`, which determines which extensions to list in your private marketplace.\n\n> [!IMPORTANT]\n>\n> If your org is using [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will not need the `admin-settings.json` file. Delete the generated file and keep only the `extensions.txt` file.\n\n## Step two: Set the behaviour\n\nThe generated `admin-settings.json` file includes various settings you can modify.\n\n> [!IMPORTANT]\n>\n> If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will define the same settings in Docker Home instead of the `admin-settings.json` file.\n\nEach setting has a `value` that you can set, including a `locked` field that lets you lock the setting and make it unchangeable by your developers.\n\n- `extensionsEnabled` enables Docker Extensions.\n- `extensionsPrivateMarketplace` activates the private marketplace and ensures Docker Desktop connects to content defined and controlled by the administrator instead of the public Docker marketplace.\n- `onlyMarketplaceExtensions` allows or blocks developers from installing other extensions by using the command line. Teams developing new extensions must have this setting unlocked (`\"locked\": false`) to install and test extensions being developed.\n- `extensionsPrivateMarketplaceAdminContactURL` defines a contact link for developers to request new extensions in the private marketplace. If `value` is empty then no link is shown to your developers on Docker Desktop, otherwise this can be either an HTTP link or a “mailto:” link. For example,\n\n  ```json\n  \"extensionsPrivateMarketplaceAdminContactURL\": {\n    \"locked\": true,\n    \"value\": \"mailto:admin@acme.com\"\n  }\n  ```\n\nTo find out more information about the `admin-settings.json` file, see [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md).\n\n## Step three: List allowed extensions\n\nThe generated `extensions.txt` file defines the list of extensions that are available in your private marketplace.\n\nEach line in the file is an allowed extension and follows the format of `org/repo:tag`.\n\nFor example, if you want to permit the Disk Usage extension you would enter the following into your `extensions.txt` file:\n\n```console\ndocker/disk-usage-extension:0.2.8\n```\n\nIf no tag is provided, the latest tag available for the image is used. You can also comment out lines with `#` so the extension is ignored.\n\nThis list can include different types of extension images:\n\n- Extensions from the public marketplace or any public image stored in Docker Hub.\n- Extension images stored in Docker Hub as private images. Developers need to be signed in and have pull access to these images.\n- Extension images stored in a private registry. Developers need to be signed in and have pull access to these images.\n\n> [!IMPORTANT]\n>\n> Your developers can only install the version of the extension that you’ve listed.\n\n## Step four: Generate the private marketplace\n\nOnce the list in `extensions.txt` is ready, you can generate the marketplace:\n\n{{< tabs group=\"os_version\" >}}\n{{< tab name=\"Mac\" >}}\n\n```console\n$ /Applications/Docker.app/Contents/Resources/bin/extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\n```console\n# For all-user installations\n$ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin generate\n\n# For per-user installations\n$ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Linux\" >}}\n\n```console\n$ /opt/docker-desktop/extension-admin generate\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThis creates an `extension-marketplace` directory and downloads the marketplace metadata for all the allowed extensions.\n\nThe marketplace content is generated from extension image information as image labels, which is the [same format as public extensions](extensions-sdk/extensions/labels.md). It includes the extension title, description, screenshots, links, etc.\n\n## Step five: Test the private marketplace setup\n\nIt's recommended that you try the private marketplace on your Docker Desktop installation.\n\n1. Run the following command in your terminal. This command automatically copies the generated files to the location where Docker Desktop reads the configuration files. Depending on your operating system, the location is:\n\n    - Mac: `/Library/Application\\ Support/com.docker.docker`\n    - Windows: `C:\\ProgramData\\DockerDesktop`\n    - Linux: `/usr/share/docker-desktop`\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ sudo /Applications/Docker.app/Contents/Resources/bin/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows (run as admin)\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin apply\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ sudo /opt/docker-desktop/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\n2. Quit and re-open Docker Desktop. \n3. Sign in with a Docker account.\n\n> [!IMPORTANT]\n>\n> > If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), in Docker Desktop 4.59 and earlier, you must manually delete the `admin-settings.json` file created in the target folder by the `apply` command before step 2. In Docker Desktop 4.60 and later, this step is no longer necessary. \n\nWhen you select the **Extensions** tab, you should see the private marketplace listing only the extensions you have allowed in `extensions.txt`.\n\n![Extensions Private Marketplace](/assets/images/extensions-private-marketplace.webp)\n\n## Step six: Distribute the private marketplace\n\nOnce you’ve confirmed that the private marketplace configuration works, the final step is to distribute the files to the developers’ machines with the MDM software your organization uses. For example, [Jamf](https://www.jamf.com/).\n\nThe files to distribute are:\n* `admin-settings.json` (except if your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md))\n* the entire `extension-marketplace` folder and its subfolders\n\nThese files must be placed on developer's machines. Depending on your operating system, the target location is (as mentioned above):\n\n- Mac: `/Library/Application\\ Support/com.docker.docker`\n- Windows: `C:\\ProgramData\\DockerDesktop`\n- Linux: `/usr/share/docker-desktop`\n\nMake sure your developers are signed in to Docker Desktop in order for the private marketplace configuration to take effect. As an administrator, you should [enforce sign-in](/manuals/enterprise/security/enforce-sign-in/_index.md).\n\n## Feedback\n\nGive feedback or report any bugs you may find by emailing `extensions@docker.com`.\n","content/manuals/extensions/settings-feedback.md":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, feedback\ntitle: Settings and feedback for Docker Extensions\nlinkTitle: Settings and feedback\nweight: 40\n---\n\n## Settings\n\n### Turn on or turn off extensions\n\nDocker Extensions is switched off by default. To change your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Enable Docker Extensions**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> If you are an [organization owner](/manuals/admin/organization/manage/manage-a-team.md#what-is-an-organization-owner), you can turn off extensions for your users. Open the `settings-store.json` file, and set `\"extensionsEnabled\"` to `false`.\n> The `settings-store.json` file is located at:\n>   - `~/Library/Group Containers/group.com.docker/settings-store.json` on Mac\n>   - `C:\\Users\\[USERNAME]\\AppData\\Roaming\\Docker\\settings-store.json` on Windows\n>\n> This can also be done with [Hardened Docker Desktop](/manuals/enterprise/security/hardened-desktop/_index.md)\n\n### Turn on or turn off extensions not available in the Marketplace\n\nYou can install extensions through the Marketplace or through the Extensions SDK tools. You can choose to only allow published extensions. These are extensions that have been reviewed and published in the Extensions Marketplace.\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Allow only extensions distributed through the Docker Marketplace**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n### See containers created by extensions\n\nBy default, containers created by extensions are hidden from the list of containers in the Docker Desktop Dashboard and the Docker CLI. To make them visible\nupdate your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Show Docker Extensions system containers**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> Enabling extensions doesn't use computer resources (CPU / Memory) by itself.\n>\n> Specific extensions might use computer resources, depending on the features and implementation of each extension, but there is no reserved resources or usage cost associated with enabling extensions.\n\n## Submit feedback\n\nFeedback can be given to an extension author through a dedicated Slack channel or GitHub. To submit feedback about a particular extension:\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the extension you want to provide feedback on. \n3. Scroll down to the bottom of the extension's description and, depending on the \nextension, select:\n    - Support\n    - Slack\n    - Issues. You'll be sent to a page outside of Docker Desktop to submit your feedback.\n\nIf an extension doesn't provide a way for you to give feedback, contact us and we'll pass on the feedback for you. To provide feedback, select the **Give feedback** to the right of **Extensions Marketplace**.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nInstructions for AI agents working on Docker documentation.\nThis site builds https://docs.docker.com/ using Hugo.\n\n## Project structure\n\n```text\ncontent/          # Documentation source (Markdown + Hugo front matter)\n├── manuals/      # Product docs (Engine, Desktop, Hub, etc.)\n├── guides/       # Task-oriented guides\n├── reference/    # API and CLI reference\n└── includes/     # Reusable snippets\nlayouts/          # Hugo templates and shortcodes\ndata/             # YAML data files (CLI reference, etc.)\nassets/           # CSS (Tailwind v4) and JS (Alpine.js)\nstatic/           # Images, fonts\n_vendor/          # Vendored Hugo modules (read-only)\n```\n\n## URL prefix stripping\n\nThe `/manuals` prefix is stripped from published URLs:\n`content/manuals/desktop/install.md` becomes `/desktop/install/` on the live\nsite.\n\nWhen writing internal cross-references in source files, keep the `/manuals/`\nprefix in the path — Hugo requires the full source path. The stripping only\naffects the published URL, not the internal link target. Anchor links must\nexactly match the generated heading ID (Hugo lowercases and slugifies\nheadings).\n\n## Vendored content (do not edit)\n\nContent in `_vendor/` and CLI reference data in `data/cli/` are vendored\nfrom upstream repos. Content pages under `content/reference/cli/` are\ngenerated from `data/cli/` YAML. Do not edit any of these files — changes\nmust go to the source repository:\n\n| Content | Source repo |\n|---------|-------------|\n| CLI reference (`docker`, `docker build`, etc.) | docker/cli |\n| Buildx reference | docker/buildx |\n| Compose reference | docker/compose |\n| Model Runner reference | docker/model-runner |\n| Dockerfile reference | moby/buildkit |\n| Engine API reference | moby/moby |\n| AI Governance API (`content/reference/api/ai-governance/api.yaml`) | docker/governor-services (private) |\n\nIf a validation failure or broken link traces back to vendored content, note\nthe upstream repo that needs fixing. Do not attempt to fix it locally.\n\n`content/reference/api/ai-governance/api.yaml` is a verbatim copy of the\nupstream `openapi.yaml` — do not edit it by hand. Re-vendor it with\n`hack/sync-governance-api.sh`, which fetches the latest spec from the private\n`docker/governor-services` repo (using your own `gh` auth).\n\n## Writing guidelines\n\nRead and follow [STYLE.md](STYLE.md) and [COMPONENTS.md](COMPONENTS.md).\nThese contain all style rules, shortcode syntax, and front matter requirements.\n\n### Style violations to avoid\n\nEvery piece of writing must avoid these words and patterns (enforced by Vale):\n\n- Hedge words: \"simply\", \"easily\", \"just\", \"seamlessly\"\n- Meta-commentary: \"it's worth noting\", \"it's important to understand\"\n- \"allows you to\" or \"enables you to\" — use \"lets you\" or rephrase\n- \"we\" — use \"you\" or \"Docker\"\n- \"click\" — use \"select\"\n- Bold for emphasis or product names — only bold UI elements\n- Time-relative language: \"currently\", \"new\", \"recently\", \"now\"\n\n### Version-introduction notes\n\nExplicit version anchors (\"Starting with Docker Desktop version X...\") are\ndifferent from time-relative language — they mark when a feature was\nintroduced, which is permanently true.\n\n- Recent releases (~6 months): leave version callouts in place\n- Old releases: consider removing if the callout adds little value\n- When in doubt, keep the callout and flag for maintainer review\n\n### Vale gotchas\n\n- Use lowercase \"config\" in prose — `vale.Terms` flags a capital-C \"Config\"\n\n### Updating the vocabulary\n\nIf Vale flags a legitimate tech term, product name, or compound identifier\nas a misspelling, add it to `_vale/config/vocabularies/Docker/accept.txt`.\nThis is optional — only update when a real new term is missing, not to\nsilence individual violations.\n\n- Use the canonical form for case-sensitive product names (`PyTorch`,\n  `GitHub`, `Kubernetes`, `BuildKit`). `Vale.Terms` enforces that exact\n  case across the docs.\n- Use `[Aa]bcd` character-class regex for words that legitimately appear\n  in multiple cases (e.g., sentence-starting capitalization, or a name\n  that's also a generic noun). This covers spelling without enforcing\n  a single canonical form.\n- Avoid broad regex patterns — entries that match many words at once\n  (especially with `(?i)`) suppress other rule checks on every match.\n- Don't add a wrong-cased entry to silence one false positive — it\n  cascades into `Vale.Terms` violations on every correct usage.\n\n## Alpine.js patterns\n\nDo not combine Alpine's `x-show` with the HTML `hidden` attribute on the\nsame element. `x-show` toggles inline `display` styles, but `hidden` applies\n`display: none` via the user-agent stylesheet — the element stays hidden\nregardless of `x-show` state. Use `x-cloak` for pre-Alpine hiding instead.\nThe site defines `[x-cloak=\"\"] { display: none !important }` in `global.css`.\n\n## Front matter requirements\n\nEvery content page under `content/` requires:\n\n- `title:` — page title\n- `description:` — short description for SEO/previews\n- `keywords:` — list of search keywords\n\nAdditional common fields:\n\n- `linkTitle:` — sidebar label (keep under 30 chars)\n- `weight:` — ordering within a section\n\n## Hugo shortcodes\n\nShortcodes are defined in `layouts/shortcodes/`. Syntax reference is in\nCOMPONENTS.md. Wrong shortcode syntax fails silently during build but\nproduces broken HTML — always check COMPONENTS.md for correct syntax.\n\n## Commands\n\n```sh\nnpx --no-install rumdl fmt <file>  # Format Markdown before committing\nnpx prettier --write <file>        # Format non-Markdown files\nscripts/lint.sh <file>...          # Lint specific files (rumdl + Vale)\ndocker buildx bake validate        # Run all validation checks\ndocker buildx bake lint            # Markdown linting only\ndocker buildx bake vale            # Style guide checks only\ndocker buildx bake test            # HTML and link checking\n```\n\nFor incremental work, prefer `scripts/lint.sh` over the `bake` targets —\nit runs the same checks on just the files you pass, so the output stays\nscoped to your changes instead of the whole repo.\n\n### Validation in git worktrees\n\n`docker buildx bake validate` fails in git worktrees because Hugo cannot\nresolve the worktree path. Use `lint` and `vale` targets separately instead.\nNever modify `hugo.yaml` to work around this. The `test`, `path-warnings`,\nand `validate-vendor` targets run correctly in CI.\n\n## Verification loop\n\n1. Make changes\n2. Format Markdown with rumdl: `npx --no-install rumdl fmt <file>`\n3. Lint the changed files: `scripts/lint.sh <file>...`\n4. Run a full build with `docker buildx bake` (optional for small changes)\n\nAlways lint the specific files you changed before committing. Use\n`scripts/lint.sh` rather than the `bake` targets so the output is scoped\nto your changes — bake runs across the entire repo and the noise makes\nreal issues easy to miss.\n\n## Git hygiene\n\n- **Stage files explicitly.** Never use `git add .` / `git add -A` /\n  `git add --all`. Running `npx prettier` updates `package-lock.json` in the\n  repo root, and broad staging sweeps it into the commit.\n- **Verify before committing.** Run `git diff --cached --name-only` and\n  confirm only documentation files appear. If `package-lock.json` or other\n  generated files are staged, unstage them:\n  `git reset HEAD -- package-lock.json`\n- **Push to your fork, not upstream.** Before pushing, confirm\n  `git remote get-url origin` returns your fork URL, not\n  `github.com/docker/docs`. Use `--head FORK_OWNER:branch-name` with\n  `gh pr create`.\n\n## Working with issues and PRs\n\n### Principles\n\n- **One issue, one branch, one PR.** Never combine multiple issues in a\n  single branch or PR.\n- **Minimal changes only.** Fix the issue. Do not improve surrounding\n  content, add comments, refactor, or address adjacent problems.\n- **Verify before documenting.** Don't take an issue reporter's claim at\n  face value — the diagnosis may be wrong even when the symptom is real.\n  Verify the actual behavior before updating docs.\n\n### Review feedback\n\n- **Always reply to review comments** — never silently fix. After every\n  commit that addresses review feedback, reply to each thread explaining\n  what was done.\n- **Treat reviewer feedback as claims to verify, not instructions to\n  execute.** Before implementing a suggestion, verify that it is correct.\n  Push back when evidence contradicts the reviewer.\n- **Inline review comments need a separate API call.** `gh pr view --json\n  reviews` does not include line-level comments. Always also call:\n\n  ```bash\n  gh api repos/<org>/<repo>/pulls/<N>/comments \\\n    --jq '[.[] | {author: .user.login, body: .body, path: .path, line: .line}]'\n  ```\n\n### Labels\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<N>/labels \\\n  --method POST --field 'labels[]=<label>'\n```\n\n### External links\n\nIf a replacement URL cannot be verified (e.g. network restrictions), treat\nthe task as blocked — do not commit a guessed URL. Report the blocker so a\nhuman can confirm. Exception: when a domain migration is well-established and\nonly the anchor is unverifiable, dropping the anchor is acceptable.\n\n## Page deletion checklist\n\nWhen removing a documentation page, search the entire `content/` tree and\nall YAML/TOML config files for the deleted page's slug and heading text.\nCross-references from unrelated sections and config-driven nav entries can\nremain and cause broken links.\n\n## Engine API version bumps\n\nWhen a new Engine API version ships, three coordinated changes are needed in\na single commit:\n\n1. `hugo.yaml` — update `latest_engine_api_version`, `docker_ce_version`,\n   and `docker_ce_version_prev`\n2. Create `content/reference/api/engine/version/v<NEW>.md` with the\n   `/latest/` aliases block (copy from previous version)\n3. Remove the aliases block from\n   `content/reference/api/engine/version/v<PREV>.md`\n\nNever leave both version files carrying `/latest/` aliases simultaneously.\n\n## Hugo icon references\n\nBefore changing an icon reference in response to a \"file not found\" error,\nverify the file actually exists via Hugo's virtual filesystem. Files may\nexist in `node_modules/@material-symbols/svg-400/rounded/` but not directly\nin `assets/icons/`. Check both locations before concluding an icon is\nmissing.\n\n## Self-improvement\n\nAfter completing work that reveals a non-obvious pattern or repo quirk not\nalready documented here, propose an update to this file. For automated\nsessions, note the learning in a comment on the issue. For human-supervised\nsessions, discuss with the user whether to update CLAUDE.md directly.\n","isInternal":false,"tokens":2523,"sizeBytes":10645},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Tools\"\ndescription: \"Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more.\"\nkeywords: docker agent, ai agents, concepts, tools\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/tools/\n---\n\n_Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more._\n\n## How Tools Work\n\nWhen an agent needs to perform an action, it makes a **tool call**. The Docker Agent runtime executes the tool and returns the result to the agent, which can then use it to continue its work.\n\n1. Agent receives a user message\n2. Agent decides it needs to use a tool (e.g., read a file)\n3. Docker Agent executes the tool and returns the result\n4. Agent incorporates the result and responds\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing tools that have side effects (shell commands, file writes). Use `--yolo` to auto-approve all tool calls.\n\n## Built-in Tools\n\nDocker Agent ships with several built-in tools that require no external dependencies. Each is enabled by adding its `type` to the agent's `toolsets` list:\n\n| Tool | Description |\n| --- | --- |\n| [Filesystem](../../tools/filesystem/index.md) | Read, write, list, search, and navigate files and directories |\n| [Shell](../../tools/shell/index.md) | Execute shell commands synchronously |\n| [Background Jobs](../../tools/background-jobs/index.md) | Run and manage long-running shell commands |\n| [Think](../../tools/think/index.md) | Step-by-step reasoning scratchpad for planning and decision-making |\n| [Todo](../../tools/todo/index.md) | Task list management for complex multi-step workflows |\n| [Tasks](../../tools/tasks/index.md) | Persistent task database shared across sessions |\n| [Memory](../../tools/memory/index.md) | Persistent key-value storage backed by SQLite |\n| [Fetch](../../tools/fetch/index.md) | Read content from HTTP/HTTPS URLs (GET only) |\n| [Script](../../tools/script/index.md) | Define custom shell scripts as named tools |\n| [LSP](../../tools/lsp/index.md) | Connect to Language Server Protocol servers for code intelligence |\n| [API](../../tools/api/index.md) | Create custom tools that call HTTP APIs without writing code |\n| [OpenAPI](../../tools/openapi/index.md) | Generate tools from an OpenAPI 3.x document |\n| [RAG](../../tools/rag/index.md) | Retrieval-augmented generation over indexed sources |\n| [Model Picker](../../tools/model-picker/index.md) | Let the agent pick between several models per turn |\n| [User Prompt](../../tools/user-prompt/index.md) | Ask users questions and collect interactive input |\n| [Open URL](../../tools/open-url/index.md) | Open a fixed URL in the user's default browser |\n| [Transfer Task](../../tools/transfer-task/index.md) | Delegate tasks to sub-agents (auto-enabled with `sub_agents`) |\n| [Background Agents](../../tools/background-agents/index.md) | Dispatch work to sub-agents concurrently |\n| [Handoff](../../tools/handoff/index.md) | Hand the conversation off to another local agent in the same config (auto-enabled with `handoffs:`) |\n| [A2A](../../tools/a2a/index.md) | Connect to remote agents via the Agent-to-Agent protocol |\n| [MCP Catalog](../../tools/mcp-catalog/index.md) | Discover and activate remote MCP servers from the Docker MCP Catalog on demand |\n| [Git](../../tools/git/index.md) | Read-only git repository inspection |\n| [Scheduler](../../tools/scheduler/index.md) | Schedule instructions to run at a time or on a recurring interval |\n| [Webhook](../../tools/webhook/index.md) | Outbound notifications to Slack, Discord, Telegram, IFTTT, and more |\n| [Plan](../../tools/plan/index.md) | Shared persistent scratchpad for multi-agent collaboration |\n| [Session Plan](../../tools/session_plan/index.md) | Per-session plan tracker for the draft/review/execute workflow |\n| [Session Context](../../tools/session_context/index.md) | Reference a previous session as context |\n\n## MCP Tools\n\nDocker Agent supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for extending agents with external tools. There are three ways to connect MCP tools:\n\n- **Docker MCP** (recommended) — Run MCP servers in Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway). Browse the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n- **Local MCP (stdio)** — Run MCP servers as local processes communicating over stdin/stdout.\n- **Remote MCP (Streamable HTTP / SSE)** — Connect to MCP servers running on a network. See [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n```\n\nSee [Tool Config](../../configuration/tools/index.md#mcp-tools) for full MCP configuration reference.\n\n> [!TIP]\n> **See also**\n>\n> For full configuration reference, see [Tool Config](../../configuration/tools/index.md).\n","frontmatter":{"title":"Tools","description":"Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more.","keywords":"docker agent, ai agents, concepts, tools","weight":30,"canonical":"https://docs.docker.com/ai/docker-agent/concepts/tools/"},"isInternal":false,"tokens":1147,"sizeBytes":4969},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Tool Configuration\"\ndescription: \"Complete reference for configuring built-in tools, MCP tools, and Docker-based tools.\"\nkeywords: docker agent, ai agents, configuration, yaml, tool configuration\nlinkTitle: \"Tool Config\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/tools/\naliases:\n  - /ai/docker-agent/reference/toolsets/\n---\n\n_Complete reference for configuring built-in tools, MCP tools, and Docker-based tools._\n\n## Built-in Tools\n\nBuilt-in tools are included with Docker Agent and require no external dependencies. Add them to your agent's `toolsets` list by `type`. Each tool's dedicated page covers its full configuration options, available operations, and examples.\n\n| Type | Description | Page |\n| --- | --- | --- |\n| `filesystem` | Read, write, list, search, navigate | [Filesystem](../../tools/filesystem/index.md) |\n| `git` | Read-only repository inspection (status, log, branches, show, blame) | [Git](../../tools/git/index.md) |\n| `shell` | Execute shell commands synchronously | [Shell](../../tools/shell/index.md) |\n| `background_jobs` | Run and manage long-running shell commands | [Background Jobs](../../tools/background-jobs/index.md) |\n| `scheduler` | Schedule instructions to run at a time or on a recurring interval | [Scheduler](../../tools/scheduler/index.md) |\n| `think` | Reasoning scratchpad | [Think](../../tools/think/index.md) |\n| `plan` | Shared persistent scratchpad for multi-agent collaboration | [Plan](../../tools/plan/index.md) |\n| `session_plan` | Per-session markdown plan for the draft-review-execute workflow | [Session Plan](../../tools/session_plan/index.md) |\n| `session_context` | Reference a previous session as context (read-only) | [Session Context](../../tools/session_context/index.md) |\n| `todo` | Task list management | [Todo](../../tools/todo/index.md) |\n| `memory` | Persistent key-value storage (SQLite) | [Memory](../../tools/memory/index.md) |\n| `tasks` | Persistent task database shared across sessions | [Tasks](../../tools/tasks/index.md) |\n| `fetch` | HTTP `GET` requests with text/markdown/html output | [Fetch](../../tools/fetch/index.md) |\n| `script` | Custom shell scripts as tools | [Script](../../tools/script/index.md) |\n| `lsp` | Language Server Protocol integration | [LSP](../../tools/lsp/index.md) |\n| `api` | Custom HTTP API tools | [API](../../tools/api/index.md) |\n| `openapi` | Import every operation of an OpenAPI 3.x document as tools | [OpenAPI](../../tools/openapi/index.md) |\n| `rag` | Retrieval-augmented generation over indexed sources | [RAG](../../tools/rag/index.md) |\n| `model_picker` | Let the agent pick between several models per turn | [Model Picker](../../tools/model-picker/index.md) |\n| `user_prompt` | Interactive user input | [User Prompt](../../tools/user-prompt/index.md) |\n| `open_url` | Open a fixed URL in the user's default browser | [Open URL](../../tools/open-url/index.md) |\n| `transfer_task` | Delegate to sub-agents (auto-enabled) | [Transfer Task](../../tools/transfer-task/index.md) |\n| `background_agents` | Parallel sub-agent dispatch | [Background Agents](../../tools/background-agents/index.md) |\n| `webhook` | Reliable notifications to a configured destination, with retries (Slack, Discord, Telegram, IFTTT, Teams, …) | [Webhook](../../tools/webhook/index.md) |\n| `handoff` | Local conversation handoff to another agent in the same config (auto-enabled by `handoffs:`) | [Handoff](../../tools/handoff/index.md) |\n| `a2a` | A2A remote agent connection | [A2A](../../tools/a2a/index.md) |\n| `mcp_catalog` | Discover and activate remote MCP servers from the Docker MCP Catalog on demand | [MCP Catalog](../../tools/mcp-catalog/index.md) |\n\n**Example:**\n\n```yaml\ntoolsets:\n  - type: filesystem\n  - type: shell\n  - type: background_jobs\n  - type: think\n  - type: todo\n  - type: memory\n    path: ./dev.db\n```\n\n## MCP Tools\n\nExtend agents with external tools via the [Model Context Protocol](https://modelcontextprotocol.io/). For a standalone overview of the `mcp` toolset see the [MCP tool page](../../tools/mcp/index.md).\n\n> [!TIP]\n> **Reusable MCP definitions**\n>\n> Repeated MCP server definitions can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`. See [Reusable MCP Servers](../overview/index.md#reusable-mcp-servers-mcps).\n\n### Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway):\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo # web search\n  - type: mcp\n    ref: docker:github-official # GitHub integration\n```\n\nBrowse available tools at the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`)                             |\n| `tools`       | array  | Optional: only expose these tools                                |\n| `instruction` | string | Custom instructions injected into the agent's context            |\n| `config`      | any    | MCP server-specific configuration (passed during initialization) |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n\n### Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `command` | string | Command to execute the MCP server |\n| `args` | array | Command arguments |\n| `tools` | array | Optional: only expose these tools |\n| `env` | object | Environment variables (key-value pairs) |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context |\n| `version` | string | Package reference for [auto-installing](#auto-installing-tools) the command binary |\n\n### Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp-server.example.com\"\n      transport_type: \"streamable\"\n      headers:\n        Authorization: \"Bearer your-token\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: true\n    tools: [\"search_web\", \"fetch_url\"]\n```\n\n| Property                | Type    | Description                                                                                                           |\n| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |\n| `remote.url`            | string  | URL of the MCP server. Accepts `https://`, `http://`, and `unix://` (Unix domain socket) schemes.                     |\n| `remote.transport_type` | string  | `streamable` or `sse`                                                                                                 |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. `${env.VAR}` reads an environment variable; `${headers.NAME}` forwards a header from the caller's incoming request (useful when Docker Agent runs as an API server). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers.        |\n\n## Auto-Installing Tools\n\nWhen configuring MCP or LSP tools that require a binary command, Docker Agent can **automatically download and install** the command if it's not already available on your system. This uses the [aqua registry](https://github.com/aquaproj/aqua-registry) — a curated index of CLI tool packages.\n\n### How It Works\n\n1. When a toolset with a `command` is loaded, Docker Agent checks if the command is available in your `PATH`\n2. If not found, it checks the Docker Agent tools directory (`~/.cagent/tools/bin/`)\n3. If still not found, it looks up the command in the aqua registry and installs it automatically\n\n### Explicit Package Reference\n\nUse the `version` property to specify exactly which package to install:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls\n    version: \"golang/tools@v0.21.0\"\n    args: [\"mcp\"]\n  - type: lsp\n    command: rust-analyzer\n    version: \"rust-lang/rust-analyzer@2024-01-01\"\n    file_types: [\".rs\"]\n```\n\nThe format is `owner/repo` or `owner/repo@version`. When a version is omitted, the latest release is used.\n\n### Automatic Detection\n\nIf the `version` property is not set, Docker Agent tries to auto-detect the package from the command name by searching the aqua registry:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls  # auto-detected as golang/tools\n    args: [\"mcp\"]\n```\n\n### Checksum Verification\n\nWhere the aqua registry includes a checksum manifest, downloaded binaries are verified against it before installation. Verification behaviour depends on the checksum type advertised:\n\n- **Strong checksums (sha256, sha512, etc.)** — verified before the binary is installed. If the downloaded archive does not match, the install is aborted and an error is returned (fails closed).\n- **Unsupported or weak checksum types (e.g. md5, sha1)** — skipped with a warning; installation proceeds without verification.\n- **No manifest** — if no checksum is advertised in the registry entry, the binary is installed without verification.\n\n### version_overrides Resolution\n\nThe auto-installer correctly resolves **`version_overrides`** entries in the aqua registry. Many common tools (for example, `fzf`) keep their package configuration — including download URLs and checksums — under `version_overrides` rather than at the top level of their registry entry. These tools previously failed to install silently; they are now handled correctly.\n\n### Disabling Auto-Install\n\n**Per toolset** — set `version` to `\"false\"` or `\"off\"`:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: my-custom-server\n    version: \"false\"\n```\n\n**Globally** — set the `DOCKER_AGENT_AUTO_INSTALL` environment variable:\n\n```bash\nexport DOCKER_AGENT_AUTO_INSTALL=false\n```\n\n### Environment Variables\n\n| Variable                     | Default            | Description                                      |\n| ---------------------------- | ------------------ | ------------------------------------------------ |\n| `DOCKER_AGENT_AUTO_INSTALL`  | (enabled)          | Set to `false` to disable all auto-installation  |\n| `DOCKER_AGENT_TOOLS_DIR`     | `~/.cagent/tools/` | Base directory for installed tools               |\n| `GITHUB_TOKEN`               | —                  | GitHub token to raise API rate limits (optional) |\n\nInstalled binaries are placed in `~/.cagent/tools/bin/` and cached so they are only downloaded once.\n\n> [!TIP]\n> Auto-install supports both Go packages (via `go install`) and GitHub release binaries (via archive download). The aqua registry metadata determines which method is used.\n\n## Toolset Lifecycle\n\nLong-running toolsets — local MCP servers (stdio), remote MCP servers (Streamable HTTP / SSE), and LSP servers — are managed by a single supervisor that can auto-reconnect them when they crash, time out, or drop their session. The `lifecycle` block on the toolset lets you tune that supervisor per toolset. It applies to every `type: mcp` and `type: lsp` toolset.\n\nThe simplest knob is `profile`, which picks a preset:\n\n| Profile | Auto-restart | Use case |\n| --- | --- | --- |\n| `resilient` | Yes | Default. Exponential backoff on disconnect; the agent keeps running if the toolset is unavailable. Matches the historical Docker Agent behaviour. |\n| `strict` | No | Fail-fast. Marks the toolset as required. Intended for CI / headless runs where a missing dependency should be a hard error. |\n| `best-effort` | No | Single attempt, no retries. Good for experimental MCPs whose flakiness should not amplify into a restart loop. |\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; shown here for clarity\n\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: strict\n\n  - type: mcp\n    ref: docker:openbnb-airbnb\n    lifecycle:\n      profile: best-effort\n```\n\n### Tuning the defaults\n\nAny field set on `lifecycle` overrides the profile preset, so you can mix-and-match: pick a profile and only override the knobs you care about.\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: [\"docker\", \"mcp\", \"gateway\"]\n    lifecycle:\n      profile: resilient\n      max_restarts: 10        # keep trying longer than the default of 5\n      backoff:\n        initial: 500ms\n        max: 1m\n        multiplier: 2\n        jitter: 0.2           # 20% random offset to avoid thundering-herd retries\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `profile` | string | One of `resilient` (default), `strict`, `best-effort`. Picks defaults for every other field. |\n| `restart` | string | When the supervisor should reconnect after a disconnect: `never`, `on_failure` (default), or `always`. For **remote** MCP toolsets (Streamable HTTP / SSE), `on_failure` is automatically promoted to `always` so idle-timeout closes reconnect gracefully — `never` is still honored. |\n| `max_restarts` | int | Maximum consecutive restart attempts before the toolset is marked `Failed`. `0` uses the profile default (5); `-1` means unlimited. |\n| `backoff.initial` | duration | First wait between attempts (Go duration: `500ms`, `1s`, …). Default: `1s`. |\n| `backoff.max` | duration | Cap on the wait between attempts. Default: `32s`. |\n| `backoff.multiplier` | number | Multiplier applied each attempt. Default: `2`. |\n| `backoff.jitter` | number | Fraction (0..1) of the computed delay applied as a uniform random offset. `0` disables jitter (default). |\n| `required` | boolean | Marks the toolset as critical. Today this is informational; a future eager-startup phase will refuse to start the agent when a required toolset cannot reach Ready. Defaults to `true` under `strict`, `false` otherwise. |\n| `startup_timeout` | duration | Cap on the initial connect+initialize duration. Enforced since v1.94.0: on expiry the toolset stays stopped and the runtime retries on the next turn. |\n| `call_timeout` | duration | Cap on an individual tool call, including one reconnect-retry. Enforced: on expiry the call is cancelled and surfaced to the model as a tool error; cancellation is propagated to the server. `0`/unset means no timeout — opt-in only, no profile default. |\n\n> [!NOTE]\n> **`required` is not yet enforced**\n>\n> The schema validates this field and the supervisor stores it, but no code path acts on it yet. It is documented now so config files written today keep working when the planned eager-startup phase lands. Picking the `strict` profile is forward-compatible — it will start enforcing `required=true` automatically.\n\n### Inspecting and restarting toolsets at runtime\n\nThe TUI exposes the supervisor through two slash commands:\n\n- `/tools` — the unified tools dialog. Its top section lists every toolset on the current agent with its lifecycle state (`Stopped`, `Starting`, `Ready`, `Degraded`, `Restarting`, `Failed`), restart count, and last error; its bottom section lists every tool the agent can call, grouped by category. Use this to answer both \"what can the agent do?\" and \"is anything degraded?\" with one command.\n- `/toolset-restart <name>` — force the supervisor to reconnect the named toolset. Useful after completing OAuth, when a remote MCP server has been redeployed, or when an LSP like `gopls` is stuck.\n\nSee the [TUI reference](../../features/tui/index.md) for the full list of slash commands.\n\nSee [`examples/lifecycle.yaml`](https://github.com/docker/docker-agent/blob/main/examples/lifecycle.yaml) for a complete lifecycle configuration example.\n\n## TOON-Encoded Tool Outputs\n\nMany MCP servers return verbose JSON responses that consume a lot of context budget. The `toon` field on a toolset transparently re-encodes matching tools' JSON output as [TOON](https://github.com/alpkeskin/gotoon) — a compact, model-friendly key/value format — before the result is shown to the model.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"          # toonify every tool from this MCP server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `toon`   | string | Comma-delimited list of regular expressions matching tool names whose JSON output should be re-encoded as TOON. Non-JSON outputs and non-matching tools are passed through untouched. |\n\nWhen a tool's output is not valid JSON, it is returned unchanged — TOON encoding is best-effort and never breaks tools that emit plain text.\n\n> [!NOTE]\n> **When to use TOON**\n>\n> TOON typically yields 30-60% smaller payloads than equivalent JSON for MCP tools that return arrays of records (issue lists, search results, file listings, …). It works best when the schema is regular; one-off responses with deeply nested or heterogeneous shapes may benefit less.\n\n## Per-Toolset Model Routing\n\nThe `model` field on a toolset overrides which LLM is invoked for the **next turn** after a tool from that toolset returns — letting you process simple tool results (file reads, knowledge-base lookups, shell stdout) with a cheaper or faster model while keeping the agent's primary model for reasoning.\n\n```yaml\nmodels:\n  primary:\n    provider: anthropic\n    model: claude-sonnet-4-5\n  fast:\n    provider: anthropic\n    model: claude-haiku-4-5\n\nagents:\n  root:\n    model: primary\n    toolsets:\n      - type: filesystem\n        model: fast            # process file reads with the fast model\n      - type: shell\n        model: fast            # ditto for shell stdout\n      - type: mcp\n        ref: docker:github-official\n        model: openai/gpt-4o-mini  # inline provider/model also works\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `model`  | string | Model used for the LLM turn that processes tool results from this toolset. Either a name from the `models:` section or an inline `provider/model` (e.g. `openai/gpt-4o-mini`). The override is **one-shot**: subsequent turns return to the agent's primary model. |\n\nWhen multiple tool calls in a single turn come from toolsets with different `model` overrides, the runtime picks the override of the **first** tool call that has one set. See [`examples/per_tool_model_routing.yaml`](https://github.com/docker/docker-agent/blob/main/examples/per_tool_model_routing.yaml) for a complete configuration.\n\n## Tool Filtering\n\nToolsets may expose many tools. Use the `tools` property to whitelist only the ones your agent needs. This works for any toolset type — not just MCP:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n  - type: filesystem\n    tools: [\"read_file\", \"search_files_content\"]\n  - type: shell\n    tools: [\"shell\"]\n```\n\n> [!TIP]\n> Filtering tools improves agent performance — fewer tools means less confusion for the model about which tool to use.\n\n## Tool Instructions\n\nAdd context-specific instructions that get injected when a toolset is loaded:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\nBy default, the `instruction:` field **replaces** the toolset's built-in instructions (if any). To keep the built-in guidance and add your own rules on top, include the `{ORIGINAL_INSTRUCTIONS}` placeholder anywhere in your instruction text. At runtime it expands to the toolset's default instructions:\n\n```yaml\ntoolsets:\n  # Enrich: keep built-in instructions, then add your own rules\n  - type: filesystem\n    instruction: |\n      {ORIGINAL_INSTRUCTIONS}\n\n      ## Project-specific rules\n      - Never modify files outside the `src/` directory.\n      - Always create a backup before overwriting a file.\n\n  # Enrich: prepend your rules before the built-in instructions\n  - type: shell\n    instruction: |\n      Important: only run commands inside the project root.\n      {ORIGINAL_INSTRUCTIONS}\n\n  # Replace: omit the placeholder to discard built-in instructions entirely\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Only read GitHub issues. Never create, edit, or close anything.\n```\n\nThree patterns at a glance:\n\n| Pattern | Description |\n| --- | --- |\n| `{ORIGINAL_INSTRUCTIONS}` then your text | Append your rules after the defaults |\n| Your text then `{ORIGINAL_INSTRUCTIONS}` | Prepend your rules before the defaults |\n| No placeholder | Replace the defaults entirely |\n\nSee [`examples/toolset_instructions.yaml`](https://github.com/docker/docker-agent/blob/main/examples/toolset_instructions.yaml) for a complete example.\n\n## Deferred Tool Loading\n\nLoad tools on-demand to speed up agent startup. When a toolset is deferred, its tools are registered lazily — the tool server process is not started until the agent first calls one of its tools. This is useful for large toolsets (e.g., an MCP server with hundreds of tools) where startup time matters.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  - type: mcp\n    ref: docker:slack\n    defer: true\n  - type: filesystem\n```\n\nOr defer specific tools within a toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer:\n      - \"list_issues\"\n      - \"search_repos\"\n```\n\nWhen `defer` is a list of tool names, only those specific tools are deferred; all other tools in the toolset load eagerly. Setting `defer: true` defers the entire toolset.\n\n### Tool Discovery with `search_tool`\n\nWhen an entire toolset is deferred (`defer: true`), the deferred toolset exposes two built-in tools to the agent:\n\n- **`search_tool`** — Discover available deferred tools by keyword. The search uses **fuzzy matching** against both tool names and descriptions: all characters of the query must appear in the target string in order (but not necessarily adjacently), so a query like `\"crfil\"` matches `\"create_file\"`. Returns a list of matching tool names with descriptions.\n- **`add_tool`** — Activate a discovered tool by name so it becomes available for use.\n\nThese tools let the agent browse a large toolset on-demand without activating every tool upfront.\n\nSee [`examples/deferred.yaml`](https://github.com/docker/docker-agent/blob/main/examples/deferred.yaml) for a complete example.\n\n## Combined Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Built-in tools\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n      - type: memory\n        path: ./dev.db\n      - type: user_prompt\n      # LSP for code intelligence\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      # Custom scripts\n      - type: script\n        shell:\n          run_tests:\n            description: Run the test suite\n            cmd: task test\n          lint:\n            description: Run the linter\n            cmd: task lint\n      # Custom API tool\n      - type: api\n        api_config:\n          name: get_status\n          method: GET\n          endpoint: \"https://api.example.com/status\"\n          instruction: Check service health\n      # Docker MCP tools\n      - type: mcp\n        ref: docker:github-official\n        tools: [\"list_issues\", \"create_issue\"]\n      - type: mcp\n        ref: docker:duckduckgo\n      # Remote MCP\n      - type: mcp\n        remote:\n          url: \"https://internal-api.example.com/mcp\"\n          transport_type: \"streamable\"\n          headers:\n            Authorization: \"Bearer ${env.INTERNAL_TOKEN}\"\n```\n\n> [!WARNING]\n> **Toolset Order Matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins. Order your toolsets intentionally.\n","frontmatter":{"title":"Tool Configuration","description":"Complete reference for configuring built-in tools, MCP tools, and Docker-based tools.","keywords":"docker agent, ai agents, configuration, yaml, tool configuration","linkTitle":"Tool Config","weight":50,"canonical":"https://docs.docker.com/ai/docker-agent/configuration/tools/","aliases":["/ai/docker-agent/reference/toolsets/"]},"isInternal":false,"tokens":5830,"sizeBytes":24996},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/features/skills/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/features/skills/index.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Skills\"\ndescription: \"Skills provide specialized instructions that agents can load on demand when a task matches a skill's description.\"\nkeywords: docker agent, ai agents, features, skills\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/features/skills/\n---\n\n_Skills provide specialized instructions that agents can load on demand when a task matches a skill's description._\n\n## How Skills Work\n\n1. Docker Agent scans standard directories for `SKILL.md` files\n2. Skill metadata (name, description) is injected into the agent's system prompt\n3. When a user request matches a skill, the agent reads the full instructions\n4. The agent follows the skill's detailed instructions to complete the task\n\n## Enabling Skills\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills: true\n    toolsets:\n      - type: filesystem # required for reading skill files\n```\n\n> [!TIP]\n> Skills are perfect for encoding team-specific workflows (PR review, deployment, coding standards) that apply across projects.\n\n## Filtering Skills\n\nThe `skills` field also accepts a list, letting you restrict the agent to a specific subset of skills instead of exposing every discovered one. List items are classified automatically:\n\n- `\"local\"` or any `http://` / `https://` URL → a **source** to load skills from\n- any other string → the **name** of a skill to include\n\nWhen only names are given, local sources are used by default.\n\n```yaml\nagents:\n  # Load every discovered local skill (same as `skills: true`).\n  full:\n    skills: true\n\n  # Load local skills, but only expose \"commit\" and \"poem\".\n  scoped:\n    skills:\n      - commit\n      - poem\n\n  # Combine an explicit source with a name filter.\n  remote_filtered:\n    skills:\n      - https://skills.example.com\n      - commit\n\n  # Disable skills entirely.\n  none:\n    skills: false\n```\n\nA name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored.\n\n## Inline Skills\n\nInstead of (or alongside) loading skills from files and URLs, you can define skills directly in the agent config. An inline skill is a mapping item in the `skills` list, freely mixed with the string items above:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills:\n      - name: changelog\n        description: Write a concise changelog entry from a diff or description.\n        instructions: |\n          Produce a single changelog entry in Keep a Changelog style.\n          Pick the right category (Added, Changed, Fixed, Removed) and write\n          one imperative sentence summarising the user-visible change.\n\n      # A fork-mode inline skill runs in an isolated sub-agent.\n      - name: triage\n        description: Triage a bug report in an isolated context.\n        context: fork\n        instructions: |\n          Restate the problem, list likely root causes most-probable-first,\n          and propose the smallest reproduction and next concrete action.\n\n      # Inline skills mix freely with sources and name filters.\n      - local\n    toolsets:\n      - type: filesystem\n```\n\nInline skills carry their body in the config itself, so they need no `SKILL.md` file and require no filesystem source. They are **always exposed** — the name filter only applies to file- and URL-based skills. Because inline skills travel inside the agent YAML, they also work in `--sandbox` mode without any kit staging, and they can be shared with the agent via `share push`.\n\n### Inline Skill Fields\n\n| Field           | Required | Description                                                                |\n| --------------- | -------- | -------------------------------------------------------------------------- |\n| `name`          | Yes      | Skill identifier used by `read_skill` / `run_skill` and the `/<name>` command |\n| `description`   | Yes      | Short description shown to the agent for skill matching                    |\n| `instructions`  | Yes      | The skill body (what a `SKILL.md` would contain below its frontmatter)     |\n| `context`       | No       | Set to `fork` to run the skill as an isolated sub-agent                    |\n| `model`         | No       | Override the model used while running a fork-mode skill                    |\n| `allowed_tools` | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (glob or exact). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`      | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session on top of the inherited tools. |\n\n> [!NOTE]\n> **Inline vs. file-based skills**\n>\n> Inline skills support the subset of the SKILL.md format that fits in YAML. They cannot bundle supporting files (no `read_skill_file`) or use `` !`command` `` expansion. For skills that need bundled resources or executable helpers, use a `SKILL.md` directory instead.\n\n## SKILL.md Format\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\nlicense: Apache-2.0\nmetadata:\n  author: my-org\n  version: \"1.0\"\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Minimize image size by using slim base images\n4. Follow security best practices (non-root user, etc.)\n```\n\n### Frontmatter Fields\n\n| Field            | Required | Description                                                                 |\n| ---------------- | -------- | --------------------------------------------------------------------------- |\n| `name`           | Yes      | Unique skill identifier                                                     |\n| `description`    | Yes      | Short description shown to the agent for skill matching                     |\n| `context`        | No       | Set to `fork` to run the skill as an isolated sub-agent (see below)         |\n| `model`          | No       | Override the model used while running the skill as a sub-agent (fork only)  |\n| `allowed-tools`  | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (YAML list or comma-separated string). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`       | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session (YAML list or comma-separated string). |\n| `license`        | No       | License identifier (e.g. `Apache-2.0`)                                      |\n| `compatibility`  | No       | Free-text compatibility notes                                               |\n| `metadata`       | No       | Arbitrary key-value pairs (e.g. `author`, `version`)                        |\n\n## Running a Skill as a Sub-Agent\n\nBy default, when an agent invokes a skill it reads the instructions inline into its own conversation. For complex, multi-step skills this can consume a large portion of the agent's context window and pollute the parent conversation with intermediate tool calls.\n\nAdding `context: fork` to the SKILL.md frontmatter tells the agent to run the skill in an **isolated sub-agent** instead:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\n---\n\n# Bump Dependencies\n\n1. List outdated deps\n2. Update each one, run tests, commit or revert\n3. Produce a summary table\n```\n\nWhen the agent encounters a task that matches a `context: fork` skill, it uses the `run_skill` tool instead of `read_skill`. This:\n\n- **Spawns a child session** with the skill content as the system prompt and the caller's task as the user message\n- **Isolates the context window** — the sub-agent has its own conversation history, so lengthy tool-call chains don't eat into the parent's token budget\n- **Folds the result** — only the sub-agent's final answer is returned to the parent as the tool result\n- **Inherits the parent's model and tools** — the sub-agent can use all tools available to the parent agent (scope this with `allowed_tools` / `toolsets`, see [Scoping a fork skill's tools](#scoping-a-fork-skills-tools))\n\n> [!TIP]\n> **When to use context: fork**\n>\n> Use `context: fork` for skills that involve many steps, heavy tool usage, or that should not clutter the main conversation — for example dependency bumping, large refactors, or code generation pipelines.\n\n### Overriding the model for a fork skill\n\nFork skills can declare a `model` field in their frontmatter to use a\ndifferent model than the parent agent for the duration of the sub-session.\nThis is useful when a skill is best handled by a faster, cheaper, or more\nspecialised model — for example a powerful reasoning model for refactors,\nor a fast model for routine bookkeeping work. The override only applies\nwhile the skill is running; the parent agent keeps its own model.\n\nThe `model` value accepts either a named model from the agent config or\nan inline `provider/model` reference (and the same comma-separated alloy\nsyntax as the rest of the agent config):\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\nmodel: openai/gpt-4o-mini\n---\n\n# Bump Dependencies\n\n1. ...\n```\n\nIf the model reference cannot be resolved (unknown name, missing\ncredentials, runtime not configured for model switching, …) the skill\nfalls back to the agent's currently-active model (its configured\ndefault, or any override the user previously set via the model picker)\nand a warning is logged.\n\nWhen the skill completes, the agent's previous model is restored — but\nonly if no one else changed the model in the meantime. If the user\nswitches the model via the TUI model picker while the fork skill is\nrunning, their choice is preserved (the deferred restore becomes a\nno-op).\n\n### Scoping a fork skill's tools\n\nBy default a fork skill inherits the parent agent's entire tool set. Two\noptional fields let you scope what the sub-session can use. Both apply\n**only to fork-mode skills** and work the same whether the skill is\ninline or loaded from a `SKILL.md` file.\n\n`allowed_tools` (frontmatter: `allowed-tools`) is an **allow-list** over\nthe inherited tools: only tools whose names match an entry are kept,\neverything else is hidden from the sub-session. Entries support glob\npatterns (e.g. `read_*`) and otherwise match exactly. This is the\nClaude-Code-compatible `allowed-tools` field, now enforced for fork\nskills rather than merely recorded.\n\n`toolsets` references reusable [top-level toolsets](../../configuration/overview/index.md#reusable-toolsets-toolsets)\nby name. The referenced toolsets are exposed in the sub-session **in\naddition to** the inherited tools, and they bypass the `allowed_tools`\nfilter (the skill explicitly asked for them).\n\n```yaml\ntoolsets:\n  web:\n    type: fetch\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    toolsets:\n      - type: filesystem\n      - type: shell\n    skills:\n      # Inherits the parent tools but is restricted to read-only filesystem\n      # access while it runs — shell and write tools are hidden.\n      - name: audit\n        description: Review the repository layout without modifying anything.\n        context: fork\n        allowed_tools:\n          - read_file\n          - list_directory\n          - directory_tree\n        instructions: Inspect the repository structure and summarise it.\n\n      # Brings in the top-level `web` toolset on top of the parent's tools.\n      - name: research\n        description: Research a topic using web fetches in an isolated context.\n        context: fork\n        toolsets:\n          - web\n        instructions: Research the requested topic and summarise with links.\n```\n\nThe equivalent in a `SKILL.md` file uses frontmatter lists:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: research\ndescription: Research a topic using web fetches\ncontext: fork\nallowed-tools:\n  - fetch\ntoolsets:\n  - web\n---\n```\n\n> [!NOTE]\n> **Fork only**\n>\n> Both fields are rejected by config validation when set on a non-fork skill, and a `toolsets` entry that doesn't resolve to a top-level toolset is a load-time error.\n\n## Search Paths\n\nSkills are discovered from these locations (later overrides earlier):\n\n### Global\n\n| Path                | Search Type                             |\n| ------------------- | --------------------------------------- |\n| `~/.codex/skills/`  | Recursive (searches all subdirectories) |\n| `~/.claude/skills/` | Flat (immediate children only)          |\n| `~/.agents/skills/` | Recursive (searches all subdirectories) |\n\n### Project (from git root to current directory)\n\n| Path              | Search Type                                |\n| ----------------- | ------------------------------------------ |\n| `.claude/skills/` | Flat (cwd only)                            |\n| `.github/skills/` | Flat (each directory from git root to cwd) |\n| `.agents/skills/` | Flat (each directory from git root to cwd) |\n\n## Invoking Skills\n\nSkills can be invoked in multiple ways:\n\n- **Automatic:** The agent detects when your request matches a skill's description and loads it automatically\n- **Explicit:** Reference the skill name in your prompt: \"Use the create-dockerfile skill to...\"\n- **Slash command:** Use `/{skill-name}` to invoke a skill directly\n\n```bash\n# In the TUI, invoke skill directly:\n/create-dockerfile\n\n# Or mention it in your message:\n\"Create a dockerfile for my Python app (use the create-dockerfile skill)\"\n```\n\n## Precedence\n\nWhen multiple skills share the same name:\n\n1. Global skills load first\n2. Project skills load next, from git root toward current directory\n3. Skills closer to the current directory override those further away\n4. At the same directory level, `.agents/skills/` overrides `.github/skills/`\n\n## Skills in Sandbox Mode\n\nWhen you run an agent with [`--sandbox`](../../configuration/sandbox/index.md), the sandbox VM has its own filesystem with no access to your host's skill directories. Docker Agent handles this transparently via the [auto-kit](../../configuration/sandbox/index.md#auto-kit): every discovered local skill is staged into a per-agent kit on the host, run through best-effort secret redaction (see the [auto-kit](../../configuration/sandbox/index.md#secret-redaction) docs), and bind-mounted read-only into the sandbox so the agent sees the same skills inside the VM as on the host. No configuration is required — use `--no-kit` only if you explicitly want to run the sandbox without any host skills.\n\n## Creating a Skill\n\n```bash\n# Create the skill directory\n$ mkdir -p ~/.agents/skills/create-dockerfile\n\n# Write the SKILL.md file\n$ cat > ~/.agents/skills/create-dockerfile/SKILL.md << 'EOF'\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Use slim base images to minimize size\n4. Run as non-root user for security\nEOF\n```\n\nThe skill will automatically be available to any agent with skills enabled (`skills: true`, or a list that targets its name — see [Filtering Skills](#filtering-skills)).\n\n> [!NOTE]\n> **See also**\n>\n> Skills are enabled in the [Agent Config](../../configuration/agents/index.md) with the `skills` property (boolean or list). For tool-based capabilities, see [Tools](../../concepts/tools/index.md).\n>\n> Example configs: [`examples/skills_inline.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_inline.yaml) (inline skill definition), [`examples/skills_fork_toolsets.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_fork_toolsets.yaml) (scoping a fork skill's tools), [`examples/skills_filter.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_filter.yaml) (filtering which skills load).\n","frontmatter":{"title":"Skills","description":"Skills provide specialized instructions that agents can load on demand when a task matches a skill's description.","keywords":"docker agent, ai agents, features, skills","weight":110,"canonical":"https://docs.docker.com/ai/docker-agent/features/skills/"},"isInternal":false,"tokens":3601,"sizeBytes":16205},{"name":"_index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/_index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Built-in Tools\"\ndescription: \"Built-in toolsets agents can use out of the box.\"\nweight: 40\n---\n","frontmatter":{"title":"Built-in Tools","description":"Built-in toolsets agents can use out of the box.","weight":40},"isInternal":false,"tokens":29,"sizeBytes":107},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md","title":"A2a Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"A2A Tool\"\ndescription: \"Connect to remote agents via the Agent-to-Agent protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, a2a tool\nlinkTitle: \"A2A\"\nweight: 60\ncanonical: https://docs.docker.com/ai/docker-agent/tools/a2a/\n---\n\n_Connect to remote agents via the Agent-to-Agent protocol._\n\n## Overview\n\nThe A2A tool connects to a remote agent exposed over the A2A (Agent-to-Agent) protocol. Unlike [`handoff`](../handoff/index.md), which only targets local agents declared in the same config, `a2a` reaches out to an agent running on the network.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: a2a\n    url: \"http://localhost:8080/a2a\"\n    # Optional: custom tool name (defaults to a sanitized form of the URL / agent card name)\n    name: research_agent\n    # Optional: custom HTTP headers (typically for auth)\n    headers:\n      Authorization: \"Bearer ${env.A2A_TOKEN}\"\n      X-Tenant: \"acme\"\n```\n\nThe `Authorization` header shown above authenticates to endpoints served with `docker agent serve a2a --auth-token`.\n\n## Properties\n\n| Property   | Type             | Required | Description                                                                                              |\n| ---------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `url`      | string           | ✓        | A2A server endpoint URL (must include scheme).                                                           |\n| `name`     | string           | ✗        | Tool name registered for the remote agent. Defaults to a name derived from the server's agent card.     |\n| `headers`  | map\\[string\\]string | ✗     | Extra HTTP headers sent with every request (useful for `Authorization`, tenant selection, tracing, \\u2026). |\n\n> [!TIP]\n> **See also**\n>\n> For full details on the A2A protocol and serving agents as A2A endpoints, see [A2A Protocol](../../features/a2a/index.md).\n","frontmatter":{"title":"A2A Tool","description":"Connect to remote agents via the Agent-to-Agent protocol.","keywords":"docker agent, ai agents, tools, toolsets, a2a tool","linkTitle":"A2A","weight":60,"canonical":"https://docs.docker.com/ai/docker-agent/tools/a2a/"},"isInternal":false,"tokens":440,"sizeBytes":1973},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/api/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/api/index.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"API Tool\"\ndescription: \"Create custom tools that call HTTP APIs.\"\nkeywords: docker agent, ai agents, tools, toolsets, api tool\nlinkTitle: \"API\"\nweight: 240\ncanonical: https://docs.docker.com/ai/docker-agent/tools/api/\n---\n\n_Create custom tools that call HTTP APIs._\n\n## Overview\n\nThe API tool type lets you define custom tools that make HTTP requests to external APIs. This is useful for integrating agents with REST APIs, webhooks, or any HTTP-based service without writing code.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Integrating with REST APIs that don't have an MCP server\n> - Simple HTTP operations (GET, POST)\n> - Quick prototyping before building a full MCP server\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant with API access\n    instruction: You can look up weather information.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_weather\n          method: GET\n          endpoint: \"https://api.weather.example/v1/current?city=${city}\"\n          instruction: Get current weather for a city\n          args:\n            city:\n              type: string\n              description: City name to get weather for\n          required: [\"city\"]\n          headers:\n            Authorization: \"Bearer ${env.WEATHER_API_KEY}\"\n```\n\n## Properties\n\nThe `api` toolset accepts the following toolset-level fields in addition to the `api_config` block:\n\n| Property            | Type    | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `api_config`        | object  | ✓        | The HTTP tool definition. See the table below.                                                                                                                                                                                                                    |\n| `timeout`           | int     | ✗        | HTTP client timeout in seconds (default: `30`). Applies to every call the generated tool makes.                                                                                                                                                                   |\n| `allow_private_ips` | boolean | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the configured endpoint legitimately targets internal services. See [Reaching internal services](#reaching-internal-services). |\n\n### `api_config`\n\n| Property        | Type   | Required | Description                                      |\n| --------------- | ------ | -------- | ------------------------------------------------ |\n| `name`          | string | ✓        | Tool name (how the agent references it)          |\n| `method`        | string | ✓        | HTTP method: `GET` or `POST`                     |\n| `endpoint`      | string | ✓        | URL endpoint (supports `${param}` interpolation) |\n| `instruction`   | string | ✗        | Description shown to the agent                   |\n| `args`          | object | ✗        | Parameter definitions (JSON Schema properties)   |\n| `required`      | array  | ✗        | List of required parameter names                 |\n| `headers`       | object | ✗        | HTTP headers to include. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request, useful when docker agent is itself exposed as an HTTP server). |\n| `output_schema` | object | ✗        | JSON Schema for the response. Used by MCP / Code Mode consumers; tool responses are still returned to the model as raw strings.                                                                                          |\n\n## HTTP Methods\n\n### GET Requests\n\nFor GET requests, parameters are interpolated into the URL:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: search_users\n      method: GET\n      endpoint: \"https://api.example.com/users?q=${query}&limit=${limit}\"\n      instruction: Search for users by name\n      args:\n        query:\n          type: string\n          description: Search query\n        limit:\n          type: integer\n          description: Maximum results (default 10)\n      required: [\"query\"]\n```\n\n### POST Requests\n\nFor POST requests, parameters are sent as JSON in the request body:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: create_task\n      method: POST\n      endpoint: \"https://api.example.com/tasks\"\n      instruction: Create a new task\n      args:\n        title:\n          type: string\n          description: Task title\n        description:\n          type: string\n          description: Task description\n        priority:\n          type: string\n          enum: [\"low\", \"medium\", \"high\"]\n          description: Task priority\n      required: [\"title\"]\n      headers:\n        Content-Type: \"application/json\"\n        Authorization: \"Bearer ${env.API_TOKEN}\"\n```\n\n## URL Interpolation\n\nUse `${param}` syntax to insert parameter values into URLs:\n\n```yaml\nendpoint: \"https://api.example.com/users/${user_id}/posts/${post_id}\"\n```\n\nParameter values are inserted as strings by the template expansion. Add URL encoding in the template when needed (for example, `${encodeURIComponent(city)}`).\n\n## Headers\n\nHeaders can include environment variables:\n\n```yaml\nheaders:\n  Authorization: \"Bearer ${env.API_KEY}\"\n  X-Custom-Header: \"static-value\"\n  Content-Type: \"application/json\"\n```\n\n## Output Schema\n\nOptionally document the expected response format:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: get_user\n      method: GET\n      endpoint: \"https://api.example.com/users/${id}\"\n      instruction: Get user details by ID\n      args:\n        id:\n          type: string\n          description: User ID\n      required: [\"id\"]\n      output_schema:\n        type: object\n        properties:\n          id:\n            type: string\n          name:\n            type: string\n          email:\n            type: string\n          created_at:\n            type: string\n```\n\n## Example: GitHub API\n\n```yaml\nagents:\n  github_assistant:\n    model: openai/gpt-4o\n    description: Assistant that can query GitHub\n    instruction: You can look up GitHub repositories and users.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_repo\n          method: GET\n          endpoint: \"https://api.github.com/repos/${owner}/${repo}\"\n          instruction: Get information about a GitHub repository\n          args:\n            owner:\n              type: string\n              description: Repository owner (user or org)\n            repo:\n              type: string\n              description: Repository name\n          required: [\"owner\", \"repo\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n            Authorization: \"Bearer ${env.GITHUB_TOKEN}\"\n\n      - type: api\n        api_config:\n          name: get_user\n          method: GET\n          endpoint: \"https://api.github.com/users/${username}\"\n          instruction: Get information about a GitHub user\n          args:\n            username:\n              type: string\n              description: GitHub username\n          required: [\"username\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n```\n\n## Limitations\n\n- Only supports GET and POST methods\n- Response body is limited to 1MB\n- Default 30-second timeout per request (override with the `timeout` field)\n- Only HTTP and HTTPS URLs are supported\n- No support for file uploads or multipart forms\n- By default, requests to non-public IP ranges (loopback, RFC1918, link-local, the cloud-metadata endpoint, multicast, the unspecified address) are refused at dial time — even when DNS for an otherwise-public host resolves there. Set `allow_private_ips: true` to disable that check.\n\n## Reaching internal services\n\n```yaml\ntoolsets:\n  - type: api\n    timeout: 60\n    allow_private_ips: true\n    api_config:\n      name: get_local_status\n      method: GET\n      endpoint: \"http://localhost:8080/health\"\n      instruction: Check the local service health\n```\n\n> [!WARNING]\n> **SSRF**\n>\n> Setting `allow_private_ips: true` re-exposes the SSRF surface for this tool. Only enable it when the configured `endpoint` is a trusted internal service — a prompt-injected agent cannot redirect the call elsewhere because the endpoint is fixed in config, but redirects from the configured host can still reach unexpected places.\n\n> [!TIP]\n> **For Complex APIs**\n>\n> For APIs that need authentication flows, pagination, or complex request/response handling, consider using an MCP server instead. The API tool is best for simple, stateless HTTP operations.\n\n> [!WARNING]\n> **Security**\n>\n> API keys and tokens in headers are visible in debug logs. Use environment variables (`${env.VAR}`) rather than hardcoding secrets in configuration files.\n","frontmatter":{"title":"API Tool","description":"Create custom tools that call HTTP APIs.","keywords":"docker agent, ai agents, tools, toolsets, api tool","linkTitle":"API","weight":240,"canonical":"https://docs.docker.com/ai/docker-agent/tools/api/"},"isInternal":false,"tokens":1870,"sizeBytes":9433},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md","title":"Background-agents Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Background Agents Tool\"\ndescription: \"Dispatch work to sub-agents concurrently and collect results asynchronously.\"\nkeywords: docker agent, ai agents, tools, toolsets, background agents tool\nlinkTitle: \"Background Agents\"\nweight: 90\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-agents/\n---\n\n_Dispatch work to sub-agents concurrently and collect results asynchronously._\n\n## Overview\n\nThe background agents tool lets an orchestrator dispatch work to sub-agents concurrently and collect results asynchronously. Unlike [transfer_task](../transfer-task/index.md) (which blocks until the sub-agent finishes), background agent tasks run in parallel — the orchestrator can start several tasks, do other work, and check on them later.\n\n## Available Tools\n\n| Tool                     | Description                                                     |\n| ------------------------ | --------------------------------------------------------------- |\n| `run_background_agent`   | Start a sub-agent task in the background; returns a task ID     |\n| `list_background_agents` | List all background tasks with their status and runtime         |\n| `view_background_agent`  | View live output or final result of a task by ID                |\n| `stop_background_agent`  | Cancel a running task by ID                                     |\n\n### `run_background_agent` parameters\n\n| Parameter         | Type   | Required | Description                                                                 |\n| ----------------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to run. Must be listed under the caller's `sub_agents`. |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.        |\n| `expected_output` | string | ✗        | Optional description of the result format the caller expects.               |\n\n`run_background_agent` returns a **task ID** string. Tools run by the sub-agent inherit the parent session's permissions. Because background tasks run non-interactively, any tool call that would normally prompt the user for approval will be automatically denied. To allow background agents to run mutating tools, you must explicitly approve them in the parent session (e.g. via YOLO mode or explicit allow rules).\n\nBackground delegation shares the same runtime guards as `transfer_task`: delegation cycles are rejected and chains are capped at 10 nested delegations. See [Delegation Limits](../transfer-task/index.md#delegation-limits).\n\n### `view_background_agent` and `stop_background_agent` parameters\n\n| Parameter | Type   | Required | Description                                                    |\n| --------- | ------ | -------- | -------------------------------------------------------------- |\n| `task_id` | string | ✓        | Task ID returned by `run_background_agent` or `list_background_agents`. |\n\n`list_background_agents` takes no parameters.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: background_agents\n```\n\nNo configuration options. Requires the agent to have `sub_agents` configured so the background tasks have agents to dispatch to.\n\n## Example\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Orchestrates parallel research\n    instruction: Fan out research tasks and synthesize results.\n    sub_agents: [researcher]\n    toolsets:\n      - type: background_agents\n      - type: think\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Research topics thoroughly.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\n> [!TIP]\n> **When to Use**\n>\n> Use `background_agents` when your orchestrator needs to fan out work to multiple specialists in parallel — for example, researching several topics simultaneously or running independent code analyses side by side.\n\nIn the TUI, each background task's token usage is accounted for live: the sidebar's Agents panel shows the sub-agent's context usage percentage on its roster row, the Agent Inspector shows its exact token counts, and the task's cost joins the session total.\n\n## Using Harness Sub-Agents\n\nBackground agents work equally well with [harness-backed sub-agents](../../features/harnesses/index.md) — sub-agents driven by external coding CLIs such as Claude Code or Codex. This lets you dispatch multiple independent coding tasks in parallel:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Orchestrator that fans out coding tasks\n    instruction: |\n      Dispatch the frontend and backend tasks in parallel,\n      then collect results and produce a summary.\n    sub_agents:\n      - claude-coder\n      - codex-coder\n    toolsets:\n      - type: background_agents\n\n  claude-coder:\n    description: Frontend specialist (Claude Code)\n    harness:\n      type: claude-code\n      effort: medium\n\n  codex-coder:\n    description: Backend specialist (Codex)\n    harness:\n      type: codex\n```\n\nThe orchestrator calls `run_background_agent` for each coding task, then uses `list_background_agents` and `view_background_agent` to collect results when they finish.\n\n> [!NOTE]\n> **Harness toolsets are ignored**\n>\n> Harness agents use the external CLI's own tools — any `toolsets:` configured on the harness agent are silently ignored. See [Coding Harnesses](../../features/harnesses/index.md) for details and caveats.\n\nSee [`examples/coding_harness_background_agents.yaml`](https://github.com/docker/docker-agent/blob/main/examples/coding_harness_background_agents.yaml) for a complete configuration.\n","frontmatter":{"title":"Background Agents Tool","description":"Dispatch work to sub-agents concurrently and collect results asynchronously.","keywords":"docker agent, ai agents, tools, toolsets, background agents tool","linkTitle":"Background Agents","weight":90,"canonical":"https://docs.docker.com/ai/docker-agent/tools/background-agents/"},"isInternal":false,"tokens":1149,"sizeBytes":5690},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md","title":"Background-jobs Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Background Jobs Tool\"\ndescription: \"Run and manage long-running shell commands.\"\nkeywords: docker agent, ai agents, tools, toolsets, background jobs, shell\nlinkTitle: \"Background Jobs\"\nweight: 21\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-jobs/\n---\n\n_Run and manage long-running shell commands._\n\n## Overview\n\nThe `background_jobs` toolset starts shell commands that should keep running while the agent continues with other work, such as local servers, file watchers, long builds, or test suites. It returns a job ID immediately, captures combined stdout/stderr up to 10 MB per job, and terminates all running jobs when the agent session ends.\n\nUse the [`shell`](../shell/index.md) toolset for short synchronous commands. Add both toolsets when an agent needs both synchronous commands and long-running processes.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n  - type: background_jobs\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                                                                          |\n| -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `env`    | object  | Environment variables to set for all background job commands.                                                                                        |\n| `recall` | boolean | Let `run_background_job` expose a `recall` parameter so jobs can steer the agent when they finish (see [Background job recall](#background-job-recall)). Default `false`. |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Background job recall\n\nSet `recall: true` to let the `run_background_job` tool expose a `recall` boolean parameter:\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    recall: true\n```\n\nWhen the agent starts a background job with `recall: true`, Docker Agent sends a steering message back into the running agent loop after the job finishes. The message contains a short completion sentence and the job output, so the agent can react without polling `view_background_job`.\n\nUse recall for finite background work where completion matters (for example, a long build or test suite). Avoid it for servers and watchers that are expected to run until stopped. See [`examples/shell_recall.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shell_recall.yaml) for a complete configuration.\n\n## Available Tools\n\nThe background jobs toolset exposes five tools:\n\n| Tool Name              | Description                                                                                    |\n| ---------------------- | ---------------------------------------------------------------------------------------------- |\n| `run_background_job`   | Start a command asynchronously and return a job ID immediately. Use for servers/watchers/etc. |\n| `list_background_jobs` | List all background jobs with their status, runtime, and metadata.                             |\n| `view_background_job`  | View the buffered output and status of a specific background job by ID.                        |\n| `stop_background_job`  | Stop a running background job. Child processes are terminated too.                             |\n| `wait_background_job`  | Block until a job finishes and return its exit code and output. Safe on already-finished jobs. |\n\n### `run_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                                                 |\n| --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute in the background.                                                                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                                                                                     |\n| `recall`  | boolean | ✗        | Only available when the `background_jobs` toolset has `recall: true`. When true, send a steering message with the job output when it finishes. |\n\n`view_background_job` and `stop_background_job` each take a single required `job_id` string returned by `run_background_job` or `list_background_jobs`.\n\n### `wait_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                    |\n| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------- |\n| `job_id`  | string  | ✓        | Job ID returned by `run_background_job` or `list_background_jobs`.                                             |\n| `timeout` | integer | ✗        | Maximum seconds to wait (default: `60`). If the job is still running when the limit fires, the tool returns the current output with a notice and the job continues in the background. |\n\n> [!WARNING]\n> **Safety**\n>\n> Background jobs run shell commands with the same access as the agent process. Stop servers and watchers when they are no longer needed, and use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n","frontmatter":{"title":"Background Jobs Tool","description":"Run and manage long-running shell commands.","keywords":"docker agent, ai agents, tools, toolsets, background jobs, shell","linkTitle":"Background Jobs","weight":21,"canonical":"https://docs.docker.com/ai/docker-agent/tools/background-jobs/"},"isInternal":false,"tokens":976,"sizeBytes":5608},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md","title":"Fetch Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Fetch Tool\"\ndescription: \"Read content from HTTP/HTTPS URLs.\"\nkeywords: docker agent, ai agents, tools, toolsets, fetch tool\nlinkTitle: \"Fetch\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/tools/fetch/\n---\n\n_Read content from HTTP/HTTPS URLs._\n\n## Overview\n\nThe fetch tool lets agents retrieve content from one or more HTTP/HTTPS URLs. It is **read-only** — only `GET` requests are supported. The tool respects `robots.txt`, limits response size (1 MB per URL), and can return content as plain text, Markdown (converted from HTML), or raw HTML.\n\n> [!NOTE]\n> **GET only**\n>\n> The fetch tool does **not** support `POST`, `PUT`, `DELETE` or other methods, and does not expose request bodies or per-call custom headers (the toolset can still attach static [credential headers](#custom-headers) to every request). To call REST endpoints with other verbs, use the [API tool](../api/index.md) or an [OpenAPI toolset](../openapi/index.md).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: fetch\n```\n\n### Options\n\n| Property            | Type          | Default | Description                                                                                                                                                                                                                                                                                                      |\n| ------------------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `timeout`           | int           | `30`    | Default request timeout in seconds (overridable per tool call).                                                                                                                                                                                                                                                  |\n| `allowed_domains`   | array[string] | _none_  | Allow-list of hosts the tool may fetch. When set, every URL whose host is **not** in the list is rejected before any network call is made. Mutually exclusive with `blocked_domains`.                                                                                                                            |\n| `blocked_domains`   | array[string] | _none_  | Deny-list of hosts the tool must not fetch. URLs whose host matches one of these patterns are rejected before any network call (including `robots.txt`) is made. Mutually exclusive with `allowed_domains`.                                                                                                      |\n| `allow_private_ips` | boolean       | `false` | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast, and the unspecified address). Required to reach `localhost` / internal services. See [SSRF protection](#ssrf-protection-and-reaching-localhost) below. |\n| `headers`           | map[string]string | _none_ | Static HTTP headers attached to **every** request the toolset issues (including `robots.txt`). Values support `${env.VAR}` for secrets. Caller-supplied entries override the default `User-Agent` and the format-driven `Accept` header. Headers are stripped on cross-host redirects so credentials never leak to a third-party host. See [Custom headers](#custom-headers) below. |\n\n### Domain matching\n\nDomain patterns in `allowed_domains` and `blocked_domains` use the following rules (case-insensitive):\n\n- **Bare domain** — `example.com` matches the host `example.com` _and_ any subdomain such as `docs.example.com`. It does **not** match unrelated hosts that share a suffix (e.g. `badexample.com`).\n- **Leading dot** — `.example.com` matches **only** strict subdomains (`docs.example.com`, `a.b.example.com`), not the apex `example.com`.\n- **Wildcard glob** — `*.example.com` is an alias for the leading-dot form; the apex is excluded. The `*` is only valid as a leading `*.` token (entries like `foo.*`, `*.*.example.com`, or a bare `*` are rejected at config-load time).\n- **IP literal** — IP addresses are matched exactly (`169.254.169.254`).\n- **CIDR range** — `169.254.0.0/16`, `10.0.0.0/8`, `::1/128`, `fc00::/7`. Matches when the URL's host parses as an IP inside the network. Hostname hosts never match a CIDR pattern. Malformed CIDRs are rejected at config-load time.\n- **Trailing dots** in FQDN-form URLs (`http://example.com./`) are stripped before matching, so they cannot bypass a deny-list entry.\n\nThe lists are mutually exclusive: a single fetch toolset may set either `allowed_domains` or `blocked_domains`, but not both.\n\nWhen a list is configured, every redirect target is re-checked against the same list. A request to an allowed origin that redirects to a forbidden host is rejected before any data is read from the redirect.\n\n> [!WARNING]\n> **Limitations**\n>\n> Matching is purely string-based on the URL host. It does **not** perform DNS resolution and does **not** normalise alternative IP encodings (decimal `2852039166`, hex `0xa9.0xfe.0xa9.0xfe`, octal, etc. IPv4-mapped IPv6 addresses ARE normalized to their IPv4 form). If you need to deny access to a specific IP, also list its alternative encodings, or block at the network layer.\n\n### Custom Timeout\n\n```yaml\ntoolsets:\n  - type: fetch\n    timeout: 60\n```\n\n### Custom headers\n\nAttach static headers — typically credentials — to every request. Values support `${env.VAR}` interpolation so secrets stay out of YAML, and headers are dropped on cross-host redirects so a redirect chain cannot leak them to a third-party host:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docs.internal.example.com\n    headers:\n      Authorization: \"Bearer ${env.INTERNAL_DOCS_TOKEN}\"\n      X-Internal-Client: \"docker-agent\"\n```\n\n> [!WARNING]\n> **Pair credential headers with an allow-list**\n>\n> When `headers` carries credentials (e.g. `Authorization`), set `allowed_domains` to the specific hosts that should receive them. Stdlib already strips a small allow-list (`Authorization`, `Cookie`, `WWW-Authenticate`) on cross-domain redirects, and the fetch tool additionally strips every operator-supplied header on cross-host redirects — but an allow-list is the strongest guarantee against accidental exfiltration.\n\n### Restrict to specific domains\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docker.com          # docker.com and *.docker.com\n      - github.com          # github.com and *.github.com\n      - .githubusercontent.com  # only subdomains, e.g. raw.githubusercontent.com\n```\n\n### Block sensitive hosts\n\n```yaml\ntoolsets:\n  - type: fetch\n    blocked_domains:\n      - 169.254.169.254       # cloud metadata endpoint (literal IP)\n      - 169.254.0.0/16        # entire link-local range (CIDR)\n      - 10.0.0.0/8            # RFC1918 private range\n      - \"*.internal.example.com\"  # any subdomain (wildcard)\n      - internal.example.com  # internal corporate hostname\n```\n\n> [!NOTE]\n> **Already blocked by default**\n>\n> You do **not** need to add loopback, RFC1918, link-local (incl. `169.254.169.254`), multicast or the unspecified address to `blocked_domains` to be safe — the fetch tool already refuses connections to those ranges at dial time, after DNS resolution. The example above is only useful if you also want to reject those hosts _before_ any network call (and to surface a clearer error message to the agent), or if you have set `allow_private_ips: true` and want to deny a specific subset.\n\n### SSRF protection and reaching localhost\n\nBy default, the fetch tool refuses connections to **non-public IP addresses** — even when DNS for an otherwise-public host resolves to one of them (so DNS rebinding is also blocked). The check happens at dial time, after DNS resolution, and rejects:\n\n- **Loopback** — `127.0.0.0/8`, `::1` (this is what blocks `http://localhost/...` and `http://127.0.0.1/...`)\n- **RFC1918 private ranges** — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n- **Link-local** — `169.254.0.0/16` (IPv4, including the cloud-metadata endpoint `169.254.169.254`) and `fe80::/10` (IPv6)\n- **Multicast** and the **unspecified** address (`0.0.0.0`, `::`)\n- **IPv4-mapped IPv6** — addresses like `::ffff:127.0.0.1` or `::ffff:169.254.169.254` are normalized to their IPv4 form and blocked accordingly\n\nThis is the default because LLM-driven fetches are a classic Server-Side Request Forgery (SSRF) vector: a prompt-injected URL can otherwise reach internal services, cloud metadata, or admin interfaces on the host running the agent.\n\nIf an agent legitimately needs to call **localhost** or an **internal service**, opt in with `allow_private_ips: true`:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allow_private_ips: true\n    allowed_domains:\n      - localhost\n      - 127.0.0.1\n      - 10.0.0.0/8            # internal corporate range\n```\n\n> [!WARNING]\n> **Pair with an allow-list**\n>\n> Setting `allow_private_ips: true` alone re-exposes the SSRF surface. We strongly recommend combining it with an `allowed_domains` entry that restricts the tool to the specific internal hosts or CIDRs the agent actually needs (e.g. `localhost`, `127.0.0.1`, or your internal CIDR).\n>\n> **Note:** `allowed_domains` is checked _before_ DNS resolution (string-based on hostname), while the SSRF check happens _after_ DNS resolution (on the resolved IP). This means `allowed_domains` and `blocked_domains` are evaluated independently of `allow_private_ips` and continue to apply. A public hostname in `allowed_domains` that resolves to a private IP will still be blocked unless `allow_private_ips: true` is set.\n\n## Tool Interface\n\nThe toolset exposes a single tool, `fetch`, with the following parameters:\n\n| Parameter | Type           | Required | Description                                                                                                 |\n| --------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------- |\n| `urls`    | array[string]  | ✓        | One or more HTTP/HTTPS URLs to fetch (all via `GET`).                                                       |\n| `format`  | string         | ✓        | Output format: `text`, `markdown`, or `html`. HTML responses are converted to text/markdown when requested. |\n| `timeout` | integer        | ✗        | Per-call request timeout in seconds. Overrides the toolset default. Valid range: `1`–`300`.                 |\n\nResponses are capped at **1 MB** per URL. Hosts that disallow the agent's user-agent via `robots.txt` are skipped with a clear error.\n\n> [!TIP]\n> **Fetch vs. API Tool**\n>\n> Use `fetch` when the agent needs to read arbitrary public URLs at runtime. Use the [API tool](../api/index.md) to expose specific, structured HTTP endpoints (including non-`GET` verbs) as named tools.\n\n## Domain Filtering\n\nThe `allowed_domains`, `blocked_domains`, and `allow_private_ips` options let you control which hosts the fetch tool may reach. The complete reference is in the [Options](#options) table and [Domain matching](#domain-matching) section above.\n\n**Key points:**\n\n- `allowed_domains` — allow-list; only listed hosts (and their subdomains for bare-domain entries) are reachable\n- `blocked_domains` — deny-list; mutually exclusive with `allowed_domains` (a config error is thrown if both are set)\n- `allow_private_ips` — defaults to `false`; set to `true` to reach loopback / RFC-1918 / link-local addresses\n- The same `allow_private_ips` flag is also supported on `api`, `openapi`, `a2a`, and remote `mcp` toolsets\n\nSee [`examples/fetch_domain_filtering.yaml`](https://github.com/docker/docker-agent/blob/main/examples/fetch_domain_filtering.yaml) for a complete filtering example, and [`examples/remote_mcp_allow_private_ips.yaml`](https://github.com/docker/docker-agent/blob/main/examples/remote_mcp_allow_private_ips.yaml) for the equivalent pattern on remote MCP toolsets.\n","frontmatter":{"title":"Fetch Tool","description":"Read content from HTTP/HTTPS URLs.","keywords":"docker agent, ai agents, tools, toolsets, fetch tool","linkTitle":"Fetch","weight":50,"canonical":"https://docs.docker.com/ai/docker-agent/tools/fetch/"},"isInternal":false,"tokens":2711,"sizeBytes":12213},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md","title":"Filesystem Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Filesystem Tool\"\ndescription: \"Read, write, list, search, and navigate files and directories.\"\nkeywords: docker agent, ai agents, tools, toolsets, filesystem tool\nlinkTitle: \"Filesystem\"\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/tools/filesystem/\n---\n\n_Read, write, list, search, and navigate files and directories._\n\n## Overview\n\nThe filesystem tool gives agents the ability to explore codebases, read and edit files, create new files, search across files, and navigate directory structures.\n\n### Path resolution\n\nPaths are resolved relative to the **working directory** (the directory where the agent session started, or the directory specified with `--workdir`):\n\n- **Relative paths** (e.g., `src/main.go`, `../README.md`) are joined with the working directory.\n- **Absolute paths** must match the host operating system:\n  - Unix/Linux/macOS: `/home/user/project/file.txt`\n  - Windows: `C:\\Users\\user\\project\\file.txt` or `C:/Users/user/project/file.txt`\n- **Home directory expansion**: paths starting with `~` or `~/` expand to the user's home directory.\n\nWhen a file is not found, error messages include the resolved absolute path to help diagnose incorrect base directories or path formats.\n\n> [!IMPORTANT]\n> Agents must use paths appropriate for the host OS. A Windows absolute path like `C:\\file.txt` on a Unix system (or vice versa) is rejected with a clear error message.\n\n### Empty directory detection\n\nWhen `list_directory` encounters an empty directory or a directory where all entries are hidden by ignore patterns (e.g., only a `.git` folder when `ignore_vcs: true`), it explicitly reports the state:\n\n- **Empty directory**: \"Directory is empty: /path/to/dir\"\n- **All entries ignored**: \"Directory has no visible entries (N hidden by ignore patterns): /path/to/dir\"\n\nThis helps agents distinguish between an empty directory and a tool failure, avoiding unnecessary retries with shell commands.\n\n## Available Tools\n\n| Tool                   | Description                                                               |\n| ---------------------- | ------------------------------------------------------------------------- |\n| `read_file`            | Read the contents of a file (whole file, or a line range of a text file)  |\n| `read_multiple_files`  | Read several files in one call (more efficient than multiple `read_file`) |\n| `write_file`           | Create or overwrite a file with new content                               |\n| `edit_file`            | Make line-based edits (find-and-replace) in an existing file. Each edit must specify a non-empty `oldText` to match and replace; empty `oldText` values are rejected with an error. |\n| `list_directory`       | List files and directories at a given path (explicitly reports empty directories) |\n| `directory_tree`       | Recursive tree view of a directory                                        |\n| `create_directory`     | Create a new directory (creates parent directories as needed)             |\n| `remove_directory`     | Remove an empty directory                                                 |\n| `search_files_content` | Search for text or regex patterns across files                            |\n\n## edit_file Validation\n\nThe `edit_file` tool applies a sequence of find-and-replace edits to a file in memory, then writes the result back atomically. Each edit must provide a non-empty `oldText` value:\n\n- **Valid**: `{\"oldText\": \"line one\", \"newText\": \"LINE ONE\"}`\n- **Invalid**: `{\"oldText\": \"\", \"newText\": \"INJECTED\"}` — rejected with error\n\nAn empty `oldText` is never a meaningful edit: Go's `strings.Contains(s, \"\")` is always `true`, and `strings.Replace(s, \"\", new, 1)` silently inserts at offset 0. Without validation, this would prepend content to the file while still reporting success. The tool now returns an explicit error (\"oldText must not be empty\") when an edit has an empty `oldText`, and no changes are written to disk.\n\nWhen a sequence contains multiple edits and a later one is rejected, the entire operation fails and the file is left untouched — edits are applied in memory and only written once at the end, so partial application is not possible.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: filesystem\n```\n\n### Options\n\n| Property | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ignore_vcs` | boolean | `true` | When `true` (default), `.git` directories and `.gitignore` patterns are excluded from listings and searches. Set to `false` to include them. |\n| `post_edit` | array | `[]` | Commands to run after editing files matching a path pattern |\n| `post_edit[].path` | string | — | Glob pattern for files (e.g., `*.go`, `src/*/*.ts`) |\n| `post_edit[].cmd` | string | — | Command to run (use `${file}` for the edited file path) |\n| `allow_list` | array | `[]` | Directories the tools may access. Empty = unrestricted (default). |\n| `deny_list` | array | `[]` | Directories the tools must not access. Takes precedence over `allow_list`. |\n\n### Path access control\n\nBy default the filesystem tools are unrestricted: relative paths resolve\nfrom the working directory, but absolute paths and `..` traversals can\nreach anywhere the agent process can. Configure `allow_list` and/or\n`deny_list` to sandbox the toolset.\n\nEntries in either list are expanded as follows:\n\n- `\".\"` — the agent's working directory\n- `\"~\"` or `\"~/...\"` — the user's home directory\n- `\"$VAR\"` / `\"${VAR}\"` / `\"${env.VAR}\"` — environment variable expansion\n- absolute paths — used as-is\n- relative paths — anchored at the working directory\n\nSymlinks are resolved before the containment check, so a symlink inside an\nallowed root cannot be used to escape it. When an `allow_list` is set,\neach entry is opened as a Go [`*os.Root`](https://pkg.go.dev/os#Root) so\nthat the kernel's rooted-lookup semantics also reject `..` and symlink\nescapes at I/O time, not just at resolve time.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    # Restrict every operation to the working directory and the user's\n    # home folder, then carve credentials out of the home folder.\n    allow_list:\n      - \".\"\n      - \"~\"\n    deny_list:\n      - \"~/.ssh\"\n      - \"~/.aws\"\n```\n\nWhen the path supplied by the agent is rejected, the tool returns a\nstructured error rather than performing any filesystem I/O. This makes the\nrestriction visible to the model so it can adjust its plan.\n\n### Post-Edit Hooks\n\nAutomatically run formatting, linting, or other commands after the agent edits a file. The command fires once per file after each edit operation (`write_file` and `edit_file`). Use `${file}` as a placeholder for the absolute path of the edited file.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    ignore_vcs: false\n    post_edit:\n      - path: \"*.go\"\n        cmd: \"gofmt -w ${file}\"\n      - path: \"*.ts\"\n        cmd: \"prettier --write ${file}\"\n      - path: \"src/*/*.py\"\n        cmd: \"black ${file}\"\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `path` | string | Glob pattern matched against the file path. `*.go` matches any `.go` file; `src/*/*.ts` matches `.ts` files inside `src/`. |\n| `cmd` | string | Shell command to run. `${file}` expands to the absolute path of the just-edited file. |\n\nPost-edit commands run with the same working directory as the agent. If a command exits non-zero, the error is logged and surfaced to the model as a warning, but the edit is not rolled back.\n\nSee [`examples/post_edit.yaml`](https://github.com/docker/docker-agent/blob/main/examples/post_edit.yaml) for a complete example.\n","frontmatter":{"title":"Filesystem Tool","description":"Read, write, list, search, and navigate files and directories.","keywords":"docker agent, ai agents, tools, toolsets, filesystem tool","linkTitle":"Filesystem","weight":10,"canonical":"https://docs.docker.com/ai/docker-agent/tools/filesystem/"},"isInternal":false,"tokens":1730,"sizeBytes":7545},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/git/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/git/index.md","title":"Git Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Git Tool\"\ndescription: \"Read-only inspection of the working git repository.\"\nkeywords: docker agent, ai agents, tools, toolsets, git tool\nlinkTitle: \"Git\"\nweight: 125\ncanonical: https://docs.docker.com/ai/docker-agent/tools/git/\n---\n\n_Read-only inspection of the working git repository._\n\n## Overview\n\nThe git toolset gives an agent structured, **read-only** access to the working repository — status, history, branches, a commit's changes, and line-level authorship. It is implemented with go-git, so it needs **no `git` binary**.\n\nCompared with running `git` through the `shell` tool, the git toolset returns clean, structured output the model can read reliably, is **safe by construction** (no command can modify the repository), and works even when `shell` is disabled or no `git` binary is installed.\n\n> [!NOTE]\n> The git toolset is read-only. To stage, commit, or check out, use the [`shell`](../shell/index.md) tool.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: git\n```\n\nNo configuration options. The repository is opened at the agent's working directory; a subdirectory still resolves to the repository root.\n\n> [!WARNING]\n> **The repository is discovered by walking up parent directories.** If the working\n> directory is not itself a repository but an ancestor is (for example a\n> home directory tracked as dotfiles), the toolset resolves to that ancestor and\n> `git_show` / `git_blame` can expose its full history and file contents. The\n> filesystem toolset's allow/deny lists do **not** apply here. Only enable this\n> toolset where the surrounding repository is safe to read.\n\n> [!NOTE]\n> **Performance.** go-git is pure Go, which costs speed on large repositories:\n> `git_status` rehashes the whole worktree, and `git_blame` scales with history\n> depth times file size — its 400-line output cap is applied *after* the full\n> computation, so it does not make blaming a large file cheaper.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `git_status` | Current branch and changed files (staged / unstaged / untracked). |\n| `git_log` | Recent commits (hash, date, author, subject). |\n| `git_branches` | Local branches, current one marked with `*`. |\n| `git_show` | A commit's metadata, message, and changed files with +/- counts. |\n| `git_blame` | Line-by-line authorship for a file. |\n\n### `git_log`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `limit` | No | Maximum number of commits to return (default 20). |\n| `path` | No | Only show commits that touch this path. |\n\n### `git_show`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `ref` | No | Commit hash or revision to show (default HEAD). |\n\n### `git_blame`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `path` | Yes | File path to blame, relative to the repository root. |\n| `rev` | No | Commit or revision to blame at (default HEAD). |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A code review assistant\n    instruction: |\n      Review the working changes: check git_status, then git_show the latest\n      commit, and summarize what changed.\n    toolsets:\n      - type: git\n      - type: filesystem\n```\n\nExample `git_status` output:\n\n```text\nOn branch master\n1 changed file(s) [XY = staged/worktree; M=modified A=added D=deleted R=renamed ?=untracked]:\n   M main.go\n```\n\n> [!TIP]\n> **When to use**\n>\n> Use the git toolset whenever the agent needs repository context — before editing, to review recent history, or to find who last touched a line — without exposing the writable `shell` surface.\n","frontmatter":{"title":"Git Tool","description":"Read-only inspection of the working git repository.","keywords":"docker agent, ai agents, tools, toolsets, git tool","linkTitle":"Git","weight":125,"canonical":"https://docs.docker.com/ai/docker-agent/tools/git/"},"isInternal":false,"tokens":890,"sizeBytes":3572},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md","title":"Handoff Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Handoff Tool\"\ndescription: \"Hand off the active conversation to another local agent defined in the same config.\"\nkeywords: docker agent, ai agents, tools, toolsets, handoff tool\nlinkTitle: \"Handoff\"\nweight: 70\ncanonical: https://docs.docker.com/ai/docker-agent/tools/handoff/\n---\n\n_Hand off the active conversation to another local agent defined in the same config._\n\n## Overview\n\nThe `handoff` tool lets an agent transfer control of the **current conversation** to another agent in the **same config file**. Unlike [`transfer_task`](../transfer-task/index.md), which delegates a sub-task and collects the result, `handoff` rewires the session so the receiving agent continues the conversation directly with the user.\n\nThis is the core mechanism for **handoffs routing** — a pattern where a router agent classifies the user's request and hands it off to a specialist, which then owns the rest of the session.\n\n> [!NOTE]\n> **Local only**\n>\n> The `handoff` tool only targets agents declared in the **same** config file by their local name. It does **not** open network connections. To delegate to a remote agent over the network, use the [A2A toolset](../a2a/index.md) instead.\n\n## Configuration\n\nThe tool is enabled implicitly when an agent declares a non-empty `handoffs:` list. You do **not** add `- type: handoff` under `toolsets:` — it is not a toolset type.\n\n```yaml\nagents:\n  router:\n    model: openai/gpt-4o\n    description: Routes questions to the right specialist\n    instruction: |\n      Classify the user's question and hand off to the most appropriate\n      specialist. If unsure, ask a clarifying question first.\n    handoffs: [billing, support]\n\n  billing:\n    model: openai/gpt-4o\n    description: Billing specialist\n    instruction: Answer billing questions.\n\n  support:\n    model: openai/gpt-4o\n    description: Technical support specialist\n    instruction: Help with technical issues.\n```\n\nThe router agent automatically gets a `handoff` tool it can call to switch the conversation to `billing` or `support`.\n\n## Tool Interface\n\nThe `handoff` tool takes a single parameter:\n\n| Parameter | Type   | Required | Description                                                       |\n| --------- | ------ | -------- | ----------------------------------------------------------------- |\n| `agent`   | string | ✓        | The local name of the agent to hand off the conversation to.      |\n\nOnly names listed in the current agent's `handoffs:` field are valid targets.\n\n> [!TIP]\n> **See also**\n>\n> For sub-task delegation (caller stays in control, waits for the result), see [Transfer Task](../transfer-task/index.md). For remote agent connections over the network, see the [A2A toolset](../a2a/index.md). For the broader pattern, see [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).\n","frontmatter":{"title":"Handoff Tool","description":"Hand off the active conversation to another local agent defined in the same config.","keywords":"docker agent, ai agents, tools, toolsets, handoff tool","linkTitle":"Handoff","weight":70,"canonical":"https://docs.docker.com/ai/docker-agent/tools/handoff/"},"isInternal":false,"tokens":639,"sizeBytes":2835},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md","title":"Lsp Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"LSP Tool\"\ndescription: \"Connect to Language Server Protocol servers for code intelligence.\"\nkeywords: docker agent, ai agents, tools, toolsets, lsp tool\nlinkTitle: \"LSP\"\nweight: 220\ncanonical: https://docs.docker.com/ai/docker-agent/tools/lsp/\n---\n\n_Connect to Language Server Protocol servers for code intelligence._\n\n## Overview\n\nThe LSP tool connects your agent to any Language Server Protocol (LSP) server, providing comprehensive code intelligence capabilities like go-to-definition, find references, diagnostics, and more.\n\n> [!NOTE]\n> **What is LSP?**\n>\n> The [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) is a standard for providing language features like autocomplete, go-to-definition, and diagnostics. Most programming languages have LSP servers available.\n\n## Available Tools\n\nThe LSP toolset provides these tools to the agent:\n\n| Tool                    | Description                                   | Read-Only |\n| ----------------------- | --------------------------------------------- | --------- |\n| `lsp_workspace`         | Get workspace info and available capabilities | ✓         |\n| `lsp_hover`             | Get type info and documentation for a symbol  | ✓         |\n| `lsp_definition`        | Find where a symbol is defined                | ✓         |\n| `lsp_references`        | Find all references to a symbol               | ✓         |\n| `lsp_document_symbols`  | List all symbols in a file                    | ✓         |\n| `lsp_workspace_symbols` | Search symbols across the workspace           | ✓         |\n| `lsp_diagnostics`       | Get errors and warnings for a file            | ✓         |\n| `lsp_code_actions`      | Get available quick fixes and refactorings    | ✓         |\n| `lsp_rename`            | Rename a symbol across the workspace          | ✗         |\n| `lsp_format`            | Format a file                                 | ✗         |\n| `lsp_call_hierarchy`    | Find incoming/outgoing calls                  | ✓         |\n| `lsp_type_hierarchy`    | Find supertypes/subtypes                      | ✓         |\n| `lsp_implementations`   | Find interface implementations                | ✓         |\n| `lsp_signature_help`    | Get function signature at call site           | ✓         |\n| `lsp_inlay_hints`       | Get type annotations and parameter names      | ✓         |\n\n## Configuration\n\n```yaml\nagents:\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code developer with LSP support\n    instruction: You are a software developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        args: []\n        file_types: [\".go\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Properties\n\n| Property      | Type   | Required | Description                                                                                                                  |\n| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `command`     | string | ✓        | LSP server executable command                                                                                                |\n| `args`        | array  | ✗        | Command-line arguments for the LSP server                                                                                    |\n| `env`         | object | ✗        | Environment variables for the LSP process                                                                                    |\n| `file_types`  | array  | ✗        | File extensions this LSP handles (e.g., `[\".go\", \".mod\"]`)                                                                   |\n| `working_dir` | string | ✗        | Working directory for the LSP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. |\n| `version`     | string | ✗        | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary |\n\n## Common LSP Servers\n\nHere are configurations for popular languages:\n\n### Go (gopls)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    version: \"golang/tools@v0.21.0\" # optional: auto-install if not in PATH\n    file_types: [\".go\"]\n```\n\nIf your Go module lives in a subdirectory (e.g. a monorepo where `go.mod` is under `./backend`), set `working_dir` so `gopls` is started from the module root:\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    working_dir: ./backend # gopls must be started from the module root\n```\n\n### TypeScript/JavaScript (typescript-language-server)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: typescript-language-server\n    args: [\"--stdio\"]\n    file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n```\n\n### Python (pylsp)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: pylsp\n    file_types: [\".py\"]\n```\n\n### Rust (rust-analyzer)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: rust-analyzer\n    file_types: [\".rs\"]\n```\n\n### C/C++ (clangd)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: clangd\n    file_types: [\".c\", \".cpp\", \".h\", \".hpp\"]\n```\n\n## Multiple LSP Servers\n\nYou can configure multiple LSP servers for different file types:\n\n```yaml\nagents:\n  polyglot:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-language developer\n    instruction: You are a full-stack developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      - type: lsp\n        command: typescript-language-server\n        args: [\"--stdio\"]\n        file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n      - type: lsp\n        command: pylsp\n        file_types: [\".py\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Workflow Instructions\n\nThe LSP tool includes built-in instructions that guide the agent on how to use it effectively. The agent learns to:\n\n1. Start with `lsp_workspace` to understand available capabilities\n2. Use `lsp_workspace_symbols` to find relevant code\n3. Use `lsp_references` before modifying any symbol\n4. Check `lsp_diagnostics` after every code change\n5. Apply `lsp_format` after edits are complete\n\n> [!TIP]\n> **Best Practice**\n>\n> Always include the `filesystem` tool alongside LSP. The agent needs filesystem access to read and write code files, while LSP provides intelligence about the code.\n\n## Capability Detection\n\nNot all LSP servers support all features. During the `initialize` handshake, Docker Agent reads the server's `ServerCapabilities` and **filters out the `lsp_*` tools the server does not advertise**. The model never sees, for example, `lsp_inlay_hints` against a server that doesn't support it, so it can't waste a turn calling a tool that would only fail.\n\nThe agent uses `lsp_workspace` to discover what's available:\n\n```text\nWorkspace Information:\n- Root: /path/to/project\n- Server: gopls v0.14.0\n- File types: .go\n\nAvailable Capabilities:\n- Hover: Yes\n- Go to Definition: Yes\n- Find References: Yes\n- Rename: Yes\n- Code Actions: Yes\n- Formatting: Yes\n- Call Hierarchy: Yes\n- Type Hierarchy: Yes\n...\n```\n\n## Auto-Restart and Lifecycle\n\nLSP toolsets are managed by the same supervisor as MCP toolsets, so a crashed `gopls` (or any other language server) is reconnected automatically with exponential backoff. Use the [`lifecycle`](../../configuration/tools/index.md#toolset-lifecycle) block to tune the policy per toolset — for example, mark `gopls` as `strict` if your CI flow requires it to be available, or use `/toolset-restart gopls` from the TUI to force a reconnect when the server gets stuck.\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: resilient # default: auto-restart on crash with exponential backoff\n```\n\n## Position Format\n\nAll LSP tools use **1-based** line and character positions:\n\n- Line 1 is the first line of the file\n- Character 1 is the first character on a line\n\n```json\n{\n  \"file\": \"/path/to/file.go\",\n  \"line\": 42,\n  \"character\": 15\n}\n```\n\n> [!TIP]\n> **Auto-Installation**\n>\n> Docker Agent can automatically download and install LSP servers if they are not found in your PATH. Use the `version` property to specify a package, or let Docker Agent auto-detect it from the command name. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) for details.\n","frontmatter":{"title":"LSP Tool","description":"Connect to Language Server Protocol servers for code intelligence.","keywords":"docker agent, ai agents, tools, toolsets, lsp tool","linkTitle":"LSP","weight":220,"canonical":"https://docs.docker.com/ai/docker-agent/tools/lsp/"},"isInternal":false,"tokens":1941,"sizeBytes":8425},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md","title":"Mcp-catalog Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"MCP Catalog Tool\"\ndescription: \"Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp catalog tool\nlinkTitle: \"MCP Catalog\"\nweight: 120\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp-catalog/\n---\n\n_Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand._\n\n## Overview\n\nThe `mcp_catalog` toolset gives an agent access to a curated subset of the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp) — every server in this subset is reachable over the **streamable-http** transport, so Docker Agent can talk to it directly without the MCP gateway or a local subprocess.\n\nServers are **not** active by default. Instead, the toolset exposes a small set of meta-tools the agent uses to search, enable, and disable servers as a turn unfolds. Tools from un-enabled servers stay hidden, so the prompt is not flooded with hundreds of tool definitions the agent will never use.\n\n> [!NOTE]\n> **When to use it**\n>\n> Use `mcp_catalog` when you want the agent to _decide at runtime_ which third-party services it needs (Notion, Stripe, Brave Search, …) instead of pinning that decision in YAML up front. For a fixed set of servers, declare each one with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) directly — the catalog adds an extra layer of meta-tools that pure `type: mcp` entries do not need.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n```\n\nThe catalog is embedded in the `docker-agent` binary and refreshed with each release. By default every server in the embedded subset is offered.\n\n### Restricting the offered servers\n\nTwo optional lists narrow what the toolset offers, so an agent sees a focused, predictable menu instead of the full catalog:\n\n- **`allowed_servers`** — when non-empty, **only** these catalog server ids are searchable and enableable; every other entry is hidden.\n- **`blocked_servers`** — removes individual ids from the offered set. It is applied **after** `allowed_servers`, so a server listed in both is blocked (block wins over allow).\n\nBoth take server ids (the `id` field returned by `search_remote_mcp_servers`). An empty or omitted list disables that filter.\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n    allowed_servers:\n      - docker-docs\n      - microsoft-learn\n      - hugging-face\n    blocked_servers:\n      - gitmcp\n```\n\n## Meta-Tools\n\nUp to five tools are exposed to the model. The disable / reset-auth pair only appears once at least one server is enabled, so the meta-tool surface stays minimal until the agent activates something.\n\n| Tool                            | When visible            | Description                                                                                                                                          |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `search_remote_mcp_servers`     | Always                  | Case-insensitive fuzzy search over id, title, description, category and tags. Returns id, auth requirements (`oauth` / `none`) and URL. |\n| `enable_remote_mcp_server`      | Always                  | Activate a server by id. **Blocks** until the connection (and any required OAuth handshake) completes; on success the server's tools are immediately live and the model continues with the user's original request in the same turn. |\n| `list_remote_mcp_servers`       | Always                  | Show currently enabled servers and their connection state.                                                                                           |\n| `disable_remote_mcp_server`     | After first enable      | Stop a server and remove its tools from the active set.                                                                                              |\n| `reset_remote_mcp_server_auth`  | After first enable      | Drop persisted OAuth credentials so the next enable triggers a fresh authorization flow. No-op for `none` servers.                       |\n\n### Workflow\n\n1. The agent calls `search_remote_mcp_servers` with a keyword matching the user's intent (`\"notion\"`, `\"stripe\"`, `\"docs\"`, `\"browser\"`, `\"grafana\"`, …).\n2. It picks a matching server id and calls `enable_remote_mcp_server`. **`enable` blocks** until the MCP handshake (and any required OAuth flow) completes:\n   - on success the server's tools are available **in the same turn** — the agent goes straight to the user's original request, no re-ask required;\n   - on failure (user dismissed the authorization dialog, server refused) the tool returns an error result naming the specific reason so the agent can recover instead of pretending the server is connected.\n3. It uses the newly activated tools as it would any other.\n4. When done, it calls `disable_remote_mcp_server` to remove the server from the active set.\n\n## Authentication\n\nThe catalog only includes servers Docker Agent can authenticate itself, so there are two auth flavours:\n\n- **`oauth`** — `enable_remote_mcp_server` surfaces an authorization URL through the elicitation pipeline (the same one used by YAML-declared remote MCP toolsets) and blocks until the user either authorizes or cancels. Once the user authorizes, tokens are persisted in the OS keyring and re-used on subsequent runs. Use `reset_remote_mcp_server_auth` to wipe them. If the user dismisses the dialog, `enable` returns an error result naming the decline so the agent can ask whether to retry.\n- **`none`** — No authentication. The server is reachable as soon as it is enabled.\n\nServers that require a caller-provided API key are intentionally excluded from the catalog. To use one, declare it explicitly with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) and supply the key via an environment variable.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Agent that can on-demand connect to remote MCP servers from the Docker MCP Catalog.\n    instruction: |\n      You can discover and activate remote MCP servers on demand.\n      Use search_remote_mcp_servers to find a server matching the\n      user's intent, then enable_remote_mcp_server to activate it.\n      Be conservative: enable only the servers you actually need for\n      the task at hand. Disable a server with disable_remote_mcp_server\n      once you are done with it.\n    toolsets:\n      - type: mcp_catalog\n```\n\nA complete, runnable configuration lives in [`examples/mcp_catalog.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog.yaml). A curated, allow/block-listed variant lives in [`examples/mcp_catalog_filtered.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog_filtered.yaml).\n\n## Notes and Limitations\n\n- **Streamable-http only.** The catalog deliberately excludes servers that require a local subprocess or the MCP gateway — declare those with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) instead.\n- **Catalog membership changes between releases.** The set of available servers is updated with each Docker Agent release as integrations are added or removed. Servers present in one release may not appear in the next.\n- **Blocking enable.** DNS, TCP, MCP handshake and any OAuth flow happen synchronously inside `enable_remote_mcp_server` so the agent gets a deterministic result in the same turn. On startup, however, the runtime probes tools non-interactively (`mcp.WithoutInteractivePrompts`); OAuth-pending servers fail fast there and are silently deferred to the next interactive turn — including the sidebar-only tool-count pass, where a dialog would be impossible.\n- **No prompt discovery.** MCP prompt lookups (`/prompts`) walk YAML-declared `mcp` toolsets directly; prompts exposed by servers activated through the catalog are not surfaced. Tools — the primary interface — work fine.\n- **Frozen at build time.** The list of servers is embedded in the binary. New entries land with each Docker Agent release.\n\n> [!TIP]\n> **Pair with permissions**\n>\n> Because the agent decides which third-party services to talk to, this toolset works best with explicit [permissions](../../configuration/permissions/index.md) on the surrounding tools (filesystem writes, shell commands) so a misrouted server cannot exfiltrate data unnoticed.\n","frontmatter":{"title":"MCP Catalog Tool","description":"Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand.","keywords":"docker agent, ai agents, tools, toolsets, mcp catalog tool","linkTitle":"MCP Catalog","weight":120,"canonical":"https://docs.docker.com/ai/docker-agent/tools/mcp-catalog/"},"isInternal":false,"tokens":1760,"sizeBytes":8521},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md","title":"Mcp Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"MCP Tool\"\ndescription: \"Extend agents with external tools via the Model Context Protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp tool\nlinkTitle: \"MCP\"\nweight: 130\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp/\naliases:\n  - /ai/docker-agent/integrations/mcp/\n---\n\n_Extend agents with external tools via the Model Context Protocol (MCP)._\n\n## Overview\n\nThe `mcp` toolset connects your agent to any MCP server — a process or remote service that exposes tools, resources, and prompts over the [Model Context Protocol](https://modelcontextprotocol.io/). Three flavours are supported:\n\n| Flavour | Transport | Best for |\n| --- | --- | --- |\n| **Docker MCP** | Container via the [MCP Gateway](https://github.com/docker/mcp-gateway) | Curated, sandboxed servers from the [Docker MCP Catalog](https://hub.docker.com/u/mcp) |\n| **Local stdio** | Subprocess over stdin/stdout | Custom or community MCP servers run from a binary or `npx`/`pip` package |\n| **Remote** | Streamable HTTP or SSE | Cloud services with hosted MCP endpoints (Linear, Notion, Atlassian, …) |\n\n> [!NOTE]\n> **What is MCP?**\n>\n> The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI tools. Docker Agent can both _use_ MCP servers (this page) and _expose_ agents as MCP servers — see [MCP Mode](../../features/mcp-mode/index.md).\n\n## Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the MCP Gateway. The `ref: docker:<name>` syntax pulls a curated definition from the Docker MCP Catalog:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo        # web search\n  - type: mcp\n    ref: docker:github-official   # GitHub integration\n    tools: [\"list_issues\", \"create_issue\"]\n```\n\nBrowse available servers at the [Docker MCP Catalog](https://hub.docker.com/u/mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`) or a name from the [reusable `mcps:`](../../configuration/overview/index.md#reusable-mcp-servers-mcps) block. |\n| `tools`       | array  | Optional whitelist — only expose these tools to the model.       |\n| `instruction` | string | Custom instructions injected into the agent's context.           |\n| `config`      | any    | MCP server-specific configuration passed during initialization.  |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n\n## Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property      | Type   | Description |\n| ------------- | ------ | ----------- |\n| `command`     | string | Command to execute the MCP server. |\n| `args`        | array  | Command arguments. |\n| `tools`       | array  | Optional whitelist — only expose these tools. |\n| `env`         | object | Environment variables (key-value pairs). |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context. |\n| `version`     | string | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary. |\n\n> [!TIP]\n> **Auto-installation**\n>\n> If the `command` is not in your `PATH`, Docker Agent looks it up in the [aqua registry](https://github.com/aquaproj/aqua-registry) and installs it for you. Use `version: \"false\"` to opt out, or set `DOCKER_AGENT_AUTO_INSTALL=false` globally. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools).\n\n## Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network. OAuth flows (including [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591)) are handled automatically — Docker Agent opens your browser when authentication is required and caches tokens for subsequent sessions. Tokens are refreshed silently when they expire or are revoked server-side; if a silent refresh is not possible, the OAuth prompt reappears on the next message.\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp.linear.app/mcp\"\n      transport_type: \"streamable\"               # or \"sse\" for legacy servers\n      headers:\n        Authorization: \"Bearer ${env.LINEAR_TOKEN}\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: false\n    tools: [\"search_issues\", \"create_issue\"]\n```\n\n| Property                | Type    | Description |\n| ----------------------- | ------- | ----------- |\n| `remote.url`            | string  | Base URL of the MCP server. |\n| `remote.transport_type` | string  | `streamable` or `sse`. |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. See [Remote MCP Servers](../../features/remote-mcp/index.md#per-request-header-template-expansion) for details. |\n| `remote.oauth`          | object  | Explicit OAuth client credentials for servers that don't support DCR. See [Remote MCP Servers](../../features/remote-mcp/index.md#oauth-for-servers-without-dynamic-client-registration). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers. |\n\nFor a curated list of public remote MCP endpoints (Linear, GitHub, Vercel, Notion, …) and full OAuth configuration details, see [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n## MCP Prompts\n\nMCP servers can expose **prompts** — named, parameterized templates that the server provides via the `/prompts` endpoint. Docker Agent discovers these at toolset startup and registers them as **slash commands** in the TUI, so you can invoke them directly from the input box.\n\n```text\n# Type / to see available prompts alongside built-in commands\n/review         # invoke an MCP prompt named \"review\"\n/summarize My text here   # invoke with the first argument filled in\n```\n\n**How it works:**\n\n- Each MCP prompt appears in the command palette (accessible via <kbd>Ctrl</kbd>+<kbd>K</kbd>) under the **MCP Prompts** category.\n- Typing `/<prompt-name>` in the input box invokes the prompt immediately.\n- If the prompt declares arguments and you provide text after the slash command, that text is mapped to the first declared argument.\n- If a required argument is missing, Docker Agent opens the argument input dialog before running the prompt.\n- When no argument is needed or all required arguments are supplied, the prompt runs immediately.\n\n> [!NOTE]\n> MCP prompt discovery requires a YAML-declared `mcp` toolset. Prompts from servers activated through the [Docker MCP Catalog](../../tools/mcp-catalog/index.md) (`ref: docker:<name>`) are not currently surfaced.\n\n## Embedded Resources\n\nMCP tool results can include embedded resources — images, PDFs, and text files returned directly in the tool response. Docker Agent preserves these as attachments and forwards them to the model as native content blocks:\n\n- **Anthropic** — images become `image` blocks in the `tool_result`; PDFs and other documents become `document` blocks.\n- **OpenAI** — images are forwarded as `input_image` data URIs; PDFs as `input_file` data URIs in the tool result content.\n- **Bedrock** and **Gemini** — receive equivalent provider-native representations.\n\nNo configuration is required. When an MCP server returns an embedded resource alongside its text output, the resource is automatically attached and sent to the model on the next turn. This is useful for MCP servers that generate charts, export PDFs, or return binary data as part of their responses.\n\n## Reusable Definitions (`mcps:`)\n\nRepeated MCP server configurations can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`:\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n  playwright:\n    command: npx\n    args: [\"-y\", \"@modelcontextprotocol/server-playwright\"]\n\nagents:\n  root:\n    model: openai/gpt-5\n    toolsets:\n      - type: mcp\n        ref: github\n      - type: mcp\n        ref: playwright\n```\n\nSee [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) for the full reference.\n\n## Common Options\n\nThese properties apply to every MCP toolset regardless of flavour:\n\n### Tool filtering\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n```\n\nWhitelisting tools improves model accuracy — fewer choices means less confusion.\n\n### Deferred loading\n\nSkip the toolset's startup cost until its tools are actually called:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  # Or defer specific tools within a toolset:\n  - type: mcp\n    ref: docker:slack\n    defer: [\"list_channels\", \"search_messages\"]\n```\n\n### Custom instructions\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\n### TOON-encoded outputs\n\nRe-encode verbose JSON outputs as the compact [TOON](https://github.com/alpkeskin/gotoon) format to save context budget. Typically yields 30–60% smaller payloads on list/search tools.\n\n`toon` is a regex string that is matched against tool names. Any tool whose name matches the pattern has its JSON output transparently re-encoded as TOON before it is shown to the model. The re-encoding reduces schema verbosity, which is especially useful when a model struggles with large or repetitive tool output.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"            # toonify every tool from this server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\nThe value is a comma-separated list of regexes (or a single regex). A tool name must match at least one pattern to be re-encoded. Setting `toon: \".*\"` re-encodes all tools from that toolset.\n\nSee [`examples/github-toon.yaml`](https://github.com/docker/docker-agent/blob/main/examples/github-toon.yaml) for a practical example using the GitHub MCP server.\n\n### Per-toolset model routing\n\nProcess tool results from this toolset with a different (typically cheaper / faster) model. The override is one-shot — subsequent turns return to the agent's primary model:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    model: openai/gpt-4o-mini\n```\n\nSee [Per-Toolset Model Routing](../../configuration/tools/index.md#per-toolset-model-routing).\n\n### Lifecycle (auto-restart, profiles)\n\nLocal stdio and remote MCP servers are supervised: crashed servers reconnect automatically with exponential backoff. **Remote** MCP servers (Streamable HTTP / SSE) also reconnect after idle/clean connection closes — services like Notion and Linear periodically close idle connections, and Docker Agent reconnects transparently. Tune the policy with the `lifecycle` block:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; auto-restart with backoff\n  - type: mcp\n    command: docker\n    args: [\"mcp\", \"gateway\"]\n    lifecycle:\n      profile: strict      # fail-fast: required, no retries\n```\n\nSee [Toolset Lifecycle](../../configuration/tools/index.md#toolset-lifecycle) for all profiles and tuning knobs, and [`/toolset-restart`](../../features/tui/index.md) to force a reconnect from the TUI.\n\n## Combined Example\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Docker MCP catalog entry\n      - type: mcp\n        ref: docker:duckduckgo\n\n      # Reusable definition from the top-level mcps: block\n      - type: mcp\n        ref: github\n        tools: [\"list_issues\", \"create_issue\"]\n        toon: \"list_.*\"\n\n      # Local stdio server with auto-install\n      - type: mcp\n        command: gopls\n        version: \"golang/tools@v0.21.0\"\n        args: [\"mcp\"]\n\n      # Remote MCP with OAuth (handled automatically)\n      - type: mcp\n        remote:\n          url: \"https://mcp.linear.app/mcp\"\n          transport_type: \"streamable\"\n        instruction: Use Linear for issue tracking.\n```\n\n> [!WARNING]\n> **Toolset order matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins: the duplicate from the later toolset is ignored and a warning identifies both toolsets. Order your toolsets intentionally. To keep both tools callable, give the MCP toolset a unique `name:` (its tools are then exposed as `<name>_<tool>`) or restrict the overlapping toolset with its `tools:` filter.\n\n## See Also\n\n- [Tool Configuration](../../configuration/tools/index.md) — full reference for every toolset type, plus shared options (lifecycle, TOON, model routing, …).\n- [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) — the top-level `mcps:` block.\n- [Remote MCP Servers](../../features/remote-mcp/index.md) — catalog of public remote MCP endpoints + OAuth recipes.\n- [MCP Mode](../../features/mcp-mode/index.md) — expose your own agents as MCP tools to Claude Desktop, Claude Code, etc.\n- [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) — automatic installation of MCP server binaries.\n","frontmatter":{"title":"MCP Tool","description":"Extend agents with external tools via the Model Context Protocol.","keywords":"docker agent, ai agents, tools, toolsets, mcp tool","linkTitle":"MCP","weight":130,"canonical":"https://docs.docker.com/ai/docker-agent/tools/mcp/","aliases":["/ai/docker-agent/integrations/mcp/"]},"isInternal":false,"tokens":3412,"sizeBytes":14524},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md","title":"Memory Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Memory Tool\"\ndescription: \"Persistent key-value storage backed by SQLite for cross-session recall.\"\nkeywords: docker agent, ai agents, tools, toolsets, memory tool\nlinkTitle: \"Memory\"\nweight: 100\ncanonical: https://docs.docker.com/ai/docker-agent/tools/memory/\n---\n\n_Persistent key-value storage backed by SQLite for cross-session recall._\n\n## Overview\n\nThe memory tool provides persistent key-value storage backed by SQLite. Data survives across sessions, allowing agents to remember facts, user preferences, project context, and past decisions. Memories can be organized with categories and searched by keyword.\n\nBy default, the database is stored at `~/.cagent/memory/<config-name>/memory.db`, where `<config-name>` is derived from the loaded configuration (typically the YAML file name) and falls back to `default` when unavailable. When the agent is loaded from an OCI reference (e.g. `docker/my-agent:latest`), characters that are reserved in filesystem paths (such as `:`) are sanitised in the `<config-name>` segment — the agent's display name elsewhere is unchanged. Agents declared in the same configuration share this database by default; set an explicit `path` per toolset to isolate them.\n\n## Available Tools\n\n| Tool              | Description                                                                      |\n| ----------------- | -------------------------------------------------------------------------------- |\n| `add_memory`      | Store a new memory with optional category                                        |\n| `get_memories`    | Retrieve all stored memories                                                     |\n| `delete_memory`   | Delete a specific memory by ID                                                   |\n| `search_memories` | Search memories by keywords and/or category (more efficient than `get_memories`) |\n| `update_memory`   | Update an existing memory's content and/or category by ID                        |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: memory\n```\n\n### Options\n\n| Property | Type   | Default                                   | Description                      |\n| -------- | ------ | ----------------------------------------- | -------------------------------- |\n| `path`   | string | `~/.cagent/memory/<config-name>/memory.db` | Path to the SQLite database file |\n\n### Custom Database Path\n\n```yaml\ntoolsets:\n  - type: memory\n    path: ./agent_memory.db\n```\n\n## Categories\n\nMemories support an optional `category` field for organization and filtering. Common categories include:\n\n- `preference` — User preferences and settings\n- `fact` — Factual information about the project or user\n- `project` — Project-specific context\n- `decision` — Past decisions and their rationale\n\n> [!TIP]\n> Memory is especially useful for long-running assistants that need to recall information across conversations — like coding preferences, project conventions, or context discovered during previous sessions.\n","frontmatter":{"title":"Memory Tool","description":"Persistent key-value storage backed by SQLite for cross-session recall.","keywords":"docker agent, ai agents, tools, toolsets, memory tool","linkTitle":"Memory","weight":100,"canonical":"https://docs.docker.com/ai/docker-agent/tools/memory/"},"isInternal":false,"tokens":559,"sizeBytes":2984},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md","title":"Model-picker Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Model Picker Tool\"\ndescription: \"Let the agent pick between several models per turn.\"\nkeywords: docker agent, ai agents, tools, toolsets, model picker tool\nlinkTitle: \"Model Picker\"\nweight: 200\ncanonical: https://docs.docker.com/ai/docker-agent/tools/model-picker/\n---\n\n_Let the agent pick between several models per turn._\n\n## Overview\n\nThe model picker tool gives an agent the ability to dynamically choose which model to use for each turn of the conversation. This is useful when you want the agent to route different types of requests to different models — for example, using a fast, inexpensive model for simple queries and a more capable model for complex reasoning tasks.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: model_picker\n    models:\n      - openai/gpt-5-mini\n      - anthropic/claude-sonnet-4-5\n      - openai/gpt-5\n```\n\n### Options\n\n| Property | Type           | Required | Description                                                  |\n| -------- | -------------- | -------- | ------------------------------------------------------------ |\n| `models` | array[string]  | ✓        | List of model references the agent can choose from. Use `provider/model` format. |\n\n## How It Works\n\nWhen the model picker toolset is enabled, the agent gets two tools: `change_model` to switch to one of the configured models, and `revert_model` to return to its default model. The agent decides which model to use based on the complexity of the task, cost considerations, or other factors you describe in its instruction.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini  # Default model\n    instruction: |\n      You are a helpful assistant. For simple questions, use gpt-5-mini.\n      For complex reasoning or coding tasks, switch to claude-sonnet-4-5 or gpt-5.\n    toolsets:\n      - type: model_picker\n        models:\n          - openai/gpt-5-mini\n          - anthropic/claude-sonnet-4-5\n          - openai/gpt-5\n```\n\n> [!TIP]\n> **Cost optimization**\n>\n> The model picker tool is particularly useful for cost optimization: let the agent use a cheap model by default and only escalate to expensive models when necessary.\n\n## Tool Interface\n\nThe toolset exposes two tools:\n\n### `change_model`\n\n| Parameter | Type   | Required | Description                                                                 |\n| --------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `model`   | string | ✓        | The model to switch to. Must be one of the configured models.               |\n\n### `revert_model`\n\nTakes no parameters. Reverts the agent to its original/default model.\n\nThe switch takes effect immediately: the next inference call — including the remainder of the current agentic loop — uses the new model.\n","frontmatter":{"title":"Model Picker Tool","description":"Let the agent pick between several models per turn.","keywords":"docker agent, ai agents, tools, toolsets, model picker tool","linkTitle":"Model Picker","weight":200,"canonical":"https://docs.docker.com/ai/docker-agent/tools/model-picker/"},"isInternal":false,"tokens":602,"sizeBytes":2800},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md","title":"Open-url Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Open URL Tool\"\ndescription: \"Open a fixed URL in the user's default browser.\"\nkeywords: docker agent, ai agents, tools, toolsets, open url tool\nlinkTitle: \"Open URL\"\nweight: 40\ncanonical: https://docs.docker.com/ai/docker-agent/tools/open-url/\n---\n\n_Open a fixed URL in the user's default browser._\n\n## Overview\n\nThe `open_url` toolset exposes a single, argument-less tool that opens a URL\nbaked into the toolset definition in the user's default browser. The model\nnever supplies the URL — it just calls the tool by name. Launching the browser\nis cross-platform: Docker Agent uses `open` on macOS, `xdg-open` on Linux, and\n`rundll32` on Windows.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Letting an agent open a dashboard, documentation page, or deep link on demand\n> - Deep-linking into a desktop app via a custom URI scheme (e.g. `docker-desktop://`)\n> - Any \"take me there\" action where the destination is fixed and known up front\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant that can open the dashboard\n    instruction: When the user asks to see the dashboard, call open_dashboard.\n    toolsets:\n      - type: open_url\n        name: open_dashboard\n        url: https://example.com/dashboard\n```\n\n## Properties\n\n| Property | Type   | Required | Description                                                                                          |\n| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |\n| `url`    | string | ✓        | URL to open. Supports `${env.VAR}` interpolation. Any scheme the OS can dispatch is allowed.         |\n| `name`   | string | ✗        | Tool name the agent references. Defaults to `open_url`. Use a descriptive name when configuring several. |\n\n## Multiple URLs\n\nAdd one toolset entry per destination, each with its own `name`:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_dashboard\n    url: https://example.com/dashboard\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## URL Interpolation\n\nThe `url` field supports `${env.VAR}` placeholders, expanded at call time\nagainst the runtime environment:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## Custom URI Schemes\n\nAny scheme the operating system knows how to dispatch works, including deep\nlinks into desktop applications:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_in_docker_desktop\n    url: docker-desktop://dashboard/apps\n```\n\n## Limitations\n\n- The URL must include a scheme (e.g. `https://`); bare paths are rejected.\n- URLs that look like a command-line flag (starting with `-`) are refused to\n  prevent argument injection into the platform `open` helper.\n- The tool opens the URL on the **host** running Docker Agent; in headless or\n  remote environments where no browser/launcher is available, the call fails\n  gracefully and reports the error to the agent.\n\nSee [`examples/open_url.yaml`](https://github.com/docker/docker-agent/blob/main/examples/open_url.yaml) for a complete configuration.\n","frontmatter":{"title":"Open URL Tool","description":"Open a fixed URL in the user's default browser.","keywords":"docker agent, ai agents, tools, toolsets, open url tool","linkTitle":"Open URL","weight":40,"canonical":"https://docs.docker.com/ai/docker-agent/tools/open-url/"},"isInternal":false,"tokens":733,"sizeBytes":3178},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md","title":"Openapi Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"OpenAPI Tool\"\ndescription: \"Automatically generate tools from an OpenAPI specification.\"\nkeywords: docker agent, ai agents, tools, toolsets, openapi tool\nlinkTitle: \"OpenAPI\"\nweight: 230\ncanonical: https://docs.docker.com/ai/docker-agent/tools/openapi/\n---\n\n_Automatically generate tools from an OpenAPI specification._\n\n## Overview\n\nThe OpenAPI tool fetches an OpenAPI 3.x specification from a URL and creates one tool per API operation. Each endpoint's parameters, request body, and description are translated into a callable tool that the agent can invoke directly.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://petstore3.swagger.io/api/v3/openapi.json\"\n```\n\n### With custom headers\n\nPass custom headers to every HTTP request made by the generated tools (for example, for authentication):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    headers:\n      Authorization: \"Bearer ${env.API_TOKEN}\"\n      X-Custom-Header: \"my-value\"\n```\n\n### Custom timeout\n\nOverride the default 30-second HTTP timeout (applies both to fetching the spec and to the generated tool calls):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    timeout: 60\n```\n\n### Reaching internal services\n\nBy default the OpenAPI tool refuses connections to non-public IP addresses, blocking SSRF attempts even when DNS resolves an otherwise-public host to an internal range. Opt in with `allow_private_ips` when the spec or its `servers` entries legitimately target localhost or your internal network:\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"http://localhost:8080/openapi.json\"\n    allow_private_ips: true\n```\n\n## Properties\n\n| Property            | Type              | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `url`               | string            | ✓        | URL of the OpenAPI specification (JSON format). Supports `${env.VAR}` interpolation.                                                                                                                                                                              |\n| `headers`           | map[string]string | ✗        | Custom HTTP headers sent with every request — both the spec fetch and every generated tool call. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request when docker agent is exposed as a server). |\n| `timeout`           | int               | ✗        | HTTP client timeout in seconds (default: `30`). Applies to both the spec fetch and the generated tools' requests.                                                                                                                                                 |\n| `allow_private_ips` | boolean           | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the spec or its servers legitimately target internal services. By default such addresses are refused at dial time, after DNS resolution, so DNS rebinding cannot bypass the check. |\n\n## How it works\n\n1. The spec is fetched from the configured `url` at startup.\n2. Each operation (GET, POST, PUT, …) becomes a separate tool named after its `operationId` (or `method_path` when no `operationId` is set).\n3. Path and query parameters are exposed as tool parameters. Request body properties are prefixed with `body_`.\n4. Read-only operations (GET, HEAD, OPTIONS) are annotated accordingly.\n5. Responses are returned as text; errors include the HTTP status code.\n\n## Limits\n\n- The OpenAPI spec must be **10 MB or less**.\n- Individual API responses are truncated at **1 MB**.\n\n## Example\n\nSee the full [Pet Store example](https://github.com/docker/docker-agent/blob/main/examples/openapi-petstore.yaml) for a working agent configuration.\n","frontmatter":{"title":"OpenAPI Tool","description":"Automatically generate tools from an OpenAPI specification.","keywords":"docker agent, ai agents, tools, toolsets, openapi tool","linkTitle":"OpenAPI","weight":230,"canonical":"https://docs.docker.com/ai/docker-agent/tools/openapi/"},"isInternal":false,"tokens":844,"sizeBytes":4505},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md","title":"Plan Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Plan Tool\"\ndescription: \"Shared persistent scratchpad for multi-agent collaboration.\"\nkeywords: docker agent, ai agents, tools, toolsets, plan tool\nlinkTitle: \"Plan\"\nweight: 150\ncanonical: https://docs.docker.com/ai/docker-agent/tools/plan/\n---\n\n_Shared persistent scratchpad for multi-agent collaboration._\n\n## Overview\n\nThe plan tool gives agents a shared, persistent scratchpad of named documents. Any agent in a multi-agent config that loads the `plan` toolset can read and write the same plans, and those plans survive across sessions. This makes it straightforward to wire a planner agent that sketches work and one or more executor agents that consume it without any custom tool wiring.\n\nPlans are stored as JSON files in the Docker Agent data directory (`~/.cagent/plans/` by default). Agents that share a process serialize on a single mutex, and every write or delete additionally holds an advisory lock on a sentinel file in the plans directory, so writers in *separate* Docker Agent processes are serialized too: concurrent edits can never silently overwrite each other, and a stale revision always fails with a deterministic version conflict. Writes are atomic (temp file + rename), so a reader never observes partial content.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: plan\n```\n\nNo additional options are required. All agents that include `type: plan` in their toolsets share the same plans.\n\n## Available Tools\n\n| Tool                    | Description                                                                                       |\n| ----------------------- | ------------------------------------------------------------------------------------------------- |\n| `write_plan`            | Create or update a shared plan by name. Replaces the entire plan content — read it first to preserve what you want to keep. Each write bumps the revision number. |\n| `read_plan`             | Read a shared plan by name, including its title, content, author, status, revision number, and last-updated timestamp. |\n| `list_plans`            | List all shared plans with their name, title, author, status, revision, and last-updated timestamp. |\n| `delete_plan`           | Delete a shared plan by name.                                                                     |\n| `update_plan_from_file` | Create or update a plan, taking the new content from a file on disk instead of inline. Use it with `export_plan_to_file` to edit a large plan without re-sending its whole body. |\n| `export_plan_to_file`   | Write a plan's content to a file. The content goes to disk and is **not** returned as tool output, so materialising a plan costs no tokens. |\n| `set_plan_status`       | Set a plan's free-form status without rewriting its body. The plan must already exist. |\n| `get_plan_status`       | Read a plan's status and current revision without fetching its body.                  |\n\n### Cheap edits with file-based revisions\n\nRe-sending a whole plan on every revision is expensive. The file-based tools let\nan agent edit a plan without paying input-token cost for its body:\n\n1. `export_plan_to_file` writes the current plan content to a path. The content\n   is written to disk and is **not** returned.\n2. The agent edits that file in place with its filesystem tools.\n3. `update_plan_from_file` commits the file's new contents as the next revision.\n\n### Free-form status\n\nEach plan carries a free-form `status` string. There is no fixed vocabulary:\ndefine your own in the system prompt (e.g. `idle`, `in-progress`, `blocked`,\n`done`, `canceled`). Read and write it independently of the body with\n`get_plan_status` and `set_plan_status`, or pass `status` to `write_plan` and\n`update_plan_from_file`. The TUI surfaces the status next to the plan title.\n\n### Optimistic locking\n\nWhen several sessions edit the same plan, concurrent writes could silently\noverwrite each other. Every read returns a `revision` number; pass the value you\nlast read as `last_known_revision` to `write_plan`, `update_plan_from_file`,\n`set_plan_status`, or `delete_plan`. If the plan changed since (its current\nrevision no longer matches), the write is rejected with a version-conflict\nerror and the caller should re-read the plan and retry. The revision check and\nthe write happen under the storage's cross-process file lock, so the conflict\nis detected reliably even when the competing writer runs in a different Docker\nAgent process. Omit `last_known_revision` to write unconditionally (last\nwriter wins).\n\n### Plan Names\n\nPlan names must match the pattern `[a-z0-9][a-z0-9_-]*` (lowercase letters, digits, `-`, `_`). This is enforced structurally so two different inputs can never collapse onto the same file and path-traversal is impossible by construction.\n\n### Plan Fields\n\nEach plan document contains:\n\n| Field      | Description                                               |\n| ---------- | --------------------------------------------------------- |\n| `name`     | The plan's unique slug name                               |\n| `title`    | A short human-readable title (optional)                   |\n| `content`  | The full Markdown or free-form plan text                  |\n| `author`   | Free-form label identifying who last wrote the plan       |\n| `status`   | Free-form lifecycle label (optional), e.g. `in-progress`  |\n| `revision` | Monotonically increasing version counter, bumped on every write |\n| `updatedAt`| ISO 8601 timestamp of the last write                      |\n\n## Example\n\nTwo agents collaborate on a shared plan — the architect drafts it and the builder refines it:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Coordinator\n    instruction: |\n      Route work between the architect and the builder.\n    handoffs: [architect, builder]\n\n  architect:\n    model: anthropic/claude-sonnet-4-5\n    description: Drafts high-level plans\n    instruction: |\n      Use list_plans and read_plan to inspect existing plans, then write_plan\n      to create or revise one. Always read before writing. When done, hand off\n      to the builder.\n    toolsets:\n      - type: plan\n    handoffs: [builder]\n\n  builder:\n    model: openai/gpt-4o\n    description: Adds implementation steps to plans\n    instruction: |\n      Read the architect's plan with read_plan, then use write_plan to append\n      concrete implementation steps. Always read before writing. When done,\n      hand off back to root.\n    toolsets:\n      - type: plan\n    handoffs: [root]\n```\n\nSee [`examples/shared_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_plan` returns a distinct \"not found\" error when a plan does not exist, as opposed to any other I/O error, so callers can tell \"plan missing\" from \"plan unreadable.\"\n- `list_plans` skips corrupt entries but reports them in a `warnings` field so an agent can detect and recover from a bad state (e.g., by calling `delete_plan`).\n- `delete_plan` can remove a corrupt plan to recover from a bad state.\n\n## Managing plans from the host\n\nShared plans can also be inspected and managed outside a session with the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group: list, get, create, update, set status, export, and delete — with the same optimistic-locking semantics as the tools (`--expected-version` guards a write and a stale version fails with exit code 3; `--force` writes unconditionally). Session plans (the per-session \"draft, review, execute\" plan) can be listed, read, and exported through the same commands but stay owned by their session and cannot be mutated from the host.\n\n```bash\n$ docker agent plans list\n$ docker agent plans get release > plan.md\n$ docker agent plans update release --file ./plan.md --expected-version 1\n```\n\n### The `/plans` browser in the TUI\n\nInside the full-screen TUI, the `/plans` slash command (also in the <kbd>Ctrl</kbd>+<kbd>K</kbd> command palette) opens a plan browser over the same store the agents use, so changes made by agents mid-session appear immediately. The list shows every shared plan plus the current session's [session plan](../session_plan/index.md), with each plan's scope, identity (name, or session ID for the session plan), status, version (`-` for the unversioned session plan), last update time, and title.\n\nKeybindings:\n\n| Key | Action |\n| --- | ------ |\n| <kbd>↑</kbd>/<kbd>↓</kbd>, mouse | Navigate; <kbd>Enter</kbd> or double-click opens a detail view with the full metadata and scrollable markdown content |\n| <kbd>/</kbd> | Filter by name, title, status, or scope (<kbd>Esc</kbd> leaves filter mode) |\n| <kbd>r</kbd> | Refresh from storage |\n| <kbd>x</kbd> | Export the selected plan to `<name>.md` (shared) or `session-plan-<short-id>.md` (session) in the session's working directory. An existing file is never overwritten — the export fails with a notification instead |\n| <kbd>s</kbd> | Set a shared plan's free-form status via a small input dialog |\n| <kbd>e</kbd> | Edit a shared plan's content in `$VISUAL`/`$EDITOR` |\n| <kbd>n</kbd> | Create a new shared plan: pick a name, then draft the content in `$VISUAL`/`$EDITOR` (an empty draft aborts) |\n| <kbd>d</kbd> | Delete a shared plan after a confirmation that names the plan and its version |\n| <kbd>Esc</kbd> | Close the detail view / the browser |\n\nEvery mutation is guarded by the version shown on screen (the same optimistic locking as `last_known_revision`): if an agent changed the plan in the meantime, the write is rejected, a notification reports the current version, the newer content is left intact and re-read into the browser, and an edit draft is kept in a temp file so nothing is lost. Session plans are read-only here — status, edit, and delete report why instead of attempting the write. The browser also refreshes live when agents in the same process write, re-status, or delete plans (and when this session's agent updates its session plan); in the lean TUI, which has no overlays, `/plans` is unavailable.\n\n> [!TIP]\n> **Plan vs. Todo vs. Tasks**\n>\n> Use **plan** for shared, free-form documents that multiple agents collaborate on (design docs, requirements, work items). Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","frontmatter":{"title":"Plan Tool","description":"Shared persistent scratchpad for multi-agent collaboration.","keywords":"docker agent, ai agents, tools, toolsets, plan tool","linkTitle":"Plan","weight":150,"canonical":"https://docs.docker.com/ai/docker-agent/tools/plan/"},"isInternal":false,"tokens":2319,"sizeBytes":10409},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md","title":"Rag Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"RAG Tool\"\ndescription: \"Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search.\"\nkeywords: docker agent, ai agents, tools, toolsets, rag tool\nlinkTitle: \"RAG\"\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/tools/rag/\naliases:\n  - /ai/docker-agent/rag/\n---\n\n_Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search._\n\n## Overview\n\nThe `rag` toolset lets agents search through your documents to find relevant information before responding. Knowledge bases are declared once at the top of the config under `rag:` and then referenced from any agent via `type: rag, ref: <name>`. Docker Agent supports:\n\n- **Background indexing** — Files are indexed automatically and re-indexed on change\n- **Multiple strategies** — Semantic embeddings, BM25 keyword search, and LLM-enhanced search\n- **Hybrid search** — Combine strategies with result fusion for best results\n- **Reranking** — Re-score results with specialized models for improved relevance\n\nRAG is the strategy to reach for when a document collection is too large to inline directly, or gets queried repeatedly across turns/sessions — see [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how it compares to `@`/`/attach` attachments and prompt files.\n\n## Quick Start\n\n```yaml\nrag:\n  my_docs:\n    tool:\n      description: \"Technical documentation\"\n    docs: [./documents, ./some-doc.md]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./docs.db\n        vector_dimensions: 1536\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: |\n      You have access to a knowledge base. Use it to answer questions.\n    toolsets:\n      - type: rag\n        ref: my_docs\n```\n\n## Retrieval Strategies\n\n### Chunked Embeddings (Semantic Search)\n\nUses embedding models to find semantically similar content. Best for understanding intent, synonyms, and paraphrasing.\n\n```yaml\nstrategies:\n  - type: chunked-embeddings\n    embedding_model: openai/text-embedding-3-small\n    database: ./vector.db\n    vector_dimensions: 1536\n    similarity_metric: cosine_similarity\n    threshold: 0.5\n    limit: 10\n    embedding_batch_size: 50\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n### Semantic Embeddings (LLM-Enhanced)\n\nUses an LLM to generate semantic summaries of each chunk before embedding, capturing meaning and intent. Best for code search and understanding implementations.\n\n```yaml\nstrategies:\n  - type: semantic-embeddings\n    embedding_model: openai/text-embedding-3-small\n    vector_dimensions: 1536\n    chat_model: openai/gpt-4o-mini\n    database: ./semantic.db\n    ast_context: true # include AST metadata\n    chunking:\n      size: 1000\n      code_aware: true # AST-aware chunking\n```\n\n> [!NOTE]\n> **Trade-offs**\n>\n> Semantic embeddings provide higher quality retrieval but slower indexing (LLM call per chunk) and additional API costs.\n\n### BM25 (Keyword Search)\n\nTraditional keyword matching using the BM25 algorithm. Best for exact terms, technical jargon, and code identifiers.\n\n```yaml\nstrategies:\n  - type: bm25\n    database: ./bm25.db\n    k1: 1.5 # term frequency saturation\n    b: 0.75 # length normalization\n    threshold: 0.3\n    limit: 10\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n## Hybrid Search\n\nCombine multiple strategies for best results. Strategies run in parallel and results are fused together:\n\n```yaml\nrag:\n  hybrid:\n    docs: [./docs]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./vector.db\n        vector_dimensions: 1536\n        limit: 20\n        chunking: { size: 1000, overlap: 100 }\n      - type: bm25\n        database: ./bm25.db\n        limit: 15\n        chunking: { size: 1000, overlap: 100 }\n    results:\n      fusion:\n        strategy: rrf # Reciprocal Rank Fusion\n        k: 60\n      deduplicate: true\n      limit: 5\n```\n\n## Fusion Strategies\n\n| Strategy   | Best For                          | Description                                                        |\n| ---------- | --------------------------------- | ------------------------------------------------------------------ |\n| `rrf`      | General use (recommended)         | Reciprocal Rank Fusion — rank-based, no score normalization needed |\n| `weighted` | Known performance characteristics | Weight strategies differently (e.g., embeddings: 0.7, BM25: 0.3)   |\n| `max`      | Same scoring scale                | Takes the maximum score from any strategy                          |\n\n## Reranking\n\nRe-score retrieved documents with a specialized model to improve relevance:\n\n```yaml\nresults:\n  reranking:\n    model: openai/gpt-4o-mini\n    top_k: 10 # only rerank top 10\n    threshold: 0.3 # minimum score after reranking\n    criteria: |\n      Prioritize official documentation over blog posts.\n      Prefer recent information and practical examples.\n  limit: 5\n```\n\nSupported reranking providers: **DMR** (native `/rerank` endpoint), **OpenAI**, **Anthropic**, **Gemini**.\n\n## Code-Aware Chunking\n\nFor source code, enable AST-based chunking to keep functions and methods intact:\n\n```yaml\nchunking:\n  size: 2000\n  code_aware: true # Uses tree-sitter for AST-based chunking\n```\n\n> [!NOTE]\n> **Language Support**\n>\n> Currently supports Go (`.go`) files. More languages will be added. Falls back to plain text chunking for unsupported file types.\n\n## Debugging RAG\n\nEnable debug logging to see retrieval details:\n\n```bash\n$ docker agent run config.yaml --debug --log-file debug.log\n```\n\nLook for log tags: `[RAG Manager]`, `[Chunked-Embeddings Strategy]`, `[BM25 Strategy]`, `[RRF Fusion]`, `[Reranker]`.\n\n**Permanent model errors abort early.** If the embedding model, semantic-LLM model, or reranking model returns a permanent error (HTTP 400, 401, 404, or 429 — invalid config, bad auth, unknown model, or rate limit), Docker Agent treats the model configuration as invalid and stops immediately rather than retrying doomed requests:\n\n- **Indexing** — the entire indexing run is aborted after the first permanent failure (including 429). The error is surfaced in the logs so you know immediately if a model name or API key is wrong, rather than silently producing incomplete results.\n- **Reranking** — a permanent error (including 429) permanently disables the reranker for the lifetime of the manager. Subsequent queries fall back to un-reranked results. Only transient errors (5xx, timeouts) fall back and retry on the next query.\n\n> [!TIP]\n> **Examples**\n>\n> See the [RAG examples](https://github.com/docker/docker-agent/tree/main/examples/rag) in the GitHub repo for complete, runnable configurations.\n\n## Configuration Reference\n\n### Top-Level RAG Fields\n\n| Field         | Type     | Default | Description                                                    |\n| ------------- | -------- | ------- | -------------------------------------------------------------- |\n| `docs`        | []string | —       | Document paths/directories (shared across strategies)          |\n| `description` | string   | —       | Human-readable description of this RAG source                  |\n| `respect_vcs` | boolean  | `true`  | Respect `.gitignore` files when indexing documents             |\n| `strategies`  | []object | —       | Array of retrieval strategy configurations                     |\n| `results`     | object   | —       | Post-processing: fusion, reranking, deduplication, final limit |\n\n### Chunked-Embeddings Strategy\n\n| Field                       | Type   | Default             | Description                                                  |\n| --------------------------- | ------ | ------------------- | ------------------------------------------------------------ |\n| `embedding_model`           | string | —                   | **Required.** Embedding model reference                      |\n| `database`                  | string | —                   | Path to local SQLite database                                |\n| `vector_dimensions`         | int    | —                   | Embedding dimensions (e.g., 1536 for text-embedding-3-small) |\n| `similarity_metric`         | string | `cosine_similarity` | Similarity metric                                            |\n| `threshold`                 | float  | `0.5`               | Minimum similarity score (0–1)                               |\n| `limit`                     | int    | `5`                 | Max results from this strategy                               |\n| `embedding_batch_size`      | int    | `50`                | Chunks per embedding request                                 |\n| `max_embedding_concurrency` | int    | `3`                 | Max concurrent embedding requests                            |\n| `chunking.size`             | int    | `1500`              | Chunk size in characters (`4000` when `code_aware` is set)   |\n| `chunking.overlap`          | int    | `75`                | Overlap between chunks in characters                         |\n| `chunking.code_aware`       | bool   | `false`             | AST-based chunking (Go files only)                           |\n\n### Semantic-Embeddings Strategy\n\n| Field                      | Type   | Default    | Description                                                        |\n| -------------------------- | ------ | ---------- | ------------------------------------------------------------------ |\n| `embedding_model`          | string | —          | **Required.** Embedding model reference                            |\n| `chat_model`               | string | —          | **Required.** LLM for generating semantic summaries                |\n| `vector_dimensions`        | int    | —          | **Required.** Embedding dimensions                                 |\n| `database`                 | string | —          | Path to local SQLite database                                      |\n| `semantic_prompt`          | string | (built-in) | Custom prompt template (`${path}`, `${content}`, `${ast_context}`) |\n| `ast_context`              | bool   | `false`    | Include tree-sitter AST metadata in prompts                        |\n| `threshold`                | float  | `0.5`      | Minimum similarity score (0–1)                                     |\n| `limit`                    | int    | `5`        | Max results                                                        |\n| `max_indexing_concurrency` | int    | `3`        | Max concurrent file indexing                                       |\n| `chunking.size`            | int    | `1500`     | Chunk size in characters (`4000` when `code_aware` is set)         |\n| `chunking.overlap`         | int    | `75`       | Overlap between chunks                                             |\n| `chunking.code_aware`      | bool   | `false`    | AST-based chunking                                                 |\n\n### BM25 Strategy\n\n| Field              | Type   | Default | Description                                     |\n| ------------------ | ------ | ------- | ----------------------------------------------- |\n| `database`         | string | —       | Path to local SQLite database                   |\n| `k1`               | float  | `1.5`   | Term frequency saturation (1.2–2.0 recommended) |\n| `b`                | float  | `0.75`  | Length normalization (0–1)                      |\n| `threshold`        | float  | `0.0`   | Minimum BM25 score                              |\n| `limit`            | int    | `5`     | Max results                                     |\n| `chunking.size`    | int    | `1500`  | Chunk size in characters                        |\n| `chunking.overlap` | int    | `75`    | Overlap between chunks                          |\n\n### Results (Post-Processing)\n\n| Field                 | Type   | Default | Description                                                 |\n| --------------------- | ------ | ------- | ----------------------------------------------------------- |\n| `fusion.strategy`     | string | `rrf`   | Fusion method: `rrf`, `weighted`, or `max`                  |\n| `fusion.k`            | int    | `60`    | RRF rank constant                                           |\n| `deduplicate`         | bool   | `true`  | Remove duplicate results                                    |\n| `limit`               | int    | `15`    | Final number of results                                     |\n| `include_score`       | bool   | `false` | Include relevance scores in results                         |\n| `return_full_content` | bool   | `false` | Return full document content instead of just matched chunks |\n| `reranking.model`     | string | —       | Reranking model reference                                   |\n| `reranking.top_k`     | int    | (`limit`) | Only rerank top K results. Defaults to the results `limit` when set.  |\n| `reranking.threshold` | float  | `0.5`   | Minimum relevance score after reranking                     |\n| `reranking.criteria`  | string | —       | Custom relevance guidance for the reranking model           |\n","frontmatter":{"title":"RAG Tool","description":"Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search.","keywords":"docker agent, ai agents, tools, toolsets, rag tool","linkTitle":"RAG","weight":110,"canonical":"https://docs.docker.com/ai/docker-agent/tools/rag/","aliases":["/ai/docker-agent/rag/"]},"isInternal":false,"tokens":2861,"sizeBytes":13261},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md","title":"Scheduler Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Scheduler Tool\"\ndescription: \"Schedule instructions to run at a time or on a recurring interval.\"\nkeywords: docker agent, ai agents, tools, toolsets, scheduler tool, cron\nlinkTitle: \"Scheduler\"\nweight: 135\ncanonical: https://docs.docker.com/ai/docker-agent/tools/scheduler/\n---\n\n_Schedule instructions to run at a time or on a recurring interval._\n\n## Overview\n\nThe scheduler toolset lets an agent make something happen at a chosen time or on a repeating cadence during a session. You give it an instruction and a schedule; when the schedule is due, the instruction is delivered back to the agent, which then carries out the action with its normal tools (`shell`, `api`, `fetch`, and so on).\n\nThe scheduler does not run shell or API calls itself. When a schedule fires it injects the instruction into the agent loop via the runtime's recall mechanism — the same primitive [`background_jobs`](../background-jobs/index.md) uses to report completed work — and the agent decides how to act. This keeps every action under the agent's normal tools and permissions rather than adding a second, unattended\ncommand runner.\n\n> [!NOTE]\n> Schedules only fire while the session is running (interactive TUI or a server mode) and are not persisted across restarts. Scheduling requires a host that supports recall; if it does not, `create_schedule` returns an error.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: scheduler\n```\n\nNo configuration options.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `create_schedule` | Register an instruction to run at a time or interval. |\n| `list_schedules` | List active schedules with their id, spec, and next fire time. |\n| `cancel_schedule` | Remove a schedule by id. |\n\n### `create_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `prompt` | Yes | The instruction to deliver to the agent when the schedule fires. |\n| `when` | Yes | When to fire (see [Schedule specs](#schedule-specs)). |\n| `name` | No | Optional human-readable label. |\n\nReturns the new schedule's id and its next fire time.\n\n### `cancel_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `id` | Yes | The id of the schedule to cancel (from `create_schedule` or `list_schedules`). |\n\n## Schedule specs\n\nThe `when` argument accepts:\n\n| Form | Meaning | Example |\n| --- | --- | --- |\n| `in:<duration>` | One-shot, after a delay | `in:10m` |\n| `at:<RFC3339>` | One-shot, at an absolute future time | `at:2026-07-14T09:00:00Z` |\n| `every:<duration>` | Recurring, at a fixed interval | `every:1h` |\n| `minutely` / `hourly` / `daily` / `weekly` | Recurring preset intervals | `hourly` |\n\nDurations use Go's duration syntax (`30s`, `15m`, `2h`). Preset and `every:` intervals are measured from the schedule's creation time (for example `hourly` fires every hour after it is created), not aligned to wall-clock slots.\n\n> [!IMPORTANT]\n> **Recurring schedules have a one-minute minimum.** Every fire injects a message into the agent loop and typically costs an LLM turn, so `every:` values below `1m` are rejected — a typo such as `every:1s` in place of `every:1h` would otherwise become a runaway token burn. One-shot schedules (`in:` / `at:`) are not restricted, since they fire once.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A monitoring assistant\n    instruction: |\n      Every 15 minutes, run `git fetch` and tell me if origin/main moved.\n    toolsets:\n      - type: scheduler\n      - type: shell\n```\n\nThe agent calls:\n\n```text\ncreate_schedule(prompt=\"Run git fetch and report if origin/main moved\", when=\"every:15m\")\n```\n\nEvery 15 minutes it is reminded, runs the command with the `shell` tool, and reports back.\n\n> [!TIP]\n> **When to use**\n>\n> Use the scheduler for recurring monitoring, timed one-shots, and unattended housekeeping loops during a long-running session. For work that should run immediately and be awaited, use [`background_jobs`](../background-jobs/index.md) instead.\n","frontmatter":{"title":"Scheduler Tool","description":"Schedule instructions to run at a time or on a recurring interval.","keywords":"docker agent, ai agents, tools, toolsets, scheduler tool, cron","linkTitle":"Scheduler","weight":135,"canonical":"https://docs.docker.com/ai/docker-agent/tools/scheduler/"},"isInternal":false,"tokens":980,"sizeBytes":3984},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/script/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/script/index.md","title":"Script Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Script Tool\"\ndescription: \"Define custom shell scripts as named tools with typed parameters.\"\nkeywords: docker agent, ai agents, tools, toolsets, script tool\nlinkTitle: \"Script\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/tools/script/\n---\n\n_Define custom shell scripts as named tools with typed parameters._\n\n## Overview\n\nThe script tool lets you define custom shell scripts as named tools. Unlike the generic [shell tool](../shell/index.md) where the agent writes the command, script tools execute predefined commands — ideal for exposing safe, well-scoped operations with descriptive names.\n\n## Configuration\n\n### Simple Scripts\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      run_tests:\n        cmd: task test\n        description: Run the project test suite\n      lint:\n        cmd: task lint\n        description: Run the linter\n```\n\n### Scripts with Parameters\n\nUse `${param}` interpolation and JSON Schema to define typed arguments:\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      deploy:\n        cmd: ./scripts/deploy.sh ${env}\n        description: Deploy to an environment\n        args:\n          env:\n            type: string\n            enum: [staging, production]\n        required: [env]\n```\n\n## Properties\n\n| Property                          | Type   | Description                                                |\n| --------------------------------- | ------ | ---------------------------------------------------------- |\n| `shell.<name>.cmd`                | string | Shell command to execute (supports `${arg}` interpolation) |\n| `shell.<name>.description`        | string | Description shown to the model                             |\n| `shell.<name>.args`               | object | Parameter definitions (JSON Schema properties)             |\n| `shell.<name>.required`           | array  | Required parameter names                                   |\n| `shell.<name>.env`                | object | Environment variables for this script                      |\n| `shell.<name>.working_dir`        | string | Working directory for script execution                     |\n\n> [!TIP]\n> **Script vs. Shell**\n>\n> Use the [shell tool](../shell/index.md) when the agent needs to run arbitrary commands. Use the script tool when you want to expose specific, predefined operations with clear names and typed parameters — giving the agent less freedom but more safety.\n","frontmatter":{"title":"Script Tool","description":"Define custom shell scripts as named tools with typed parameters.","keywords":"docker agent, ai agents, tools, toolsets, script tool","linkTitle":"Script","weight":30,"canonical":"https://docs.docker.com/ai/docker-agent/tools/script/"},"isInternal":false,"tokens":479,"sizeBytes":2415},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md","title":"Session_context Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Session Context Tool\"\ndescription: \"Reference a previous session as context in the current one.\"\nkeywords: docker agent, ai agents, tools, toolsets, session context tool\nlinkTitle: \"Session Context\"\nweight: 210\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_context/\n---\n\n_Reference a previous session as context, without manual export/import._\n\n## Overview\n\nThe `session_context` toolset lets an agent discover earlier sessions and pull one in as context for the current session. It removes the manual workaround of exporting a conversation to HTML and re-attaching it with an `@` mention.\n\nThe tool surface is two read-only tools:\n\n| Tool            | Description                                                                                                  |\n| --------------- | ------------------------------------------------------------------------------------------------------------ |\n| `list_sessions` | List previous sessions (most recent first) with id, title, creation time and message count.                  |\n| `read_session`  | Return the transcript of a previous session, by id or by a relative reference like `-1`.                      |\n\nThe session the agent is currently running in is never listed by `list_sessions` and cannot be read by `read_session` (a circular reference returns an error).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_context\n```\n\nNo configuration options. Both tools are read-only and operate against the same session store the runtime already uses for persistence.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that may browse but never pull a full transcript into context.\ntoolsets:\n  - type: session_context\n    tools:\n      - list_sessions\n```\n\n## Selecting a session\n\n`read_session` accepts either form:\n\n- A concrete id returned by `list_sessions`, e.g. `read_session(\"a1b2c3...\")`.\n- A relative reference: `-1` is the most recent session, `-2` the second most recent, and so on. Relative references resolve against the same ordering `list_sessions` uses (most recent first), excluding sub-sessions.\n\n## Transcript size\n\nA long session could overflow the current context window, so `read_session` caps the rendered transcript. When a transcript is larger than the budget, the oldest messages are dropped (the most recent are usually the most useful for continuing work) and a note records how many were omitted:\n\n```text\n[12 earlier message(s) omitted to fit the context budget; showing the most recent 8]\n```\n\n## Notes\n\n- `list_sessions` defaults to 20 sessions and is capped at 100; pass `limit` to request fewer.\n- `read_session` returns an error when the session is not found, when the reference cannot be resolved, or when it points at the current session.\n- Both tools are read-only: they never modify, branch, or delete sessions.\n\n## Example\n\nSee [`examples/session_context.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_context.yaml) for a complete working example.\n","frontmatter":{"title":"Session Context Tool","description":"Reference a previous session as context in the current one.","keywords":"docker agent, ai agents, tools, toolsets, session context tool","linkTitle":"Session Context","weight":210,"canonical":"https://docs.docker.com/ai/docker-agent/tools/session_context/"},"isInternal":false,"tokens":614,"sizeBytes":3028},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md","title":"Session_plan Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Session Plan Tool\"\ndescription: \"Per-session plan tracker for the draft, review, execute workflow.\"\nkeywords: docker agent, ai agents, tools, toolsets, session plan tool\nlinkTitle: \"Session Plan\"\nweight: 160\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_plan/\n---\n\n_Per-session plan tracker for the \"draft, review, execute\" workflow._\n\n## Overview\n\nThe `session_plan` toolset gives one agent a place to write a plan for the current session, signal that the plan is ready, and let the host route the next turn to an executing agent.\n\nDifferent from the [`plan` toolset](../plan/index.md) — `plan` is for shared, named plans multiple agents collaborate on over many sessions. `session_plan` is for one ephemeral plan per session, scoped to that session by ID.\n\nPlans live as Markdown files under:\n\n```text\n~/.cagent/session_plans/<session-id>.md\n```\n\nThe tool surface is three tools:\n\n| Tool                 | Description                                                                                          |\n| -------------------- | ---------------------------------------------------------------------------------------------------- |\n| `write_session_plan` | Create or replace this session's plan as markdown. There's exactly one plan per session.             |\n| `read_session_plan`  | Read the plan written for the current session and return it as markdown.                             |\n| `exit_plan_mode`     | Signal that the plan is ready for review. Does not switch agents on its own.                         |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_plan\n```\n\nNo configuration options. The plan path is derived from the session ID; the agent does not name plans.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that consumes a plan but should not be able to (re)write or finalize one.\ntoolsets:\n  - type: session_plan\n    tools:\n      - read_session_plan\n```\n\n## When to call exit_plan_mode\n\nCall `exit_plan_mode` once the plan is complete and you do not intend to change it on the next turn. It validates that a plan exists for the session and returns a \"ready for review\" tool result. It does **not** switch agents or solicit user approval on its own — the host application owns the next-turn routing (for example, by reading the tool result, by a UI affordance the user toggles, or by a `handoff` declared on the agent).\n\nThis separation keeps the tool reusable across UIs: a CLI that prints tool results inline, a chat UI with a plan-mode toggle, and a server that auto-routes the next turn through a `handoff` can all consume the same signal without one stepping on another.\n\n## Storage and cleanup\n\n- Plans are markdown files written atomically (temp + rename), so concurrent readers — in this process or another — never observe a partial write.\n- A best-effort sweep on first use of the toolset removes plan files older than 30 days under the plans directory. Stranded plans for long-gone sessions do not accumulate.\n- The session ID identifies the file directly. There is no in-process mutex or revision counter, because two sessions cannot map to the same path.\n\n## Events\n\nA `session_plan_updated` event is emitted whenever `write_session_plan` succeeds:\n\n```json\n{\n  \"type\": \"session_plan_updated\",\n  \"session_id\": \"...\",\n  \"path\": \"/Users/.../.cagent/session_plans/<session-id>.md\",\n  \"content\": \"# my plan\\n...\",\n  \"agent_name\": \"planner\"\n}\n```\n\nEmbedders that render the plan inline can subscribe and update without re-reading the file.\n\n## Managing session plans from the host\n\nA session plan belongs to its session: hosts can read and export it, never change it.\n\n- **CLI** — the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group lists, reads (`get --session <session-id>`), and exports session plans alongside shared plans. Mutations (`update`, `status`, `delete`) are refused with an `unsupported` error explaining the ownership rule.\n- **TUI** — the `/plans` browser (see the [plan toolset docs](../plan/index.md#the-plans-browser-in-the-tui) for the full keybinding table) includes the **current session's** plan as the `session` scope row; plans of other sessions are never enumerated. Its identity is the session ID and its version column shows `-` — session plans have no versions. <kbd>Enter</kbd> opens the detail view (scope, session ID, update time, scrollable markdown) and <kbd>x</kbd> exports to `session-plan-<short-id>.md` in the working directory (refusing to overwrite an existing file). <kbd>e</kbd> opens the plan body in your external editor (`$VISUAL` or `$EDITOR`) for editing — the write is unguarded and last-write-wins by design. Status and delete visibly report that session plans don't support them (session plans belong to their session and carry no shared-plan metadata). The browser refreshes live on the `session_plan_updated` event, so a plan the agent just wrote appears without reopening.\n\n## Example\n\nA two-agent workflow: `root` executes, `planner` plans. `/plan` hands off to the planner; `exit_plan_mode` signals \"ready\", and the host decides what happens next.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Executes approved plans\n    instruction: |\n      You execute plans the planner has handed off. When you see a message\n      that a plan has been approved, read it with read_session_plan and work\n      through its steps in order.\n    toolsets:\n      - type: session_plan\n        tools:\n          - read_session_plan\n      - type: filesystem\n      - type: shell\n    commands:\n      plan:\n        description: \"Switch to the planner\"\n        agent: planner\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Investigates and writes plans for review\n    instruction: |\n      Investigate the user's request, then write the plan with\n      write_session_plan. Iterate with the user until the plan is complete,\n      then call exit_plan_mode to mark it ready for review.\n    toolsets:\n      - type: session_plan\n      - type: filesystem\n        readonly: true\n      - type: user_prompt\n```\n\nSee [`examples/session_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_session_plan` and `exit_plan_mode` return a \"no plan written yet\" error when called before `write_session_plan`.\n- `write_session_plan` validates the session ID and refuses to write anything that could escape the plans directory; in practice the runtime generates UUIDs so this only triggers if an embedder supplies a hand-crafted ID.\n\n> [!TIP]\n> **session_plan vs. plan vs. todo vs. tasks**\n>\n> Use **session_plan** when one agent drafts an approach for the user to review before another agent executes it (ephemeral, one per session). Use [plan](../plan/index.md) for shared, named plans multiple agents collaborate on over many sessions. Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n","frontmatter":{"title":"Session Plan Tool","description":"Per-session plan tracker for the draft, review, execute workflow.","keywords":"docker agent, ai agents, tools, toolsets, session plan tool","linkTitle":"Session Plan","weight":160,"canonical":"https://docs.docker.com/ai/docker-agent/tools/session_plan/"},"isInternal":false,"tokens":1584,"sizeBytes":7131},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md","title":"Shell Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Shell Tool\"\ndescription: \"Execute arbitrary shell commands in the user's environment.\"\nkeywords: docker agent, ai agents, tools, toolsets, shell tool\nlinkTitle: \"Shell\"\nweight: 20\ncanonical: https://docs.docker.com/ai/docker-agent/tools/shell/\n---\n\n_Execute arbitrary shell commands in the user's environment._\n\n## Overview\n\nThe shell tool allows agents to execute arbitrary shell commands synchronously. This is one of the most powerful tools — it lets agents run builds, install dependencies, query APIs, and interact with the system. Each call runs in a fresh, isolated shell session — no state persists between calls.\n\nCommands have a default 30-second timeout and require user confirmation unless `--yolo` is used. For servers, watchers, and other long-running commands, add the [`background_jobs`](../background-jobs/index.md) toolset alongside `shell`.\n\n### Shell interpreter detection\n\nThe shell tool automatically detects and names the resolved shell interpreter (e.g., `bash`, `zsh`, `powershell`, `pwsh`, `cmd`) in its description to the model, along with the operating system (Linux, macOS, Windows). This helps models use the correct shell syntax for the host environment.\n\nFor example:\n\n- On Linux with bash: \"Executes the given shell command with bash on Linux.\"\n- On Windows with PowerShell: \"Executes the given shell command with powershell on Windows. Use Windows PowerShell 5.1 syntax: chain commands with \";\" (not \"&&\"), and avoid POSIX commands/flags like \"ls -la\".\"\n\nThis reduces wasted turns where models assume POSIX syntax on Windows or vice versa.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                          |\n| -------------- | ------- | --------------------------------------------------------------------------------------------------- |\n| `env`          | object  | Environment variables to set for all shell commands                                                 |\n| `safer`        | boolean | Deprecated and ignored — shell commands are always classified now (see [Command classification](#command-classification)). Kept so existing YAMLs still parse. |\n| `sudo_askpass` | boolean | Opt in to prompting for a `sudo` password (see [Sudo support](#sudo-support)). Default `false`.     |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: shell\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Command classification\n\nEvery shell command is classified against an embedded taxonomy before the approval decision — no opt-in required:\n\n- **Destructive matches** (`rm -rf <path>`, `docker volume rm`, `mkfs`, `dd if=… of=/dev/<disk>`, …) are labelled `destructive` with a `blast_radius` (`low` / `medium` / `high`) and a `category` tag. The TUI confirmation dialog renders the blast radius with a color badge.\n- **Known-safe reads** (`ls`, `cat`, `git status`, `git diff`, `docker ps`, `docker logs`, `kubectl get`, …) are labelled `safe`.\n- **Everything else** is labelled `unknown`.\n\nThe session's [safety mode](../../configuration/permissions/index.md#safety-modes) decides what each label means: `strict` asks about everything, `balanced` auto-runs safe commands and asks about destructive/unknown ones, `restricted` auto-runs safe commands and denies destructive/unknown ones without asking (fail-closed for unattended runs), `autonomous` runs everything. Custom permission rules always win over the mode.\n\nCompound shell (`a && b`, `a; b`, `a | b`) is never matched against the safe allowlist; any destructive segment falls through to ask. The full taxonomy lives in [`pkg/safety/safety_patterns.json`](https://github.com/docker/docker-agent/blob/main/pkg/safety/safety_patterns.json).\n\nSee [`examples/safety_modes.yaml`](https://github.com/docker/docker-agent/blob/main/examples/safety_modes.yaml) for a full example. The legacy `safer: true` toolset flag is deprecated and ignored.\n\n### Sudo support\n\nBy default a shell command has no controlling terminal, so a `sudo` command that needs a password hangs until it times out (the agent usually gives up and falls back to printing manual instructions).\n\nSet `sudo_askpass: true` to enable a sudo privilege escalation flow:\n\n```yaml\ntoolsets:\n  - type: shell\n    sudo_askpass: true\n```\n\nWhen enabled, `sudo` commands prompt you for your password through the host UI (the input is masked). The password is handed to `sudo` over a private, per-session socket via the standard `SUDO_ASKPASS` mechanism — it is never written to the command line, the logs, or stored by the agent.\n\nThe bridge environment variables (`SUDO_ASKPASS`, `CAGENT_ASKPASS_SOCKET`, `CAGENT_ASKPASS_TOKEN`) are added only to commands that invoke `sudo`, but within such a command they are visible to every child process, not just `sudo`. They carry a socket path and a session token, not the password; the socket lives in a `0700` directory, so only your own user can reach it.\n\nNotes and limitations:\n\n- Unix only. The flag has no effect on Windows.\n- Interactive UI only. In headless / non-interactive runs the prompt is declined automatically and `sudo` fails as before.\n- Only a bare `sudo ...` invocation in a POSIX shell (`sh`, `bash`, `zsh`, ...) is handled. `sudo` called by absolute path (`/usr/bin/sudo`), via `env sudo`, from inside a nested script, or under a non-POSIX shell (e.g. `fish`) is not intercepted and behaves as before.\n- Caching is `sudo`'s own. Because each shell tool call runs in a fresh shell with no controlling terminal, `sudo`'s credential cache does not persist across separate tool calls: you are prompted once per shell command that uses `sudo`. Within a single command, multiple `sudo` calls (e.g. `sudo a && sudo b`) usually share one prompt, subject to `sudo`'s own timestamp configuration.\n- The prompt must be answered within the command's timeout; raise the `timeout` parameter for `sudo` commands that may wait on input.\n- Prompts are serialized: if a single command runs two `sudo` calls in parallel (e.g. `sudo a & sudo b`), the second waits for the first prompt to be answered rather than opening two dialogs at once.\n\n## Available Tools\n\nThe shell toolset exposes one tool:\n\n| Tool Name | Description                                                                  |\n| --------- | ---------------------------------------------------------------------------- |\n| `shell`   | Run a command synchronously and return its combined output when it finishes. |\n\n### `shell` parameters\n\n| Parameter | Type    | Required | Description                                                               |\n| --------- | ------- | -------- | ------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute.                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                   |\n| `timeout` | integer | ✗        | Per-call execution timeout in seconds (default: `30`).                    |\n\n> [!WARNING]\n> **Safety**\n>\n> The shell tool gives agents full access to the system shell. Always set `max_iterations` on agents that use the shell tool to prevent infinite loops. A value of 20–50 is typical for development agents. Use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing shell commands. Use `--yolo` to auto-approve all tool calls.\n","frontmatter":{"title":"Shell Tool","description":"Execute arbitrary shell commands in the user's environment.","keywords":"docker agent, ai agents, tools, toolsets, shell tool","linkTitle":"Shell","weight":20,"canonical":"https://docs.docker.com/ai/docker-agent/tools/shell/"},"isInternal":false,"tokens":1646,"sizeBytes":7634},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md","title":"Tasks Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Tasks Tool\"\ndescription: \"Persistent task database with priorities and dependencies, shared across sessions.\"\nkeywords: docker agent, ai agents, tools, toolsets, tasks tool\nlinkTitle: \"Tasks\"\nweight: 180\ncanonical: https://docs.docker.com/ai/docker-agent/tools/tasks/\n---\n\n_Persistent task database with priorities and dependencies, shared across sessions._\n\n## Overview\n\nThe tasks tool provides a persistent task database that survives across agent sessions. Unlike the [Todo tool](../todo/index.md), which maintains an in-memory task list for the current session only, the tasks tool stores tasks in a JSON file on disk so they can be accessed and updated across multiple sessions. Tasks support priorities and dependencies — a task is _blocked_ until every task it depends on is `done`.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: tasks\n    path: ./tasks.json  # Optional: custom database path\n```\n\n### Options\n\n| Property | Type   | Default       | Description                                                                                                                  |\n| -------- | ------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `path`   | string | `tasks.json`  | Path to the JSON task database. Relative paths resolve against the agent config directory (or `--working-dir` when set).     |\n\n## Available Tools\n\nThe tasks toolset exposes these tools:\n\n| Tool Name           | Description                                                                                                              |\n| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `create_task`       | Create a new task with a title, description (or markdown file path), optional priority, and optional dependencies.       |\n| `get_task`          | Get full details of a single task by ID, including its effective status (`blocked` if any dependency is not `done`).     |\n| `update_task`       | Update a task's title, description, priority, status, or dependency list.                                                |\n| `delete_task`       | Delete a task by ID. Also removes it from other tasks' dependency lists.                                                 |\n| `list_tasks`        | List tasks sorted by priority (critical first) with blocked tasks last. Optionally filter by status or priority.         |\n| `next_task`         | Return the highest-priority actionable task — one that is not blocked and not done. Great for \"what should I work on?\". |\n| `add_dependency`    | Add a dependency: a task is blocked until the task it depends on is `done`.                                              |\n| `remove_dependency` | Remove a dependency from a task.                                                                                         |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    toolsets:\n      - type: tasks\n        path: ./project-tasks.json\n```\n\n> [!TIP]\n> **Tasks vs. Todo**\n>\n> Use the **tasks** tool when you need persistence across sessions, priorities, or dependencies (e.g., long-running projects, recurring work). Use the [todo tool](../todo/index.md) for ephemeral, session-scoped task lists.\n","frontmatter":{"title":"Tasks Tool","description":"Persistent task database with priorities and dependencies, shared across sessions.","keywords":"docker agent, ai agents, tools, toolsets, tasks tool","linkTitle":"Tasks","weight":180,"canonical":"https://docs.docker.com/ai/docker-agent/tools/tasks/"},"isInternal":false,"tokens":618,"sizeBytes":3350},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/think/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/think/index.md","title":"Think Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Think Tool\"\ndescription: \"Step-by-step reasoning scratchpad for planning and decision-making.\"\nkeywords: docker agent, ai agents, tools, toolsets, think tool\nlinkTitle: \"Think\"\nweight: 140\ncanonical: https://docs.docker.com/ai/docker-agent/tools/think/\n---\n\n_Step-by-step reasoning scratchpad for planning and decision-making._\n\n## Overview\n\nThe think tool is a reasoning scratchpad that lets agents think step-by-step before acting. The agent can write its thoughts without producing visible output to the user — ideal for planning complex tasks, breaking down problems, and reasoning through multi-step solutions.\n\nThis is a lightweight tool with no side effects. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: think\n```\n\nNo configuration options.\n\n> [!TIP]\n> **When to use**\n>\n> Use the think tool with models that don't have native reasoning capabilities. If your model already supports a [thinking budget](../../configuration/models/index.md#thinking-budget), you likely don't need this tool.\n","frontmatter":{"title":"Think Tool","description":"Step-by-step reasoning scratchpad for planning and decision-making.","keywords":"docker agent, ai agents, tools, toolsets, think tool","linkTitle":"Think","weight":140,"canonical":"https://docs.docker.com/ai/docker-agent/tools/think/"},"isInternal":false,"tokens":277,"sizeBytes":1337},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md","title":"Todo Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Todo Tool\"\ndescription: \"Task list management for complex multi-step workflows.\"\nkeywords: docker agent, ai agents, tools, toolsets, todo tool\nlinkTitle: \"Todo\"\nweight: 170\ncanonical: https://docs.docker.com/ai/docker-agent/tools/todo/\n---\n\n_Task list management for complex multi-step workflows._\n\n## Overview\n\nThe todo tool provides task list management. Agents can create, update, list, and track progress on tasks with status tracking (pending, in-progress, completed). Useful for complex multi-step workflows where the agent needs to stay organized and ensure all steps are completed.\n\n## Available Tools\n\n| Tool           | Description                              |\n| -------------- | ---------------------------------------- |\n| `create_todo`  | Create a new task                        |\n| `create_todos` | Create multiple tasks at once            |\n| `update_todos` | Update status of one or more tasks       |\n| `list_todos`   | List all current tasks with their status |\n\n### Task Statuses\n\n| Status        | Description                  |\n| ------------- | ---------------------------- |\n| `pending`     | Task has not been started    |\n| `in-progress` | Task is currently being done |\n| `completed`   | Task is finished             |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: todo\n```\n\n### Options\n\n| Property | Type    | Default | Description                                                             |\n| -------- | ------- | ------- | ----------------------------------------------------------------------- |\n| `shared` | boolean | `false` | When `true`, todos are shared across all agents in a multi-agent config |\n\n### Shared Todos\n\nIn multi-agent setups, enable shared todos so all agents can see and update the same task list:\n\n```yaml\ntoolsets:\n  - type: todo\n    shared: true\n```\n","frontmatter":{"title":"Todo Tool","description":"Task list management for complex multi-step workflows.","keywords":"docker agent, ai agents, tools, toolsets, todo tool","linkTitle":"Todo","weight":170,"canonical":"https://docs.docker.com/ai/docker-agent/tools/todo/"},"isInternal":false,"tokens":367,"sizeBytes":1821},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md","title":"Transfer-task Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Transfer Task Tool\"\ndescription: \"Delegate tasks to sub-agents in multi-agent setups.\"\nkeywords: docker agent, ai agents, tools, toolsets, transfer task tool\nlinkTitle: \"Transfer Task\"\nweight: 80\ncanonical: https://docs.docker.com/ai/docker-agent/tools/transfer-task/\n---\n\n_Delegate tasks to sub-agents in multi-agent setups._\n\n## Overview\n\nThe `transfer_task` tool allows an agent to delegate tasks to specialized sub-agents and receive their results. This is the core mechanism for multi-agent orchestration.\n\n**You don't need to add it manually** — it's automatically available when an agent has `sub_agents` configured.\n\n## Configuration\n\nThe tool is enabled implicitly when `sub_agents` is set:\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Coordinates work across specialists\n    instruction: Analyze requests and delegate to the right specialist.\n    sub_agents: [developer, researcher]\n\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: Write clean, production-ready code.\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Search for information online.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\nThe coordinator agent automatically gets a `transfer_task` tool that can delegate to `developer` or `researcher`.\n\n## Tool Interface\n\nThe `transfer_task` tool takes three parameters:\n\n| Parameter         | Type   | Required | Description                                                                                 |\n| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to delegate to. Must be listed under the caller's `sub_agents`.        |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.                        |\n| `expected_output` | string | ✓        | Description of the result/format the caller expects back.                                   |\n\nThe call blocks until the sub-agent returns its result, which becomes the tool's response. For non-blocking parallel delegation, use [`background_agents`](../background-agents/index.md) instead.\n\n## Delegation Limits\n\nSub-agents can have `sub_agents` of their own, so multi-level delegation chains are supported. Two runtime guards keep chains sane, applied to both `transfer_task` and `run_background_agent`:\n\n- **Cycles are rejected.** A delegation targeting an agent that is already part of the active delegation chain (for example `a -> b -> a`) fails with an error naming the cycle.\n- **Depth is capped at 10 nested delegations.** The root agent delegating to its first sub-agent counts as depth 1; a call that would exceed the cap fails with an error stating the attempted depth.\n\nA rejected delegation returns a tool error to the calling agent and never starts the sub-agent.\n\n> [!TIP]\n> **See also**\n>\n> For parallel task delegation, see [Background Agents](../background-agents/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n","frontmatter":{"title":"Transfer Task Tool","description":"Delegate tasks to sub-agents in multi-agent setups.","keywords":"docker agent, ai agents, tools, toolsets, transfer task tool","linkTitle":"Transfer Task","weight":80,"canonical":"https://docs.docker.com/ai/docker-agent/tools/transfer-task/"},"isInternal":false,"tokens":687,"sizeBytes":3284},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md","title":"User-prompt Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"User Prompt Tool\"\ndescription: \"Ask the user questions and collect interactive input during agent execution.\"\nkeywords: docker agent, ai agents, tools, toolsets, user prompt tool\nlinkTitle: \"User Prompt\"\nweight: 190\ncanonical: https://docs.docker.com/ai/docker-agent/tools/user-prompt/\n---\n\n_Ask the user questions and collect interactive input during agent execution._\n\n## Overview\n\nThe user prompt tool allows agents to ask questions and collect input from users during execution. This enables interactive workflows where the agent needs clarification, confirmation, or additional information before proceeding.\n\n> [!NOTE]\n> **When to Use**\n>\n> - When the agent needs clarification before proceeding\n> - Collecting credentials or configuration values\n> - Presenting choices and getting user decisions\n> - Confirming destructive or important actions\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Interactive assistant\n    instruction: |\n      You are a helpful assistant. When you need information\n      from the user, use the user_prompt tool to ask them.\n    toolsets:\n      - type: user_prompt\n      - type: filesystem\n      - type: shell\n```\n\n## Tool Interface\n\nThe `user_prompt` tool takes these parameters:\n\n| Parameter | Type   | Required | Description                                                                                        |\n| --------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |\n| `message` | string | ✓        | The question or prompt to display.                                                                 |\n| `title`   | string | ✗        | Optional title for the dialog window in the TUI. Defaults to `\"Question\"` when not provided.       |\n| `schema`  | object | ✗        | JSON Schema defining the expected response structure (object or primitive).                        |\n\n## Response Format\n\nThe tool returns a JSON response:\n\n```json\n{\n  \"action\": \"accept\",\n  \"content\": {\n    \"field1\": \"user value\",\n    \"field2\": true\n  }\n}\n```\n\n### Action Values\n\n| Action    | Meaning                                    |\n| --------- | ------------------------------------------ |\n| `accept`  | User provided a response (check `content`) |\n| `decline` | User declined to answer                    |\n| `cancel`  | User cancelled the prompt                  |\n\n## Schema Examples\n\n### Simple String Input\n\n```json\n{\n  \"type\": \"string\",\n  \"title\": \"API Key\",\n  \"description\": \"Enter your API key\"\n}\n```\n\n### Multiple Choice\n\n```json\n{\n  \"type\": \"string\",\n  \"enum\": [\"development\", \"staging\", \"production\"],\n  \"title\": \"Environment\",\n  \"description\": \"Select the target environment\"\n}\n```\n\n### Boolean Confirmation\n\n```json\n{\n  \"type\": \"boolean\",\n  \"title\": \"Confirm\",\n  \"description\": \"Are you sure you want to proceed?\"\n}\n```\n\n### Object with Multiple Fields\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"username\": {\n      \"type\": \"string\",\n      \"description\": \"Your username\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"description\": \"Your password\"\n    },\n    \"remember\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remember credentials\"\n    }\n  },\n  \"required\": [\"username\", \"password\"]\n}\n```\n\n### Number Input\n\n```json\n{\n  \"type\": \"integer\",\n  \"title\": \"Port Number\",\n  \"description\": \"Enter the port number (1024-65535)\",\n  \"minimum\": 1024,\n  \"maximum\": 65535\n}\n```\n\n## Example Usage\n\nHere's how an agent might use the user prompt tool:\n\n```text\nAgent: I need to deploy this application. Let me ask which environment to target.\n\n[Calls user_prompt with message: \"Which environment should I deploy to?\"\n and schema with enum: [\"development\", \"staging\", \"production\"]]\n\nUser selects: \"staging\"\n\nAgent: Great, I'll deploy to staging. Let me confirm this action.\n\n[Calls user_prompt with message: \"Deploy to staging? This will replace the current version.\"\n and schema with type: \"boolean\"]\n\nUser confirms: true\n\nAgent: Deploying to staging...\n```\n\n## UI Presentation\n\nHow the prompt appears depends on the interface:\n\n- **TUI**: Displays an interactive dialog with appropriate input controls\n- **CLI (exec mode)**: Prints the prompt and reads from stdin\n- **API/MCP**: Returns an elicitation request to the client\n\n> [!TIP]\n> **Best Practice**\n>\n> Provide clear, concise messages. Include context about why you're asking and what the information will be used for. Use schemas with descriptions to guide users on expected input format.\n\n## Handling Responses\n\nThe agent should handle all possible actions:\n\n- **accept**: Process the `content` and continue\n- **decline**: Acknowledge and try an alternative approach or explain what's needed\n- **cancel**: Stop the current operation gracefully\n\n> [!WARNING]\n> **Context Requirement**\n>\n> The user prompt tool requires an elicitation handler to be configured. It works in the TUI and CLI modes but may not be available in all contexts (e.g., some MCP client configurations).\n","frontmatter":{"title":"User Prompt Tool","description":"Ask the user questions and collect interactive input during agent execution.","keywords":"docker agent, ai agents, tools, toolsets, user prompt tool","linkTitle":"User Prompt","weight":190,"canonical":"https://docs.docker.com/ai/docker-agent/tools/user-prompt/"},"isInternal":false,"tokens":1100,"sizeBytes":5017},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md","title":"Webhook Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Webhook Tool\"\ndescription: \"Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more.\"\nkeywords: docker agent, ai agents, tools, toolsets, webhook, slack, discord, telegram, ifttt, notifications\nlinkTitle: \"Webhook\"\nweight: 145\ncanonical: https://docs.docker.com/ai/docker-agent/tools/webhook/\n---\n\n_Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more._\n\n## Overview\n\nThe webhook toolset delivers a notification to a destination **you configure**. The\nagent supplies only the message text: it never sees or chooses the URL, because a\nwebhook URL is itself a credential (Slack and Mattermost embed a secret path,\nDiscord a token, IFTTT a key, Telegram a bot token).\n\nThis is not a general HTTP client — that is the [`api`](../api/index.md) toolset.\nThe webhook toolset owns *delivery*:\n\n- **At-least-once delivery.** Transient failures (`429`, `5xx`, network errors) are\n  retried with exponential backoff, honouring the server's `Retry-After`. A `4xx`\n  is permanent and fails immediately without wasting retries.\n- **Non-blocking.** The call returns as soon as the notification is queued, so a\n  slow or retrying endpoint never stalls the agent's turn. The agent is messaged\n  back **only if delivery ultimately fails**.\n- **Storm protection.** An identical message to the same destination inside a short\n  window is suppressed, and notifications are rate limited, so a looping agent\n  cannot flood a channel.\n- **Provider-shaped payloads.** Each service's wire format is applied for you.\n\n## Configuration\n\nThe destination lives in `webhook_config`. Use `${env.VAR}` for anything secret —\nvalues are expanded at call time and never stored in the config file.\n\n```yaml\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n| Field | Required | Description |\n| --- | --- | --- |\n| `url` | Yes | Webhook endpoint. Usually embeds a secret — prefer `${env.VAR}`. |\n| `provider` | No | Payload shape (default `generic`). |\n| `headers` | No | Extra headers, for endpoints authenticating with a token. |\n| `chat_id` | No | Destination chat — required for `provider: telegram`. |\n\n`timeout` on the toolset (seconds) overrides the per-request HTTP timeout.\n\n## Providers\n\n| Provider | Payload sent | Where the secret lives |\n| --- | --- | --- |\n| `slack`, `mattermost`, `rocketchat`, `googlechat`, `teams`, `generic` | `{\"text\": message}` | secret webhook URL |\n| `discord` | `{\"content\": message}` | token in the webhook URL |\n| `ifttt` | `{\"value1\": message, \"value2\": …, \"value3\": …}` | key in the webhook URL |\n| `telegram` | `{\"chat_id\": …, \"text\": message}` | bot token in the URL, plus `chat_id` |\n\nAliases are accepted: `msteams`/`microsoft_teams` → `teams`, `google_chat`/`gchat`\n→ `googlechat`, `rocket.chat` → `rocketchat`.\n\n### Per-service examples\n\n```yaml\n# Slack / Mattermost / Rocket.Chat — the URL is the credential\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n```yaml\n# Discord — the token is part of the webhook URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: discord\n      url: ${env.DISCORD_WEBHOOK_URL}\n```\n\n```yaml\n# Telegram — bot token in the URL, chat_id selects the destination chat\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: telegram\n      url: https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage\n      chat_id: \"123456789\"\n```\n\n```yaml\n# IFTTT — the key is part of the trigger URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: ifttt\n      url: https://maker.ifttt.com/trigger/build_failed/with/key/${env.IFTTT_KEY}\n```\n\n```yaml\n# Generic endpoint authenticating with a bearer token\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: generic\n      url: https://alerts.example.com/notify\n      headers:\n        Authorization: Bearer ${env.ALERTS_TOKEN}\n```\n\n## `send_webhook`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `message` | Yes | The message text to deliver. |\n| `value2`, `value3` | No | Extra IFTTT data fields (`provider: ifttt`). |\n\nReturns immediately once queued. On success nothing further happens; if delivery\nultimately fails, the agent receives a message saying so.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    instruction: If a check fails, notify the team with send_webhook.\n    toolsets:\n      - type: webhook\n        webhook_config:\n          provider: slack\n          url: ${env.SLACK_WEBHOOK_URL}\n```\n\n> [!NOTE]\n> Requests to non-public addresses are refused (the SSRF-safe HTTP client), and the\n> configured URL is never echoed back to the model or into error messages.\n","frontmatter":{"title":"Webhook Tool","description":"Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more.","keywords":"docker agent, ai agents, tools, toolsets, webhook, slack, discord, telegram, ifttt, notifications","linkTitle":"Webhook","weight":145,"canonical":"https://docs.docker.com/ai/docker-agent/tools/webhook/"},"isInternal":false,"tokens":1217,"sizeBytes":4751},{"name":"_index.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Build checks\ndescription: |\n  BuildKit has built-in support for analyzing your build configuration based on\n  a set of pre-defined rules for enforcing Dockerfile and building best\n  practices.\nkeywords: buildkit, linting, dockerfile, frontend, rules\n---\n\nBuildKit has built-in support for analyzing your build configuration based on a\nset of pre-defined rules for enforcing Dockerfile and building best practices.\nAdhering to these rules helps avoid errors and ensures good readability of your\nDockerfile.\n\nChecks run as a build invocation, but instead of producing a build output, it\nperforms a series of checks to validate that your build doesn't violate any of\nthe rules. To run a check, use the `--check` flag:\n\n```console\n$ docker build --check .\n```\n\nTo learn more about how to use build checks, see\n[Checking your build configuration](https://docs.docker.com/build/checks/).\n\n<table>\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><a href=\"./stage-name-casing/\">StageNameCasing</a></td>\n      <td>Stage names should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-as-casing/\">FromAsCasing</a></td>\n      <td>The 'as' keyword should match the case of the 'from' keyword</td>\n    </tr>\n    <tr>\n      <td><a href=\"./no-empty-continuation/\">NoEmptyContinuation</a></td>\n      <td>Empty continuation lines will become errors in a future release</td>\n    </tr>\n    <tr>\n      <td><a href=\"./consistent-instruction-casing/\">ConsistentInstructionCasing</a></td>\n      <td>All commands within the Dockerfile should use the same casing (either upper or lower)</td>\n    </tr>\n    <tr>\n      <td><a href=\"./duplicate-stage-name/\">DuplicateStageName</a></td>\n      <td>Stage names should be unique</td>\n    </tr>\n    <tr>\n      <td><a href=\"./reserved-stage-name/\">ReservedStageName</a></td>\n      <td>Reserved words should not be used as stage names</td>\n    </tr>\n    <tr>\n      <td><a href=\"./json-args-recommended/\">JSONArgsRecommended</a></td>\n      <td>JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals</td>\n    </tr>\n    <tr>\n      <td><a href=\"./maintainer-deprecated/\">MaintainerDeprecated</a></td>\n      <td>The MAINTAINER instruction is deprecated, use a label instead to define an image author</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-arg-in-from/\">UndefinedArgInFrom</a></td>\n      <td>FROM command must use declared ARGs</td>\n    </tr>\n    <tr>\n      <td><a href=\"./workdir-relative-path/\">WorkdirRelativePath</a></td>\n      <td>Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-var/\">UndefinedVar</a></td>\n      <td>Variables should be defined before their use</td>\n    </tr>\n    <tr>\n      <td><a href=\"./multiple-instructions-disallowed/\">MultipleInstructionsDisallowed</a></td>\n      <td>Multiple instructions of the same type should not be used in the same stage</td>\n    </tr>\n    <tr>\n      <td><a href=\"./legacy-key-value-format/\">LegacyKeyValueFormat</a></td>\n      <td>Legacy key/value format with whitespace separator should not be used</td>\n    </tr>\n    <tr>\n      <td><a href=\"./redundant-target-platform/\">RedundantTargetPlatform</a></td>\n      <td>Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior</td>\n    </tr>\n    <tr>\n      <td><a href=\"./secrets-used-in-arg-or-env/\">SecretsUsedInArgOrEnv</a></td>\n      <td>Sensitive data should not be used in the ARG or ENV commands</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-default-arg-in-from/\">InvalidDefaultArgInFrom</a></td>\n      <td>Default value for global ARG results in an empty or invalid base image name</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-platform-flag-const-disallowed/\">FromPlatformFlagConstDisallowed</a></td>\n      <td>FROM --platform flag should not use a constant value</td>\n    </tr>\n    <tr>\n      <td><a href=\"./copy-ignored-file/\">CopyIgnoredFile</a></td>\n      <td>Attempting to Copy file that is excluded by .dockerignore</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-definition-description/\">InvalidDefinitionDescription (experimental)</a></td>\n      <td>Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-proto-casing/\">ExposeProtoCasing</a></td>\n      <td>Protocol in EXPOSE instruction should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-invalid-format/\">ExposeInvalidFormat</a></td>\n      <td>IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release</td>\n    </tr>\n  </tbody>\n</table>\n","frontmatter":{"title":"Build checks","description":"BuildKit has built-in support for analyzing your build configuration based on\na set of pre-defined rules for enforcing Dockerfile and building best\npractices.\n","keywords":"buildkit, linting, dockerfile, frontend, rules"},"isInternal":false,"tokens":1301,"sizeBytes":4957},{"name":"consistent-instruction-casing.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: ConsistentInstructionCasing\ndescription: >-\n  All commands within the Dockerfile should use the same casing (either upper or lower)\naliases:\n  - /go/dockerfile/rule/consistent-instruction-casing/\n---\n\n## Output\n\n```text\nCommand 'EntryPoint' should be consistently cased\n```\n\n## Description\n\nInstruction keywords should use consistent casing (all lowercase or all\nuppercase). Using a case that mixes uppercase and lowercase, such as\n`PascalCase` or `snakeCase`, letters result in poor readability.\n\n## Examples\n\n❌ Bad: don't mix uppercase and lowercase.\n\n```dockerfile\nFrom alpine\nRun echo hello > /greeting.txt\nEntRYpOiNT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all uppercase.\n\n```dockerfile\nFROM alpine\nRUN echo hello > /greeting.txt\nENTRYPOINT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all lowercase.\n\n```dockerfile\nfrom alpine\nrun echo hello > /greeting.txt\nentrypoint [\"cat\", \"/greeting.txt\"]\n```\n\n","frontmatter":{"title":"ConsistentInstructionCasing","description":"All commands within the Dockerfile should use the same casing (either upper or lower)","aliases":["/go/dockerfile/rule/consistent-instruction-casing/"]},"isInternal":false,"tokens":230,"sizeBytes":913},{"name":"copy-ignored-file.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: CopyIgnoredFile\ndescription: >-\n  Attempting to Copy file that is excluded by .dockerignore\naliases:\n  - /go/dockerfile/rule/copy-ignored-file/\n---\n\n## Output\n\n```text\nAttempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n```\n\n## Description\n\nWhen you use the Add or Copy instructions from within a Dockerfile, you should\nensure that the files to be copied into the image do not match a pattern\npresent in `.dockerignore`.\n\nFiles which match the patterns in a `.dockerignore` file are not present in the\ncontext of the image when it is built. Trying to copy or add a file which is\nmissing from the context will result in a build error.\n\n## Examples\n\nWith the given `.dockerignore` file:\n\n```text\n*/tmp/*\n```\n\n❌ Bad: Attempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./tmp/helloworld.txt /helloworld.txt\n```\n\n✅ Good: Copying a file which is not excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./forever/helloworld.txt /helloworld.txt\n```\n\n","frontmatter":{"title":"CopyIgnoredFile","description":"Attempting to Copy file that is excluded by .dockerignore","aliases":["/go/dockerfile/rule/copy-ignored-file/"]},"isInternal":false,"tokens":260,"sizeBytes":1047},{"name":"duplicate-stage-name.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: DuplicateStageName\ndescription: >-\n  Stage names should be unique\naliases:\n  - /go/dockerfile/rule/duplicate-stage-name/\n---\n\n## Output\n\n```text\nDuplicate stage name 'foo-base', stage names should be unique\n```\n\n## Description\n\nDefining multiple stages with the same name results in an error because the\nbuilder is unable to uniquely resolve the stage name reference.\n\n## Examples\n\n❌ Bad: `builder` is declared as a stage name twice.\n\n```dockerfile\nFROM debian:latest AS builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS builder\n```\n\n✅ Good: stages have unique names.\n\n```dockerfile\nFROM debian:latest AS deb-builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS go-builder\n```\n\n","frontmatter":{"title":"DuplicateStageName","description":"Stage names should be unique","aliases":["/go/dockerfile/rule/duplicate-stage-name/"]},"isInternal":false,"tokens":179,"sizeBytes":740},{"name":"expose-invalid-format.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: ExposeInvalidFormat\ndescription: >-\n  IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release\naliases:\n  - /go/dockerfile/rule/expose-invalid-format/\n---\n\n## Output\n\n```text\nEXPOSE instruction should not define an IP address or host-port mapping, found '127.0.0.1:80:80'\n```\n\n## Description\n\nThe [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose) instruction\nin a Dockerfile is used to indicate which ports the container listens on at\nruntime. It should not include an IP address or host-port mapping, as this is\nnot the intended use of the `EXPOSE` instruction. Instead, it should only\nspecify the port number and optionally the protocol (TCP or UDP).\n\n> [!IMPORTANT]\n> This will become an error in a future release.\n\n## Examples\n\n❌ Bad: IP address and host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 127.0.0.1:80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n❌ Bad: Host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n","frontmatter":{"title":"ExposeInvalidFormat","description":"IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release","aliases":["/go/dockerfile/rule/expose-invalid-format/"]},"isInternal":false,"tokens":312,"sizeBytes":1177},{"name":"expose-proto-casing.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: ExposeProtoCasing\ndescription: >-\n  Protocol in EXPOSE instruction should be lowercase\naliases:\n  - /go/dockerfile/rule/expose-proto-casing/\n---\n\n## Output\n\n```text\nDefined protocol '80/TcP' in EXPOSE instruction should be lowercase\n```\n\n## Description\n\nProtocol names in the [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose)\ninstruction should be specified in lowercase to maintain consistency and\nreadability. This rule checks for protocols that are not in lowercase and\nreports them.\n\n## Examples\n\n❌ Bad: protocol is not in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/TcP\n```\n\n✅ Good: protocol is in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/tcp\n```\n\n","frontmatter":{"title":"ExposeProtoCasing","description":"Protocol in EXPOSE instruction should be lowercase","aliases":["/go/dockerfile/rule/expose-proto-casing/"]},"isInternal":false,"tokens":173,"sizeBytes":694},{"name":"from-as-casing.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: FromAsCasing\ndescription: >-\n  The 'as' keyword should match the case of the 'from' keyword\naliases:\n  - /go/dockerfile/rule/from-as-casing/\n---\n\n## Output\n\n```text\n'as' and 'FROM' keywords' casing do not match\n```\n\n## Description\n\nWhile Dockerfile keywords can be either uppercase or lowercase, mixing case\nstyles is not recommended for readability. This rule reports violations where\nmixed case style occurs for a `FROM` instruction with an `AS` keyword declaring\na stage name.\n\n## Examples\n\n❌ Bad: `FROM` is uppercase, `AS` is lowercase.\n\n```dockerfile\nFROM debian:latest as builder\n```\n\n✅ Good: `FROM` and `AS` are both uppercase\n\n```dockerfile\nFROM debian:latest AS deb-builder\n```\n\n✅ Good: `FROM` and `AS` are both lowercase.\n\n```dockerfile\nfrom debian:latest as deb-builder\n```\n\n## Related errors\n\n- [`FileConsistentCommandCasing`](./consistent-instruction-casing.md)\n\n","frontmatter":{"title":"FromAsCasing","description":"The 'as' keyword should match the case of the 'from' keyword","aliases":["/go/dockerfile/rule/from-as-casing/"]},"isInternal":false,"tokens":230,"sizeBytes":893},{"name":"from-platform-flag-const-disallowed.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: FromPlatformFlagConstDisallowed\ndescription: >-\n  FROM --platform flag should not use a constant value\naliases:\n  - /go/dockerfile/rule/from-platform-flag-const-disallowed/\n---\n\n## Output\n\n```text\nFROM --platform flag should not use constant value \"linux/amd64\"\n```\n\n## Description\n\nSpecifying `--platform` in the Dockerfile `FROM` instruction forces the image to build on only one target platform. This prevents building a multi-platform image from this Dockerfile and you must build on the same platform as specified in `--platform`.\n\nThe recommended approach is to:\n\n* Omit `FROM --platform` in the Dockerfile and use the `--platform` argument on the command line.\n* Use `$BUILDPLATFORM` or some other combination of variables for the `--platform` argument.\n* Stage name should include the platform, OS, or architecture name to indicate that it only contains platform-specific instructions.\n\n## Examples\n\n❌ Bad: using a constant argument for `--platform`\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using the default platform\n\n```dockerfile\nFROM alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using a meta variable\n\n```dockerfile\nFROM --platform=${BUILDPLATFORM} alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: used in a multi-stage build with a target architecture\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS build_amd64\n...\n\nFROM --platform=linux/arm64 alpine AS build_arm64\n...\n\nFROM build_${TARGETARCH} AS build\n...\n```\n\n","frontmatter":{"title":"FromPlatformFlagConstDisallowed","description":"FROM --platform flag should not use a constant value","aliases":["/go/dockerfile/rule/from-platform-flag-const-disallowed/"]},"isInternal":false,"tokens":366,"sizeBytes":1525},{"name":"invalid-default-arg-in-from.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: InvalidDefaultArgInFrom\ndescription: >-\n  Default value for global ARG results in an empty or invalid base image name\naliases:\n  - /go/dockerfile/rule/invalid-default-arg-in-from/\n---\n\n## Output\n\n```text\nUsing the global ARGs with default values should produce a valid build.\n```\n\n## Description\n\nAn `ARG` used in an image reference should be valid when no build arguments are used. An image build should not require `--build-arg` to be used to produce a valid build.\n\n## Examples\n\n❌ Bad: don't rely on an ARG being set for an image reference to be valid\n\n```dockerfile\nARG TAG\nFROM busybox:${TAG}\n```\n\n✅ Good: include a default for the ARG\n\n```dockerfile\nARG TAG=latest\nFROM busybox:${TAG}\n```\n\n✅ Good: ARG can be empty if the image would be valid with it empty\n\n```dockerfile\nARG VARIANT\nFROM busybox:stable${VARIANT}\n```\n\n✅ Good: Use a default value if the build arg is not present\n\n```dockerfile\nARG TAG\nFROM alpine:${TAG:-3.14}\n```\n\n","frontmatter":{"title":"InvalidDefaultArgInFrom","description":"Default value for global ARG results in an empty or invalid base image name","aliases":["/go/dockerfile/rule/invalid-default-arg-in-from/"]},"isInternal":false,"tokens":250,"sizeBytes":957},{"name":"invalid-definition-description.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: InvalidDefinitionDescription\ndescription: >-\n  Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\naliases:\n  - /go/dockerfile/rule/invalid-definition-description/\n---\n\n> [!NOTE]\n> This check is experimental and is not enabled by default. To enable it, see\n> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).\n\n## Output\n\n```text\nComment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\n```\n\n## Description\n\nThe [`--call=outline`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nand [`--call=targets`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nflags for the `docker build` command print descriptions for build targets and arguments.\nThe descriptions are generated from [Dockerfile comments](https://docs.docker.com/reference/cli/docker/buildx/build/#descriptions)\nthat immediately precede the `FROM` or `ARG` instruction\nand that begin with the name of the build stage or argument.\nFor example:\n\n```dockerfile\n# build-cli builds the CLI binary\nFROM alpine AS build-cli\n# VERSION controls the version of the program\nARG VERSION=1\n```\n\nIn cases where preceding comments are not meant to be descriptions,\nadd an empty line or comment between the instruction and the preceding comment.\n\n## Examples\n\n❌ Bad: A non-descriptive comment on the line preceding the `FROM` command.\n\n```dockerfile\n# a non-descriptive comment\nFROM scratch AS base\n\n# another non-descriptive comment\nARG VERSION=1\n```\n\n✅ Good: An empty line separating non-descriptive comments.\n\n```dockerfile\n# a non-descriptive comment\n\nFROM scratch AS base\n\n# another non-descriptive comment\n\nARG VERSION=1\n```\n\n✅ Good: Comments describing `ARG` keys and stages immediately proceeding the command.\n\n```dockerfile\n# base is a stage for compiling source\nFROM scratch AS base\n# VERSION This is the version number.\nARG VERSION=1\n```\n\n","frontmatter":{"title":"InvalidDefinitionDescription","description":"Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.","aliases":["/go/dockerfile/rule/invalid-definition-description/"]},"isInternal":false,"tokens":496,"sizeBytes":2219},{"name":"json-args-recommended.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: JSONArgsRecommended\ndescription: >-\n  JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\naliases:\n  - /go/dockerfile/rule/json-args-recommended/\n---\n\n## Output\n\n```text\nJSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\n```\n\n## Description\n\n`ENTRYPOINT` and `CMD` instructions both support two different syntaxes for\narguments:\n\n- Shell form: `CMD my-cmd start`\n- Exec form: `CMD [\"my-cmd\", \"start\"]`\n\nWhen you use shell form, the executable runs as a child process to a shell,\nwhich doesn't pass signals. This means that the program running in the\ncontainer can't detect OS signals like `SIGTERM` and `SIGKILL` and respond to\nthem correctly.\n\n## Examples\n\n❌ Bad: the `ENTRYPOINT` command doesn't receive OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT my-program start\n# entrypoint becomes: /bin/sh -c my-program start\n```\n\nTo make sure the executable can receive OS signals, use the exec form for `CMD`\nand `ENTRYPOINT`, which lets you run the executable as the main process (`PID\n1`) in the container, avoiding a shell parent process.\n\n✅ Good: the `ENTRYPOINT` receives OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"my-program\", \"start\"]\n# entrypoint becomes: my-program start\n```\n\nNote that running programs as PID 1 means the program now has the special\nresponsibilities and behaviors associated with PID 1 in Linux, such as reaping\nchild processes.\n\n### Workarounds\n\nThere might still be cases when you want to run your containers under a shell.\nWhen using exec form, shell features such as variable expansion, piping (`|`)\nand command chaining (`&&`, `||`, `;`), are not available. To use such\nfeatures, you need to use shell form.\n\nHere are some ways you can achieve that. Note that this still means that\nexecutables run as child-processes of a shell.\n\n#### Create a wrapper script\n\nYou can create an entrypoint script that wraps your startup commands, and\nexecute that script with a JSON-formatted `ENTRYPOINT` command.\n\n✅ Good: the `ENTRYPOINT` uses JSON format.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nCOPY --chmod=755 <<EOT /entrypoint.sh\n#!/usr/bin/env bash\nset -e\nmy-background-process &\nmy-program start\nEOT\nENTRYPOINT [\"/entrypoint.sh\"]\n```\n\n#### Explicitly specify the shell\n\nYou can use the [`SHELL`](https://docs.docker.com/reference/dockerfile/#shell)\nDockerfile instruction to explicitly specify a shell to use. This will suppress\nthe warning since setting the `SHELL` instruction indicates that using shell\nform is a conscious decision.\n\n✅ Good: shell is explicitly defined.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nSHELL [\"/bin/bash\", \"-c\"]\nENTRYPOINT echo \"hello world\"\n```\n\n","frontmatter":{"title":"JSONArgsRecommended","description":"JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals","aliases":["/go/dockerfile/rule/json-args-recommended/"]},"isInternal":false,"tokens":653,"sizeBytes":2729},{"name":"legacy-key-value-format.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: LegacyKeyValueFormat\ndescription: >-\n  Legacy key/value format with whitespace separator should not be used\naliases:\n  - /go/dockerfile/rule/legacy-key-value-format/\n---\n\n## Output\n\n```text\n\"ENV key=value\" should be used instead of legacy \"ENV key value\" format\n```\n\n## Description\n\nThe correct format for declaring environment variables and build arguments in a\nDockerfile is `ENV key=value` and `ARG key=value`, where the variable name\n(`key`) and value (`value`) are separated by an equals sign (`=`).\nHistorically, Dockerfiles have also supported a space separator between the key\nand the value (for example, `ARG key value`). This legacy format is deprecated,\nand you should only use the format with the equals sign.\n\n## Examples\n\n❌ Bad: using a space separator for variable key and value.\n\n```dockerfile\nFROM alpine\nARG foo bar\n```\n\n✅ Good: use an equals sign to separate key and value.\n\n```dockerfile\nFROM alpine\nARG foo=bar\n```\n\n❌ Bad: multi-line variable declaration with a space separator.\n\n```dockerfile\nENV DEPS \\\n    curl \\\n    git \\\n    make\n```\n\n✅ Good: use an equals sign and wrap the value in quotes.\n\n```dockerfile\nENV DEPS=\"\\\n    curl \\\n    git \\\n    make\"\n```\n\n> [!NOTE]\n> Be aware of leading whitespace when converting multi-line legacy syntax to\n> the modern `key=value` format. In the legacy format, leading whitespace on\n> continuation lines is included in the value. In the modern format with\n> quoted values, leading whitespace inside the quotes is also preserved. If\n> you don't want leading whitespace in the value, make sure to remove it when\n> rewriting to the new format:\n>\n> ```dockerfile\n> ENV DEPS=\"\\\n> curl \\\n> git \\\n> make\"\n> ```\n\n","frontmatter":{"title":"LegacyKeyValueFormat","description":"Legacy key/value format with whitespace separator should not be used","aliases":["/go/dockerfile/rule/legacy-key-value-format/"]},"isInternal":false,"tokens":408,"sizeBytes":1686},{"name":"maintainer-deprecated.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: MaintainerDeprecated\ndescription: >-\n  The MAINTAINER instruction is deprecated, use a label instead to define an image author\naliases:\n  - /go/dockerfile/rule/maintainer-deprecated/\n---\n\n## Output\n\n```text\nMAINTAINER instruction is deprecated in favor of using label\n```\n\n## Description\n\nThe `MAINTAINER` instruction, used historically for specifying the author of\nthe Dockerfile, is deprecated. To set author metadata for an image, use the\n`org.opencontainers.image.authors` [OCI label](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys).\n\n## Examples\n\n❌ Bad: don't use the `MAINTAINER` instruction\n\n```dockerfile\nMAINTAINER moby@example.com\n```\n\n✅ Good: specify the author using the `org.opencontainers.image.authors` label\n\n```dockerfile\nLABEL org.opencontainers.image.authors=\"moby@example.com\"\n```\n\n","frontmatter":{"title":"MaintainerDeprecated","description":"The MAINTAINER instruction is deprecated, use a label instead to define an image author","aliases":["/go/dockerfile/rule/maintainer-deprecated/"]},"isInternal":false,"tokens":206,"sizeBytes":868},{"name":"multiple-instructions-disallowed.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: MultipleInstructionsDisallowed\ndescription: >-\n  Multiple instructions of the same type should not be used in the same stage\naliases:\n  - /go/dockerfile/rule/multiple-instructions-disallowed/\n---\n\n## Output\n\n```text\nMultiple CMD instructions should not be used in the same stage because only the last one will be used\n```\n\n## Description\n\nIf you have multiple `CMD`, `HEALTHCHECK`, or `ENTRYPOINT` instructions in your\nDockerfile, only the last occurrence is used. An image can only ever have one\n`CMD`, `HEALTHCHECK`, and `ENTRYPOINT`.\n\n## Examples\n\n❌ Bad: Duplicate instructions.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\"]\nENTRYPOINT [\"echo\", \"Hello, Sweden!\"]\n# Only \"Hello, Sweden!\" will be printed\n```\n\n✅ Good: only one `ENTRYPOINT` instruction.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\\nHello, Sweden!\"]\n```\n\nYou can have both a regular, top-level `CMD`\nand a separate `CMD` for a `HEALTHCHECK` instruction.\n\n✅ Good: only one top-level `CMD` instruction.\n\n```dockerfile\nFROM python:alpine\nRUN apk add curl\nHEALTHCHECK --interval=1s --timeout=3s \\\n  CMD [\"curl\", \"-f\", \"http://localhost:8080\"]\nCMD [\"python\", \"-m\", \"http.server\", \"8080\"]\n```\n\n","frontmatter":{"title":"MultipleInstructionsDisallowed","description":"Multiple instructions of the same type should not be used in the same stage","aliases":["/go/dockerfile/rule/multiple-instructions-disallowed/"]},"isInternal":false,"tokens":323,"sizeBytes":1209},{"name":"no-empty-continuation.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: NoEmptyContinuation\ndescription: >-\n  Empty continuation lines will become errors in a future release\naliases:\n  - /go/dockerfile/rule/no-empty-continuation/\n---\n\n## Output\n\n```text\nEmpty continuation line found in: RUN apk add     gnupg     curl\n```\n\n## Description\n\nSupport for empty continuation (`/`) lines have been deprecated and will\ngenerate errors in future versions of the Dockerfile syntax.\n\nEmpty continuation lines are empty lines following a newline escape:\n\n```dockerfile\nFROM alpine\nRUN apk add \\\n\n    gnupg \\\n\n    curl\n```\n\nSupport for such empty lines is deprecated, and a future BuildKit release will\nremove support for this syntax entirely, causing builds to break. To avoid\nfuture errors, remove the empty lines, or add comments, since lines with\ncomments aren't considered empty.\n\n## Examples\n\n❌ Bad: empty continuation line between `EXPOSE` and 80.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n\n80\n```\n\n✅ Good: comments do not count as empty lines.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n# Port\n80\n```\n\n","frontmatter":{"title":"NoEmptyContinuation","description":"Empty continuation lines will become errors in a future release","aliases":["/go/dockerfile/rule/no-empty-continuation/"]},"isInternal":false,"tokens":243,"sizeBytes":1029},{"name":"redundant-target-platform.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: RedundantTargetPlatform\ndescription: >-\n  Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\naliases:\n  - /go/dockerfile/rule/redundant-target-platform/\n---\n\n## Output\n\n```text\nSetting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\n```\n\n## Description\n\nA custom platform can be used for a base image. The default platform is the\nsame platform as the target output so setting the platform to `$TARGETPLATFORM`\nis redundant and unnecessary.\n\n## Examples\n\n❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.\n\n```dockerfile\nFROM --platform=$TARGETPLATFORM alpine AS builder\nRUN apk add --no-cache git\n```\n\n✅ Good: omit the `--platform` argument.\n\n```dockerfile\nFROM alpine AS builder\nRUN apk add --no-cache git\n```\n\n","frontmatter":{"title":"RedundantTargetPlatform","description":"Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior","aliases":["/go/dockerfile/rule/redundant-target-platform/"]},"isInternal":false,"tokens":198,"sizeBytes":856},{"name":"reserved-stage-name.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: ReservedStageName\ndescription: >-\n  Reserved words should not be used as stage names\naliases:\n  - /go/dockerfile/rule/reserved-stage-name/\n---\n\n## Output\n\n```text\n'scratch' is reserved and should not be used as a stage name\n```\n\n## Description\n\nReserved words should not be used as names for stages in multi-stage builds.\nThe reserved words are:\n\n- `context`\n- `scratch`\n\n## Examples\n\n❌ Bad: `scratch` and `context` are reserved names.\n\n```dockerfile\nFROM alpine AS scratch\nFROM alpine AS context\n```\n\n✅ Good: the stage name `builder` is not reserved.\n\n```dockerfile\nFROM alpine AS builder\n```\n\n","frontmatter":{"title":"ReservedStageName","description":"Reserved words should not be used as stage names","aliases":["/go/dockerfile/rule/reserved-stage-name/"]},"isInternal":false,"tokens":154,"sizeBytes":610},{"name":"secrets-used-in-arg-or-env.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: SecretsUsedInArgOrEnv\ndescription: >-\n  Sensitive data should not be used in the ARG or ENV commands\naliases:\n  - /go/dockerfile/rule/secrets-used-in-arg-or-env/\n---\n\n## Output\n\n```text\nPotentially sensitive data should not be used in the ARG or ENV commands\n```\n\n## Description\n\nWhile it is common to pass secrets to running processes\nthrough environment variables during local development,\nsetting secrets in a Dockerfile using `ENV` or `ARG`\nis insecure because they persist in the final image.\nThis rule reports violations where `ENV` and `ARG` keys\nindicate that they contain sensitive data.\n\nInstead of `ARG` or `ENV`, you should use secret mounts,\nwhich expose secrets to your builds in a secure manner,\nand do not persist in the final image or its metadata.\nSee [Build secrets](https://docs.docker.com/build/building/secrets/).\n\n## Examples\n\n❌ Bad: using ARG to pass AWS credentials.\n\n```dockerfile\nARG AWS_ACCESS_KEY_ID\nARG AWS_SECRET_ACCESS_KEY\nRUN aws s3 cp s3://my-bucket/file .\n```\n\n✅ Good: using secret mounts with environment variables.\n\n```dockerfile\nRUN --mount=type=secret,id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --mount=type=secret,id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY \\\n    aws s3 cp s3://my-bucket/file .\n```\n\nTo build with these secrets:\n\n```console\n$ docker buildx build \\\n    --secret id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --secret id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY .\n```\n\n","frontmatter":{"title":"SecretsUsedInArgOrEnv","description":"Sensitive data should not be used in the ARG or ENV commands","aliases":["/go/dockerfile/rule/secrets-used-in-arg-or-env/"]},"isInternal":false,"tokens":358,"sizeBytes":1435},{"name":"stage-name-casing.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: StageNameCasing\ndescription: >-\n  Stage names should be lowercase\naliases:\n  - /go/dockerfile/rule/stage-name-casing/\n---\n\n## Output\n\n```text\nStage name 'BuilderBase' should be lowercase\n```\n\n## Description\n\nTo help distinguish Dockerfile instruction keywords from identifiers, this rule\nforces names of stages in a multi-stage Dockerfile to be all lowercase.\n\n## Examples\n\n❌ Bad: mixing uppercase and lowercase characters in the stage name.\n\n```dockerfile\nFROM alpine AS BuilderBase\n```\n\n✅ Good: stage name is all in lowercase.\n\n```dockerfile\nFROM alpine AS builder-base\n```\n\n","frontmatter":{"title":"StageNameCasing","description":"Stage names should be lowercase","aliases":["/go/dockerfile/rule/stage-name-casing/"]},"isInternal":false,"tokens":139,"sizeBytes":592},{"name":"undefined-arg-in-from.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: UndefinedArgInFrom\ndescription: >-\n  FROM command must use declared ARGs\naliases:\n  - /go/dockerfile/rule/undefined-arg-in-from/\n---\n\n## Output\n\n```text\nFROM argument 'VARIANT' is not declared\n```\n\n## Description\n\nThis rule warns for cases where you're consuming an undefined build argument in\n`FROM` instructions.\n\nInterpolating build arguments in `FROM` instructions can be a good way to add\nflexibility to your build, and lets you pass arguments that overriding the base\nimage of a stage. For example, you might use a build argument to specify the\nimage tag:\n\n```dockerfile\nARG ALPINE_VERSION=3.20\n\nFROM alpine:${ALPINE_VERSION}\n```\n\nThis makes it possible to run the build with a different `alpine` version by\nspecifying a build argument:\n\n```console\n$ docker buildx build --build-arg ALPINE_VERSION=edge .\n```\n\nThis check also tries to detect and warn when a `FROM` instruction reference\nmiss-spelled built-in build arguments, like `BUILDPLATFORM`.\n\n## Examples\n\n❌ Bad: the `VARIANT` build argument is undefined.\n\n```dockerfile\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n✅ Good: the `VARIANT` build argument is defined.\n\n```dockerfile\nARG VARIANT=\"-alpine3.20\"\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n","frontmatter":{"title":"UndefinedArgInFrom","description":"FROM command must use declared ARGs","aliases":["/go/dockerfile/rule/undefined-arg-in-from/"]},"isInternal":false,"tokens":309,"sizeBytes":1220},{"name":"undefined-var.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: UndefinedVar\ndescription: >-\n  Variables should be defined before their use\naliases:\n  - /go/dockerfile/rule/undefined-var/\n---\n\n## Output\n\n```text\nUsage of undefined variable '$foo'\n```\n\n## Description\n\nThis check ensures that environment variables and build arguments are correctly\ndeclared before being used. While undeclared variables might not cause an\nimmediate build failure, they can lead to unexpected behavior or errors later\nin the build process.\n\nThis check does not evaluate undefined variables for `RUN`, `CMD`, and\n`ENTRYPOINT` instructions where you use the [shell form](https://docs.docker.com/reference/dockerfile/#shell-form).\nThat's because when you use shell form, variables are resolved by the command\nshell.\n\nIt also detects common mistakes like typos in variable names. For example, in\nthe following Dockerfile:\n\n```dockerfile\nFROM alpine\nENV PATH=$PAHT:/app/bin\n```\n\nThe check identifies that `$PAHT` is undefined and likely a typo for `$PATH`:\n\n```text\nUsage of undefined variable '$PAHT' (did you mean $PATH?)\n```\n\n## Examples\n\n❌ Bad: `$foo` is an undefined build argument.\n\n```dockerfile\nFROM alpine AS base\nCOPY $foo .\n```\n\n✅ Good: declaring `foo` as a build argument before attempting to access it.\n\n```dockerfile\nFROM alpine AS base\nARG foo\nCOPY $foo .\n```\n\n❌ Bad: `$foo` is undefined.\n\n```dockerfile\nFROM alpine AS base\nARG VERSION=$foo\n```\n\n✅ Good: the base image defines `$PYTHON_VERSION`\n\n```dockerfile\nFROM python AS base\nARG VERSION=$PYTHON_VERSION\n```\n\n","frontmatter":{"title":"UndefinedVar","description":"Variables should be defined before their use","aliases":["/go/dockerfile/rule/undefined-var/"]},"isInternal":false,"tokens":363,"sizeBytes":1510},{"name":"workdir-relative-path.md","path":"_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: WorkdirRelativePath\ndescription: >-\n  Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes\naliases:\n  - /go/dockerfile/rule/workdir-relative-path/\n---\n\n## Output\n\n```text\nRelative workdir 'app/src' can have unexpected results if the base image changes\n```\n\n## Description\n\nWhen specifying `WORKDIR` in a build stage, you can use an absolute path, like\n`/build`, or a relative path, like `./build`. Using a relative path means that\nthe working directory is relative to whatever the previous working directory\nwas. So if your base image uses `/usr/local/foo` as a working directory, and\nyou specify a relative directory like `WORKDIR build`, the effective working\ndirectory becomes `/usr/local/foo/build`.\n\nThe `WorkdirRelativePath` build rule warns you if you use a `WORKDIR` with a\nrelative path without first specifying an absolute path in the same Dockerfile.\nThe rationale for this rule is that using a relative working directory for base\nimage built externally is prone to breaking, since working directory may change\nupstream without warning, resulting in a completely different directory\nhierarchy for your build.\n\n> [!NOTE]\n>\n> `WORKDIR` does not perform shell expansion. Paths beginning with `~` or\n> `~username` are treated as literal directory names and are not resolved to a\n> user's home directory.\n\n## Examples\n\n❌ Bad: this assumes that `WORKDIR` in the base image is `/`\n(if that changes upstream, the `web` stage is broken).\n\n```dockerfile\nFROM nginx AS web\nWORKDIR usr/share/nginx/html\nCOPY public .\n```\n\n✅ Good: a leading slash ensures that `WORKDIR` always ends up at the desired path.\n\n```dockerfile\nFROM nginx AS web\nWORKDIR /usr/share/nginx/html\nCOPY public .\n```\n\n","frontmatter":{"title":"WorkdirRelativePath","description":"Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes","aliases":["/go/dockerfile/rule/workdir-relative-path/"]},"isInternal":false,"tokens":405,"sizeBytes":1773},{"name":"report-template.md","path":".agents/skills/agent-readiness-audit/references/report-template.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/agent-readiness-audit/references/report-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Agent Readiness Report Template\n\nUse this structure for final audit output.\n\n```markdown\n## Agent Readiness Audit\n\n**Site:** <base-url>\n**Date:** <YYYY-MM-DD>\n**Overall score:** <score>/100\n**Grade:** <A-F>\n**Confidence:** <High|Medium|Low>\n\n### Summary\n\n<2-4 sentence verdict focused on what an external agent can actually\ndiscover, fetch, and interpret on this site.>\n\n### Category Scores\n\n| Category | Score | Notes |\n| --- | ---: | --- |\n| Discovery and policy | <x>/<y> | <short note> |\n| Retrieval and markdown delivery | <x>/<y> | <short note> |\n| Structure and semantics | <x>/<y> | <short note> |\n| Crawlability and delivery behavior | <x>/<y> | <short note> |\n| Machine-readable surfaces | <x>/<y> | <short note or N/A> |\n| Content legibility | <x>/<y> | <short note> |\n\n### Sample\n\n- Sample strategy: <sitemap / internal links / explicit URLs>\n- Sampled pages: <count>\n- Page types covered: <landing, guide, manual, reference, ...>\n- Weakest page type: <if any>\n\n### Findings\n\n- `P0`: <highest-priority blocker with evidence>\n- `P1`: <important recurring issue with evidence>\n- `P2`: <lower-priority or optional improvement>\n\n### Remediation\n\n- `P0`: <fix>, because <why it matters to agents>\n- `P1`: <fix>, because <why it matters to agents>\n- `P2`: <fix>, because <why it matters to agents>\n\n### Evidence\n\n- Sitewide checks: <llms.txt, robots.txt, sitemap.xml, manifests>\n- Fetch-path checks: <markdown negotiation, direct markdown routes,\n  advertised alternates, parity>\n- Structural checks: <h1/main/article/canonical/json-ld/title-h1 parity>\n- Code block checks: <fence count, language-tag coverage>\n- Scanner comparison: <optional>\n```\n\n## Notes\n\n- Keep the summary short and outcome-oriented.\n- Findings should refer to concrete URLs or page types.\n- If a criterion is `N/A`, say why instead of leaving it blank.\n","isInternal":false,"tokens":489,"sizeBytes":1834},{"name":"rubric.md","path":".agents/skills/agent-readiness-audit/references/rubric.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/agent-readiness-audit/references/rubric.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Agent Readiness Rubric\n\nScore the site on a 100-point scale before normalization. If a criterion is\nnot applicable, remove its points from the denominator instead of treating\nit as failed.\n\n## Grade bands\n\n- `A`: 90-100\n- `B`: 80-89\n- `C`: 65-79\n- `D`: 50-64\n- `F`: below 50\n\n## Confidence levels\n\n- `High`: sitemap available and at least 12 sampled pages across at least\n  four page types\n- `Medium`: six to 11 sampled pages, or weaker coverage of page types\n- `Low`: fewer than six sampled pages, or homepage-biased sampling\n\n## Foundational caps\n\nApply these after computing the raw score:\n\n- No `sitemap.xml` and no `llms.txt`: maximum grade `C`\n- Markdown delivery fails on most sampled pages and no usable alternate\n  markdown path exists: maximum grade `D`\n- Main content is missing from initial HTML on more than 25% of sampled\n  pages: maximum grade `D`\n- `robots.txt` blocks broad crawl access to the docs site and the block is\n  not clearly intentional: maximum grade `F`\n\nOptional manifest gaps alone must not drop a docs-only host below `B`.\n\n## Categories\n\n### 1. Discovery and policy - 15 points\n\n- `5` `llms.txt` exists, is fetchable, and is useful for agent discovery\n- `4` `sitemap.xml` exists and includes the main docs corpus\n- `4` `robots.txt` is accessible and does not unintentionally block major\n  crawl agents or search agents\n- `2` curated bulk-discovery aid exists, such as `llms-full.txt` or an\n  equivalent machine-readable catalog\n\nWhen `llms.txt` exists, sample some URLs from it. Stale or misleading\ndiscovery links should reduce this category even if the file itself exists.\n\n### 2. Retrieval and markdown delivery - 25 points\n\n- `8` `Accept: text/markdown` works on sampled pages or an equivalent\n  negotiated markdown response exists\n- `5` a stable direct markdown route works on sampled pages\n- `5` page-level markdown hints, alternates, or UI actions point to a\n  working markdown URL\n- `4` markdown responses strip navigation chrome and preserve headings,\n  links, and code blocks cleanly\n- `3` HTML and markdown stay in parity across the sampled set\n\n### 3. Structure and semantics - 20 points\n\n- `6` sampled pages have one `h1` and a mostly consistent heading hierarchy\n- `5` `main` or `article` marks the primary content and the content is\n  present in the initial HTML\n- `4` canonical tags and stable final URLs are correct\n- `3` structured data such as breadcrumbs or article metadata exists where\n  appropriate\n- `2` headings expose stable anchors or deep-link targets, and the HTML title\n  or H1 stays reasonably aligned with the markdown H1\n\n### 4. Crawlability and delivery behavior - 15 points\n\n- `5` crawl directives are sane for a public docs property\n- `4` the site does not depend on client-side rendering to expose core\n  content\n- `3` cache and freshness signals are reasonable for bots, such as\n  `ETag`, `Last-Modified`, or useful cache headers\n- `3` redirect chains are short and predictable\n\n### 5. Machine-readable surfaces - 10 points\n\n- `4` API or reference sections expose OpenAPI, schema, or downloadable\n  machine-readable assets where relevant\n- `3` pages with interactive JavaScript reference UIs still provide a usable\n  non-JS fallback such as markdown, YAML, or another directly linked asset\n- `3` tool manifests such as MCP, plugin, or agent descriptors exist only\n  when the audited host is actually meant to expose tools\n\n### 6. Content legibility - 15 points\n\n- `5` markdown is clean and low-noise rather than a dump of site chrome\n- `4` headings and section intros are specific enough for retrieval and\n  chunking\n- `3` fenced code blocks are mostly language-tagged and remain copyable and\n  interpretable\n- `3` repeated banners, chat chrome, consent overlays, or other boilerplate\n  do not overwhelm the main content\n\n## Scoring guidance\n\nUse the full category only when the signal is consistently good across the\nsample. Partial credit is expected.\n\nExamples:\n\n- A sitewide `llms.txt` that exists but is stale or too shallow may earn\n  partial credit rather than full credit.\n- If markdown works only on some page types, score that criterion based on\n  observed coverage instead of failing or passing it outright.\n- If a working markdown route exists but the page advertises a dead\n  alternate URL, deduct in markdown discoverability rather than in raw\n  markdown availability.\n- If `llms.txt` exists but points to stale, broken, or inconsistent paths,\n  deduct in discovery rather than in core fetchability.\n- If tool manifests are irrelevant to the host, mark them `N/A`.\n- If a major page type is weaker than the rest of the site, note that\n  explicitly instead of letting stronger page types hide it in the average.\n\n## Reporting guidance\n\nFor every category, include one line that explains the score:\n\n- what was tested\n- what passed\n- what limited the score\n\nUse evidence from live fetches. Do not score from assumptions about the\nframework or source repository.\n","isInternal":false,"tokens":1180,"sizeBytes":4947},{"name":"SKILL.md","path":".agents/skills/agent-readiness-audit/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/agent-readiness-audit/SKILL.md","title":"agent-readiness-audit","category":"anthropic-skill","format":"markdown","content":"---\nname: agent-readiness-audit\ndescription: >\n  Audit a documentation site for agent-friendliness: discovery, markdown\n  delivery, crawlability, semantic structure, machine-readable surfaces,\n  and content legibility. Use when asked to assess docs.docker.com or any\n  docs site for AI/agent readiness, produce a scored report, compare with\n  external scanners, or generate a remediation list. Triggers on:\n  \"audit docs for agent readiness\", \"how agent-friendly is docs.docker.com\",\n  \"score our docs for AI agents\", \"review llms.txt / markdown / crawlability\",\n  \"create an agent-readiness remediation plan\".\nargument-hint: \"<base-url>\"\n---\n\n# Agent Readiness Audit\n\nAudit the live site, not the source tree alone. Prefer the same fetch path\nan external agent would use in the wild: direct HTTP requests, sitemap\nsampling, and page-level inspection.\n\nDo not reduce the result to a homepage-only scan or a binary checklist.\n\n## 1. Set scope\n\nUse `$ARGUMENTS` as the base URL when provided. Otherwise infer the base\nURL from context and state the assumption.\n\nDecide whether the host being audited is:\n\n- a docs-only host\n- an app/tool host\n- a mixed host\n\nThis matters for optional checks such as MCP, plugin manifests, or other\ntool discovery files. Do not penalize a docs-only host for missing\ntooling manifests that belong on a separate service.\n\nFor `docs.docker.com`, treat the public docs host as docs-only. Docker's\nMCP server is published separately, so missing MCP files on the docs host\nshould be reported as `N/A`, not as a failure.\n\n## 2. Gather sitewide signals\n\nAlways check these resources first:\n\n- `/llms.txt`\n- `/llms-full.txt`\n- `/robots.txt`\n- `/sitemap.xml`\n\nOnly check host-level tool manifests when the host is an app/tool host,\nmixed host, or explicitly advertises them:\n\n- `/.well-known/ai-plugin.json`\n- `/.well-known/agent.json`\n- `/.well-known/agents.json`\n\nUse the bundled script for a baseline:\n\n```bash\nbash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\nThe script produces baseline evidence only. You still need to interpret\nwhat matters for a docs property and score it with the rubric.\n\nFor docs-only hosts, you may skip tool-manifest probes to reduce noise:\n\n```bash\nCHECK_TOOL_MANIFESTS=0 \\\n  bash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\n## 3. Sample representative pages\n\nUse the sitemap when available. Do not rely on the homepage alone.\n\nIf `llms.txt` exists, sample some URLs from it as well. This helps catch\nstale or misleading discovery surfaces that a sitemap-only sample would miss.\n\nSample at least 12 pages when the site is large enough, and cover multiple\npage types:\n\n- homepage or docs landing page\n- section landing pages\n- task guides\n- product manuals\n- reference or API pages\n- tutorial or learning pages\n\nIf the sitemap is missing or unusable, discover pages through internal\nlinks and note the lower confidence.\n\nIf the site has distinct delivery patterns, sample each one. For example:\n\n- normal content pages\n- generated reference pages\n- versioned docs\n- localized docs\n\n## 4. Run fetch-path checks on each sample\n\nFor each sampled page, verify:\n\n- HTML fetch status, content type, and final URL\n- `Accept: text/markdown` behavior\n- direct markdown route behavior such as `<page>.md` or another stable path\n- page-level markdown alternate links and whether they actually resolve\n- whether page actions such as \"Open Markdown\" agree with the working route\n- whether the HTML title or H1 matches the markdown H1 closely enough for\n  retrieval parity\n- whether main content is present in the initial HTML\n- redirect chain length and canonical URL consistency\n- obvious chrome/noise in the markdown response\n\nDo not assume a `.md` mirror exists just because another site uses one.\nVerify the actual markdown path the site exposes.\n\nTreat these as separate signals:\n\n- negotiated markdown works\n- a stable direct markdown URL works\n- the page advertises the correct markdown URL\n\nIf the page advertises dead markdown alternates but a working markdown route\nexists, do not fail markdown delivery outright. Score it as a discoverability\nand consistency problem instead.\n\nFor API or generated reference pages, also verify whether a machine-readable\nasset such as OpenAPI YAML is directly linked and fetchable.\n\n## 5. Judge structure and legibility\n\nMeasure structural signals:\n\n- exactly one `h1`\n- sane heading hierarchy\n- `main` and `article` presence where appropriate\n- canonical tags\n- JSON-LD or breadcrumb structured data\n- stable anchors and deep-linkable headings\n\nAlso make a qualitative judgment about agent legibility:\n\n- markdown strips site chrome cleanly\n- headings are specific and task-oriented\n- code blocks stay intelligible without client-side JS\n- the page is not dominated by banners, injected chat, or nav noise\n\nMeasure code block labeling explicitly when code samples are common. A page\ntype with many untagged fenced blocks should lose points even if the prose is\notherwise clean.\n\nFor page types that intentionally render interactive UIs with JavaScript,\njudge them separately from normal docs pages. If the HTML shell is thin,\ncheck whether the page still provides:\n\n- a fetchable markdown summary\n- a directly linked machine-readable asset\n- a usable non-JS fallback\n\n## 6. Score with the rubric\n\nUse [references/rubric.md](references/rubric.md).\n\nRules:\n\n- score only what you verified\n- mark non-applicable checks as `N/A`\n- normalize the final score against applicable points only\n- do not let optional manifest checks dominate the grade\n\nApply the foundational caps from the rubric. A site with broken discovery\nor broken markdown delivery should not earn a high grade because it has\nclean metadata.\n\nDo not average away a weak page type. If one major page type, such as API\nreference, is materially worse than the rest of the corpus, call it out as\nthe weakest segment and reflect it in the category notes.\n\n## 7. Compare with external scanners when useful\n\nIf external scanner results are available, compare them to your live\nfindings. Treat them as secondary evidence.\n\nIf a scanner and the live fetch disagree:\n\n- trust the live fetch\n- report the mismatch explicitly\n- explain whether the scanner is testing a different assumption\n\n## 8. Produce a remediation list\n\nTurn findings into a short backlog:\n\n- `P0`: fetchability or discovery blockers\n- `P1`: recurring structural or parity issues\n- `P2`: polish, optional manifests, or low-impact enhancements\n\nFor each remediation, include:\n\n- the failing signal\n- why it matters to agents\n- a concrete fix\n- whether it is sitewide or page-type-specific\n\n## 9. Report in a stable format\n\nUse [references/report-template.md](references/report-template.md).\n\nAlways include:\n\n- overall score and grade\n- confidence level\n- sampled URLs or sample strategy\n- category scores\n- highest-priority findings\n- remediation backlog\n\n## Notes\n\n- Favor docs-delivery checks over marketing-site heuristics.\n- Do not fail a docs host for lacking MCP or plugin manifests unless the\n  host itself is meant to expose tools.\n- Treat raw byte size as supporting evidence, not as a primary scoring input.\n- Prefer short evidence excerpts and commands over long copied page text.\n","frontmatter":{"name":"agent-readiness-audit","description":"Audit a documentation site for agent-friendliness: discovery, markdown delivery, crawlability, semantic structure, machine-readable surfaces, and content legibility. Use when asked to assess docs.docker.com or any docs site for AI/agent readiness, produce a scored report, compare with external scanners, or generate a remediation list. Triggers on: \"audit docs for agent readiness\", \"how agent-friendly is docs.docker.com\", \"score our docs for AI agents\", \"review llms.txt / markdown / crawlability\", \"create an agent-readiness remediation plan\".\n","argument-hint":"<base-url>"},"isInternal":false,"tokens":1614,"sizeBytes":7280},{"name":"SKILL.md","path":".agents/skills/create-lab-guide/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/create-lab-guide/SKILL.md","title":"create-lab-guide","category":"anthropic-skill","format":"markdown","content":"---\nname: create-lab-guide\ndescription: \"Clone a dockersamples Labspace repo, extract learning objectives and module structure from labspace.yaml, and produce a Hugo guide page under content/guides/ with correct frontmatter, labspace-launch shortcode, and Docker docs style compliance. Use when asked to create a lab guide, write a Labspace page, add a Docker lab tutorial, migrate a lab to docs, or document a hands-on lab.\"\n---\n\n# Create Lab Guide\n\nCreate a guide page for a Docker Labspace: clone the source repo, extract\nstructure from `labspace.yaml`, write the Hugo markdown page, and validate.\n\n## Inputs\n\n- **REPO_NAME**: GitHub repo in the `dockersamples` org (e.g. `labspace-ai-fundamentals`)\n\n## Step 1: Clone the labspace repo\n\n```bash\nTMPDIR=$(mktemp -d)\ngit clone --depth 1 https://github.com/dockersamples/{REPO_NAME}.git \"$TMPDIR/{REPO_NAME}\"\n```\n\n## Step 2: Extract key information\n\nRead these files from the cloned repo:\n\n| File | Purpose |\n|------|---------|\n| `README.md` | Lab purpose and overview |\n| `labspace/labspace.yaml` | Module structure and content paths |\n| `labspace/*.md` | Module content (only files listed in `labspace.yaml`) |\n| `.github/workflows/*.yml` | Published Compose file URL for the launch command |\n| `compose.override.yaml` | Check for top-level `model` specs (triggers `model-download` param) |\n\nExtract:\n1. A short description for the `description` and `summary` frontmatter fields.\n2. Learning objectives from the module content.\n3. Whether a model download is required (`compose.override.yaml` → top-level `model` key).\n\n## Step 3: Write the guide markdown\n\nPlace the file at `content/guides/lab-{GUIDE_ID}.md`.\n\n```markdown\n---\ntitle: \"Lab: { Short title }\"\nlinkTitle: \"Lab: { Short title }\"\ndescription: |\n  A short description of the lab for SEO and social sharing.\nsummary: |\n  A short summary of the lab for the guides listing page. 2-3 lines.\nkeywords: AI, Docker, Model Runner, agentic apps, lab, labspace\naliases: # Include only for AI-related labs\n  - /labs/docker-for-ai/{REPO_NAME_WITHOUT_LABSPACE_PREFIX}/\nparams:\n  tags: [ai, labs]\n  time: 20 minutes\n  resource_links:\n    - title: A resource link pointing to relevant documentation or code\n      url: /ai/model-runner/\n    - title: Labspace repository\n      url: https://github.com/dockersamples/{REPO_NAME}\n---\n\nShort explanation of the lab and what it covers.\n\n## Launch the lab\n\n{{< labspace-launch image=\"dockersamples/{REPO_NAME}\" >}}\n\n## What you'll learn\n\nBy the end of this Labspace, you will have completed the following:\n\n- Objective #1\n- Objective #2\n- Objective #3\n\n## Modules\n\n| # | Module | Description |\n|---|--------|-------------|\n| 1 | Module #1 | Description of module #1 |\n| 2 | Module #2 | Description of module #2 |\n| 3 | Module #3 | Description of module #3 |\n```\n\nConditional rules:\n- All lab guides **must** include `labs` in `params.tags`.\n- AI-related labs: also add `ai` tag and an alias under `/labs/docker-for-ai/`.\n- If a model download is required: add `model-download: true` to the `labspace-launch` shortcode.\n\n## Step 4: Apply Docker docs style rules\n\nFollow STYLE.md and COMPONENTS.md. Key rules:\n\n| Avoid | Use instead |\n|-------|-------------|\n| \"we\", \"let's\" | Imperative voice or \"you\" |\n| \"simply\", \"easily\", \"just\" | Remove the hedge word |\n| \"allows you to\" / \"enables you to\" | \"lets you\" or rephrase |\n| \"click\" | \"select\" |\n| Bold for emphasis / product names | Bold only for UI elements |\n| \"currently\", \"new\", \"recently\" | Remove time-relative language |\n\nUse `console` as the language hint for shell blocks with `$` prompts.\nUse contractions (\"it's\", \"you're\", \"don't\").\n\n## Step 5: Validate\n\n1. Confirm frontmatter has `title`, `description`, `keywords`, and `params.tags` including `labs`.\n2. Run `npx --no-install rumdl fmt <file>` to format.\n3. Run `docker buildx bake lint vale` and fix any errors.\n4. Re-read the file and verify: correct shortcode syntax, objectives match source content, modules match `labspace.yaml`, no vendored paths edited.\n\nDo not proceed to commit until validation passes.\n","frontmatter":{"name":"create-lab-guide","description":"Clone a dockersamples Labspace repo, extract learning objectives and module structure from labspace.yaml, and produce a Hugo guide page under content/guides/ with correct frontmatter, labspace-launch shortcode, and Docker docs style compliance. Use when asked to create a lab guide, write a Labspace page, add a Docker lab tutorial, migrate a lab to docs, or document a hands-on lab."},"isInternal":false,"tokens":1054,"sizeBytes":4069},{"name":"SKILL.md","path":".agents/skills/create-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/create-pr/SKILL.md","title":"create-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: create-pr\ndescription: >\n  Push the current branch and create a pull request against docker/docs.\n  Use after changes are committed and reviewed. \"create a PR\", \"submit the\n  fix\", \"open a pull request for this\".\n---\n\n# Create PR\n\nPush the branch and create a properly structured pull request.\n\n## 1. Verify the branch\n\nConfirm you're on a dedicated branch, not the default branch:\n\n```bash\ngit branch --show-current   # must not be main or master\n```\n\nIf this returns `main` or `master`, stop. Create a branch and move your\ncommits onto it before continuing.\n\nConfirm commits exist and the working tree is clean:\n\n```bash\ngit log --oneline main..HEAD   # confirm commits exist\ngit status --porcelain         # must print nothing\n```\n\nIf `git status --porcelain` prints anything, there are uncommitted or\nunstaged changes. Stop and commit them — or unstage stray files like\n`package-lock.json` — before opening a PR. Don't open a PR mid-edit.\n\n## 2. Push the branch\n\nIdentify the remote that points at your fork. Inspect the remotes:\n\n```bash\ngit remote -v\n```\n\nIf `origin` is your fork, use it. If `origin` points at canonical\n`docker/docs` (the upstream), push to your separate fork remote instead —\nnever push the branch to `docker/docs` directly:\n\n```bash\nFORK_REMOTE=origin   # or the name of your fork remote if origin is upstream\ngit push -u \"$FORK_REMOTE\" <branch-name>\n```\n\n## 3. Create the PR\n\nBefore creating a PR for an issue, check whether that issue already has an open\nlinked PR:\n\n```bash\ngh api repos/docker/docs/issues/<issue-number>/timeline --paginate \\\n  --jq '.[] | select((.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") and .source.issue.pull_request and .source.issue.state==\"open\") | {url: .source.issue.html_url, title: .source.issue.title}'\n```\n\nIf this returns an open PR that addresses the same issue, stop. Don't open a\nduplicate PR; report the existing PR instead. Only proceed if there is no open\nlinked PR, or if the existing PR clearly does not address the issue and you\nexplain why in the new PR body.\n\nDerive the fork owner dynamically from the same fork remote you pushed to:\n\n```bash\nFORK_OWNER=$(git remote get-url \"$FORK_REMOTE\" | sed -E 's|.*[:/]([^/]+)/[^/]+(\\.git)?$|\\1|')\n```\n\n```bash\ngh pr create --repo docker/docs \\\n  --head \"${FORK_OWNER}:<branch-name>\" \\\n  --title \"<concise summary under 70 chars>\" \\\n  --body \"$(cat <<'EOF'\n## Summary\n\n<1-2 sentences: what was wrong and what was changed>\n\nCloses #NNNN\n\nGenerated by <active coding agent name>\nEOF\n)\"\n```\n\nPrefix the title with the change type to match repo convention — `docs:` for\ndocumentation changes (or another scope like `hub:` when appropriate), for\nexample `docs: fix broken link on install page`.\n\nKeep the body short. Reviewers need to know what changed and why — nothing\nelse. Do **not** add a \"Test plan\" section — documentation PRs don't need one.\n\nUse an accurate disclosure footer that names the active coding agent, for\nexample `Generated by Codex` or `Generated by Claude Code`.\n\n### Optional: Netlify preview entry path\n\nIf the PR primarily edits a single page or a focused section of pages, add a\n`@netlify` stanza to the PR body (for example, just below the Summary). This\nsets the entry path for the Netlify deploy preview so reviewers land on the\nedited page instead of the site root:\n\n```markdown\n@netlify /desktop/setup/install/\n```\n\nThe stanza takes a single published URL path. Derive it from the source file\npath: drop the `content/` prefix and `.md` suffix, strip the `/manuals`\nsegment, and add a trailing slash. For example,\n`content/manuals/desktop/setup/install/mac-install.md` becomes\n`/desktop/setup/install/mac-install/`.\n\nOnly add this when the change is focused on one page or section. Skip it for\nPRs that touch many unrelated pages — there is no useful single entry path.\n\n### Optional: Preview links\n\nWhen the change is focused, also add direct links to the deploy preview in the\nPR body so reviewers can jump straight to the affected pages. The preview URL\nembeds the PR number:\n\n```\nhttps://deploy-preview-<pr-number>--docsdocker.netlify.app/path/to/page/\n```\n\nThe PR number isn't known until `gh pr create` returns, so add these links\nafter creating the PR by updating the body:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --body \"...\"\n```\n\nUse the same source-path-to-URL mapping as the `@netlify` stanza above.\n\n## 4. Apply labels and request review\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<pr-number>/labels \\\n  --method POST \\\n  --field 'labels[]=status/review'\n```\n\nRequest review:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --add-reviewer docker/docs-team\n```\n\nVerify the reviewer was assigned:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json reviewRequests \\\n  --jq '.reviewRequests[].slug'\n```\n\nIf the team doesn't appear, use the API directly:\n\n```bash\ngh api repos/docker/docs/pulls/<pr-number>/requested_reviewers \\\n  --method POST --field 'team_reviewers[]=docs-team'\n```\n\n## 5. Report\n\nPrint the PR URL and current CI state:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json url,state\ngh pr checks <pr-number> --repo docker/docs --json name,state\n```\n\n## Notes\n\n- Always use `Closes #NNNN` (not \"Fixes\") for GitHub auto-close linkage\n- One issue, one branch, one PR — never combine\n","frontmatter":{"name":"create-pr","description":"Push the current branch and create a pull request against docker/docs. Use after changes are committed and reviewed. \"create a PR\", \"submit the fix\", \"open a pull request for this\".\n"},"isInternal":false,"tokens":1346,"sizeBytes":5403},{"name":"openai.yaml","path":".agents/skills/curate-whats-new/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/curate-whats-new/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Curate What's New\"\n  short_description: \"Curate noteworthy Docker launches from merged docs\"\n  default_prompt: \"Use $curate-whats-new to curate Docker launches published during the requested date range.\"\n","isInternal":false,"tokens":51,"sizeBytes":232},{"name":"SKILL.md","path":".agents/skills/curate-whats-new/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/curate-whats-new/SKILL.md","title":"curate-whats-new","category":"anthropic-skill","format":"markdown","content":"---\nname: curate-whats-new\ndescription: Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights.\n---\n\n# Curate What's New\n\nTreat merged documentation as evidence that a capability shipped, but do not\ntreat a documentation change as news by itself.\n\nInclude an item only when the merged documentation directly shows all of the\nfollowing:\n\n- A released user-facing feature, material enhancement, or broader availability\n  milestone that was not available before the period\n- A substantial capability or workflow, not new syntax or a small control\n  within an existing workflow\n- Enough Docker-wide editorial significance to merit proactively telling users\n  about it outside product release notes\n- A useful published page and a factual title and description\n\nApply a high bar. The result is a curated launch archive, not a complete\nchangelog. A specialized feature can qualify when its user impact is\nsubstantial. A quiet period can produce few or no items.\n\n## Exclusions\n\nExclude documentation maintenance; fixes; rewrites; guidance for old behavior;\nroutine release or generated-content syncs; limitations, prerequisites, and\nworkarounds; narrow flags, settings, command variants, protocols, and\ncompatibility changes; incremental UI, safety, permissions, or observability\nimprovements; and lower-level Engine, Build, networking, or storage changes.\nThese qualify only when they are part of an independently newsworthy\nproduct-level launch.\n\nJudge the user outcome, not PR size, product popularity, labels, changed lines,\na dedicated page, or the existence of a new API or command.\n\n## Select highlights\n\nInclude every qualifying launch; do not impose a quota. Mark the five most\nimportant as `featured: true`, or all items when fewer than five qualify. Rank\nby the magnitude and distinctness of the user outcome and the value of helping\nits audience discover it. Breadth can matter, but a major capability for a\nspecialized audience can outrank a smaller change for a broad audience. Recency\nand product variety are not ranking goals.\n\nCreate one item per launch and combine PRs that document the same launch.\nPreserve existing copy while it remains accurate and qualifies. Change featured\nstatus only when the relative importance of the candidate set changes.\n\n## Procedure\n\n1. Read `data/whats-new.json`.\n2. Determine the review mode from the request:\n   - For an incremental review, list PRs merged from the day after the supplied\n     checkpoint through the end of the publication window. Retain existing\n     items inside the publication window without re-reviewing their source PRs.\n   - For a full review, inspect every PR merged in the supplied publication\n     window.\n3. List PRs in the range that applies to the review mode:\n\n   ```console\n   $ gh pr list --repo docker/docs --state merged --search 'merged:START..END' --limit 200\n   ```\n\n   If an incremental search returns no PRs, skip candidate inspection and only\n   remove expired items.\n4. Inspect the diff and resulting pages for every plausible new candidate.\n5. Decide what qualifies using only evidence in the merged documentation.\n6. Remove existing items published before the requested publication window.\n   Add newly qualifying launches, combine related PRs, and reconsider featured\n   status across the resulting list. Do not replace or rewrite retained items\n   merely because they were not part of the incremental candidate range.\n7. Replace `period_start`, `period_end`, and `items` in\n   `data/whats-new.json`. Sort items by `published` date, newest first.\n8. Write `.pr-body.md` with the publication period, selected highlights and\n   source PRs, plus concise reasons for plausible exclusions.\n\nEach item must contain `product`, `title`, `description`, `url`, `published`,\n`source_prs`, and `featured`. Use the canonical product name, a published\ninternal URL, the merge date in `YYYY-MM-DD` format, and source PR numbers.\n\nWrite factual, restrained copy. Avoid superlatives, promotional language, and\nclaims about ease or importance. Do not modify tracked files other than\n`data/whats-new.json`.\n","frontmatter":{"name":"curate-whats-new","description":"Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights."},"isInternal":false,"tokens":897,"sizeBytes":4294},{"name":"SKILL.md","path":".agents/skills/fix-issue/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/fix-issue/SKILL.md","title":"fix-issue","category":"anthropic-skill","format":"markdown","content":"---\nname: fix-issue\ndescription: >\n  Fix a single GitHub issue end-to-end: triage, research, write the fix,\n  review, and create a PR. Use when asked to fix an issue: \"fix issue 1234\",\n  \"resolve #500\", \"create a PR for issue 200\".\nargument-hint: \"<issue-number>\"\n---\n\n# Fix Issue\n\nGiven GitHub issue **$ARGUMENTS**, decide what to do with it and either\nclose it or fix it. This skill orchestrates the composable skills — it owns\nthe decision tree, not the individual steps.\n\n## 1. Triage\n\nInvoke `/triage-issue $ARGUMENTS` to understand the issue and decide what\nto do. This runs in a forked subagent and returns a verdict.\n\n## 2. Act on the triage result\n\nIf triage says **close it** — comment with the reason and close:\n```bash\ngh issue close $ARGUMENTS --repo docker/docs \\\n  --comment \"<one sentence explaining why>\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **escalate upstream** — comment noting the repo and stop:\n```bash\ngh issue comment $ARGUMENTS --repo docker/docs \\\n  --body \"This needs to be fixed in <upstream-repo>.\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **leave it open** — comment explaining what was checked and\nwhat's unclear. Do not close.\nDone.\n\nEnd every issue comment with an accurate agent-disclosure footer that names\nthe active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n\nIf triage says **fix it** — proceed to step 3.\n\n## 3. Research\n\nInvoke `/research` to locate affected files, verify facts, and identify\nthe fix. The issue context carries over from triage. This runs inline —\nfindings stay in conversation context for the write step.\n\nIf research reveals the issue is upstream or cannot be fixed (e.g.\nunverifiable URLs), comment on the issue and stop.\n\n## 4. Write\n\nInvoke `/write` to create a branch, make the change, format, self-review,\nand commit.\n\n## 5. Review\n\nInvoke `/review-changes` to check the diff for correctness, coherence, and\nmechanical compliance. This runs in a forked subagent with fresh context.\n\nIf issues are found, fix them and re-review until clean.\n\n## 6. Create PR\n\nInvoke `/create-pr` to push the branch and open a pull request.\n\n## 7. Return to main\n\n```bash\ngit checkout main\n```\n\n## 8. Report\n\nSummarize what happened: the issue number, what was done (closed, escalated,\nfixed with a PR link), and why — in a sentence or two.\n","frontmatter":{"name":"fix-issue","description":"Fix a single GitHub issue end-to-end: triage, research, write the fix, review, and create a PR. Use when asked to fix an issue: \"fix issue 1234\", \"resolve #500\", \"create a PR for issue 200\".\n","argument-hint":"<issue-number>"},"isInternal":false,"tokens":600,"sizeBytes":2391},{"name":"openai.yaml","path":".agents/skills/maintain-pr/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/maintain-pr/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Maintain PR\"\n  short_description: \"Maintain an authored PR through review and CI\"\n  default_prompt: \"Use $maintain-pr to maintain this pull request through CI and review follow-up.\"\n","isInternal":false,"tokens":48,"sizeBytes":210},{"name":"SKILL.md","path":".agents/skills/maintain-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/maintain-pr/SKILL.md","title":"maintain-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: maintain-pr\ndescription: >\n  Maintain and follow up on a single Docker documentation pull request that\n  you own or are responsible for updating. Check CI and review feedback, fix\n  actionable failures, push changes, reply to comments, and report status.\n  Use for requests such as \"babysit this PR\", \"check the status of my PR\",\n  \"fix CI on my PR\", or \"address review comments on #500\". Do not use for\n  maintainer review of an incoming contribution; use review-pr for that.\n---\n\n# Maintain PR\n\nDo one maintenance pass over the specified author-owned PR: inspect its\nstate, fix actionable failures or feedback, reply to reviewers, and report\nthe result. This workflow may modify the branch and GitHub because the user\nis asking to maintain the PR. Do not apply it to an incoming PR merely\nbecause the user asks to review or assess it.\n\n## 1. Gather PR state\n\n```bash\ngh pr view <PR> --repo docker/docs --json state,title,url,headRefName,headRepositoryOwner,comments,reviews,reviewDecision\ngh pr checks <PR> --repo docker/docs --json name,state,detailsUrl\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, in_reply_to_id}]'\n```\n\nAlways check both top-level reviews and inline comments. A review with an\nempty body may still contain line-level feedback. Confirm that the PR is one\nthe user owns or is authorized to update before checking out or pushing its\nbranch. If not, stop and use `review-pr`.\n\n## 2. Handle terminal states\n\nIf merged, report the final state and identify unanswered review comments.\nReply only when the user remains responsible for follow-up.\n\nIf closed without merge, read the closing context and report the reason.\nCommon causes include maintainer rejection, supersession, or automation.\n\n## 3. Diagnose CI failures\n\n- Read the failure details.\n- Determine whether the failure comes from the PR or predates it.\n- Fix actionable failures in the PR's changed files.\n- Report pre-existing or upstream failures without changing unrelated files.\n\nFollow repository instructions for formatting, targeted linting, explicit\nstaging, commits, and pushes. Preserve unrelated working-tree changes.\n\n## 4. Address review feedback\n\nTreat every review comment as a claim to verify. Implement it only when the\nevidence supports it; explain any evidence-based disagreement.\n\nAfter each fix:\n\n1. Format and validate the changed files.\n2. Commit and push the focused change.\n3. Reply to every addressed thread with what changed or why no change was\n   made.\n4. End replies with an accurate agent-disclosure footer, such as\n   `Generated by Codex`.\n5. Resolve threads only after replying.\n6. Re-request review when appropriate.\n\nUse the inline comment endpoint to reply:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --method POST \\\n  --field in_reply_to=<COMMENT_ID> \\\n  --field body='<RESPONSE>'\n```\n\nUse GraphQL to retrieve unresolved review-thread IDs and resolve only the\nthreads that were addressed. Do not silently fix feedback without replying.\n\n## 5. Report\n\n```markdown\n## PR #<number>: <title>\n\n**State:** <open, merged, or closed>\n**CI:** <passing, failing, or pending>\n**Review:** <approved, changes requested, or pending>\n**Action taken:** <changes, replies, and thread resolution, or none needed>\n```\n","frontmatter":{"name":"maintain-pr","description":"Maintain and follow up on a single Docker documentation pull request that you own or are responsible for updating. Check CI and review feedback, fix actionable failures, push changes, reply to comments, and report status. Use for requests such as \"babysit this PR\", \"check the status of my PR\", \"fix CI on my PR\", or \"address review comments on #500\". Do not use for maintainer review of an incoming contribution; use review-pr for that.\n"},"isInternal":false,"tokens":751,"sizeBytes":3299},{"name":"SKILL.md","path":".agents/skills/migrate-content-ia/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/migrate-content-ia/SKILL.md","title":"migrate-content-ia","category":"anthropic-skill","format":"markdown","content":"---\nname: migrate-content-ia\ndescription: >\n  Handle Hugo docs information-architecture moves: discover old vs new URLs,\n  add front matter aliases (Phase 1), update in-repo links (Phase 2), interactive\n  List 2 resolution and fragment validation (Phase 3; no guessing). Supports\n  PR-scoped mapping plus whole-content sweeps for inbound links to that mapping,\n  or a full-site follow-up. Triggers on: \"IA migration\", \"redirects for moved\n  pages\", \"fix links after content move\", \"PR-scoped link/anchor pass\",\n  \"aliases for old URLs\". After branch work, chain the review-changes skill\n  (main...HEAD) before a PR. Agents must run the in-file required procedure\n  and definition of done, not the phases alone in isolation.\n---\n\n# Migrate content IA (redirects + links + anchors)\n\nUse this skill when pages **move or rename** under `content/` and you must\npreserve old public URLs and/or fix cross-references. Work in **phases**;\nchoose **PR-scoped** vs **full-site** mode per run.\n\n**Read first:** **CLAUDE.md** / **AGENTS.md** (URL rules, vendored areas, external\nlinks, special cases) and **hugo.yaml** (`permalinks`, `refLinksErrorLevel`,\n`disablePathToLower`). For **prose and link text**, follow **STYLE.md**; for\n**components, front matter, and link examples**, follow **COMPONENTS.md**.\n\n**Related skills:** **research** helps map moves and find inbound links; **write**\ncommits minimal edits. Run this skill’s phases after the move is identified (or\nin parallel with research for large IA work).\n\n## Agent: required procedure (do not skip)\n\n**Common mistake (wrong):** use **`git diff main...HEAD` (or the PR’s file\nlist) as the full set of places to fix links** for a migration. That set shows\n**what *moved***; it is **not** the list of every page that **points *to*** a\nmoved page. Inbound stragglers are often in files the PR **never** touched. You\nmust still **sweep the repo** for every string in the **old path and published-URL set**\nfor this run, not only for “files in the diff.”\n\n**Definition of done (when the migration is *finished*):** **Both** of the\nfollowing (unless the user or **AGENTS.md** **explicitly defers** a **List 2**\nitem in **Phase 3**; document the deferral):\n\n1. **`docker buildx bake validate`** passes for the branch, with no new\n   build/link errors from this work.\n2. A **sweep of the old path and published-URL set for this run** (see\n   [Sweep commands](#sweep-commands) below) finds **no** remaining\n   migration-relevant **inbound** reference—**including**:\n   - links to an old **source** path (plain `.md` and equivalent `ref` forms),\n   - links that use the old path **and** a `#fragment`,\n   - and, where your mapping includes them, old **published-style** `link:` /\n   `url:` / full-site URL strings,  \n   **except** intentional entries to keep: for example `aliases` on the **new**\n   canonical page, or **redirects.yml** *sources* you must not edit per policy.\n   (A hit on a **source** that is only an `alias` line on the new page is\n   **expected**—do not “fix” that away; distinguish alias rows from straggler\n   links in body or nav config.)\n\n**Chaining (policy):** when this branch’s content work is ready for handoff,\n**run the [review-changes](../review-changes/SKILL.md) skill** on\n**`main...HEAD`** (or **`merge-base`…`HEAD`** for a different target branch) so\nthe **whole branch** is re-read for cross-page issues before opening a PR. Do\nnot treat phases 0–3 alone as the final check.\n\n**Run in order (mandatory for agents):**\n\n1. **Scope the moves (mapping input):** set the Git range like **review-changes**\n   (for a PR to `main`: `git diff --name-only main...HEAD`; for another target:\n   `BASE=$(git merge-base <target-branch> HEAD)` then\n   `git diff --name-only $BASE...HEAD`, as in **Phase 0.5**). Include\n   renames; build the **old → new** table (source and published) per **Phase\n   0**.\n2. **Sweep and list:** for every **old** path/URL in that table, run\n   [Sweep commands](#sweep-commands) on the **allowed** trees. Record\n   every hit as **List 1** (no `#`) or **List 2** (old path with `#...`) per\n   **Phase 0.5**.\n3. **Phased edits:** **Phase 1** (`aliases`), then **Phase 2** (List 1), then\n   **Phase 3** (List 2) with **no guessing**—as in the sections below.\n4. **Re-sweep** the same old-path set, then run **`docker buildx bake\n   validate`**. The **Definition of done** above is met or you have **explicit\n   defers** for the remainder.\n5. **review-changes:** run **[review-changes](../review-changes/SKILL.md)**\n   on the branch vs **`main`…`HEAD`** (or the correct base) before a PR.\n\n### Sweep commands\n\nUse a **repository** search (e.g. `rg` / your IDE) so **nothing** in the\nallowed scope is only eyeballed.\n\n**Trees to include** (at minimum): all of `content/`, plus **`data/`** and\n**`layouts/`** when a migration can appear in config, `link:`-like fields,\nshortcodes, or hardcoded path strings. Follow **Vendored / generated** rules in\n**AGENTS.md**; do not edit disallowed files.\n\n**What to search for (repeat per row in the old side of the mapping):**\n\n- **Hugo / source form:** path segments that identify the *old* file, e.g.\n  `manuals/.../old-segment/...` or `../old-segment/.../page.md` as your tree\n  uses; include variants that still appear in the repo.\n- **Published / site form:** e.g. `/admin/.../old-slug/` in front matter, nav\n  `url:`, or `https://docs.docker.com/...` in allowed files—**match the\n  file’s** established pattern, per **Conventions** below.\n- **Anchors:** search for the **old path string**; matches that also include\n  `#...` belong on **List 2** for **Phase 3** unless the whole link is\n  a pure path-only case.\n\n[scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) (if present) prints\n**`PR_SCOPE_FILES` only**—it does **not** replace this sweep. Use it to build\nthe **old → new** table, **not** to list where inbound links were fixed.\n\n## Progressive disclosure (optional)\n\nThe procedure below stays in this file. If a run produces a very large\n**old → new** URL table, store that table in **`reference.md`** in this skill\ndirectory and link it from the task summary, so the agent reads the long\nmapping only when needed.\n\n## Modes\n\n- **PR-scoped (typical for a single PR)**  \n  - **What the PR “owns” (focus):** use `git diff` / `base...HEAD` to know which\n    pages and renames the branch actually moves (`PR_SCOPE_FILES`). The **old →\n    new** mapping and **List 1 / List 2** for this migration are defined from\n    **that** work, not from unrelated areas.\n  - **Where to look for stale references (sweep):** search broadly—typically all\n    of `content/` (and config, shortcodes, layouts, per Conventions)—for **inbound**\n    links and fields whose **target** is an **old** path or URL in **this** PR’s\n    mapping. Inbound stragglers are often in files the PR never touched; finding\n    them is **in scope** for this migration.  \n  - **What to edit:** update **any** file in the allowed trees that contains a\n    **migration-relevant** reference (target ∈ this PR’s old path set) according\n    to the phases below. **Do not** treat `PR_SCOPE_FILES` as a hard limit on\n    *which files you may save* for **inbound** link repairs (unless\n    project policy for a given PR says otherwise; then follow policy and\n    **defer** out-of-PR file fixes).\n  - **Out of scope (defer / ignore in this run):** link and anchor problems that\n    are **not** about this PR’s old→new map—e.g. a different area’s own slug\n    issues, rot unrelated to the remapped path set. *Example:* a PR that only\n    remaps `content/strawberry/...` should not “fix the whole site”; it **should**\n    still fix a link under `mango/…` that **points at** an old `strawberry/…` path\n    in the mapping, and **should not** chase **mango/**-only issues that do\n    not involve those old targets.\n\n- **Full-site (complete migration after the PR)**  \n  - Update stragglers **across the repo** (or all inbound links to moved\n    sections), including config-driven `link:` fields if policy allows.  \n  - Still make **minimal** edits; no drive-by rewrites to **unrelated** targets\n    outside the run’s **declared** mapping and lists.\n\n### No guessing\n\n- The agent must **not** guess **replacement paths, published URLs, or fragment\n  IDs** (including for consolidated pages, renamed headings, or\n  “semantic” remaps of `#anchor` → new `#…`). If the user has not given an\n  explicit new target, **ask**, **defer**, or **stop** per **AGENTS.md**; never\n  infer, autocomplete, or substitute a plausible fragment from the target page’s\n  heading list. That rule applies in **every** phase, including after validation\n  in Phase 3.\n\n---\n\n## Conventions (links, anchors, redirects)\n\n### Front matter `aliases` (redirects)\n\n- Per **COMPONENTS.md**, `aliases` are **URLs that redirect to this page**.\n- Add or **merge** on the **new canonical** page; do not drop unrelated\n  entries. Match local examples: **published-style paths** (leading `/`), and\n  **trailing `/`** when that matches existing pages in the same area.\n- **No** speculative redirects for URLs that were never published.\n- **Collision check** before adding: no other page or redirect may already\n  own the same old path.\n- If the site also uses **`data/redirects.yml`**, only add entries when\n  project policy requires it; avoid duplicating the same old URL in\n  `aliases` **and** `redirects.yml` unless maintainers do.\n\n### Internal links in Markdown (STYLE.md + COMPONENTS.md)\n\n- Use **relative paths to source files** (e.g. `../section/page.md`) with\n  **`.md`**, following **COMPONENTS.md** examples, unless the file already\n  uses an established pattern (e.g. some `link:` or nav fields use **published**\n  paths without `manuals` or `.md` — **match the surrounding file**).\n- Keep **CLAUDE.md** / **AGENTS.md** rules: internal ref targets under\n  `content/manuals/...` often use the full **`/manuals/...`** path; published\n  URLs omit the `manuals` segment—do not confuse the two when fixing links.\n- **Link text (STYLE.md):** descriptive, ~**5 words**; no “click here” or\n  “learn more”; **no** end punctuation **inside** the link text; **no** bold/italic\n  on link text unless normal in the sentence.\n- **Headings (STYLE):** **sentence case**; do not rename headings in passing\n  unless the migration requires it (heading changes break fragments).\n\n### Shortcodes and layouts (links not only in Markdown)\n\n- **Phase 2–3 scope includes** any **shortcode or layout partial** (under\n  **Modes**, search broadly for inbound links to the migration; **edits** follow\n  the same file-level rules as for Markdown) that emits links: e.g. `ref` /\n  `relref`, `link` fields in shortcode args, or hardcoded\n  `docs.docker.com` / path strings. Grep for old paths, slugs, and fragments\n  under `layouts/shortcodes/` (and `layouts/_default/` if partials build nav).\n- Match each file’s existing pattern; do not rewrite working shortcode style\n  just to “clean up.”\n\n### Fragments / anchors (Phase 3)\n\n- List 1 / List 2: fragment-bearing **cross-references to old paths** are tracked\n  on **List 2** in Phase 0.5; do not bulk-rewrite them in the **List 1** pass\n  (Phase 2). See Phase 0.5 and Phase 2.\n- **Valid `#fragment` values:** after the user supplies a new fragment, it should\n  match the **target** page’s **generated** heading ID (Hugo slugification; see\n  **CLAUDE.md** / **AGENTS.md**). The agent still **validates** (see Phase 3) and\n  must **not** “pick” a different id from the page to replace a bad answer—**No\n  guessing**.\n- Same-page: `[Text](#section-id)`.\n- Cross-page: when user-provided, `#fragment` must still be checked against the\n  **target** file. Validate fragments in shortcodes the same way as in body\n  Markdown.\n\n### External URLs (**AGENTS.md**)\n\n- Do not commit **guessed** replacement URLs. If a URL cannot be verified,\n  treat as blocked or drop the fragment per AGENTS guidance. See also **No\n  guessing** above; internal and external link targets are treated the same for\n  inference: **none** without user input or a verified source.\n\n### Special cases (**AGENTS.md**)\n\n- **Engine API version** pages: respect coordinated **`/latest/` `aliases`**\n  rules—never leave two version files both owning `/latest/`.\n- **Vendored / generated** trees: read-only; see CLAUDE.md. Do not “fix” links\n  there if policy forbids.\n\n---\n\n## Phase 0 — Discovery (read-only; may use whole repo)\n\n1. Read **hugo.yaml** (permalinks, `refLinksErrorLevel`, `disablePathToLower`).\n2. From the branch (diff, renames), build a **mapping table**:\n   - old source path → new source path  \n   - old published URL → new published URL (from permalink rules)\n3. **Case:** with `disablePathToLower: true`, filesystem path **case** appears in\n   URLs—**directory and link casing must match** (e.g. `setup` vs `Setup`).\n4. When planning **inbound link** fixes, treat old-path references as two\n   categories: **no fragment** vs **with `#fragment`**. That split feeds\n   **List 1** and **List 2** in Phase 0.5 and drives Phase 2 ordering (see\n   there).\n\n---\n\n## Phase 0.5 — PR-scoped evaluation (required before edits in PR mode)\n\n1. **Set `PR_SCOPE_FILES` (Git scope for PR mode)**  \n   - When the PR **targets `main`**, use the same triple-dot form as\n     **review-changes**:  \n     `git diff --name-only main...HEAD`  \n   - For a **different target branch** or a custom base, use the merge base:  \n     `BASE=$(git merge-base <target-branch> HEAD)`  \n     then:  \n     `git diff --name-only \"$BASE\"...HEAD`  \n   - Those paths define **what moved** in the branch; they are the primary input\n     to the **old → new** path/URL table. They are **not** a hard cap on *where\n     to search* for **inbound** links (see **Modes**): sweeps for links **to** old\n     paths usually cover all of `content/` (and other trees per Conventions).  \n   - If project policy **limits edits** to the diff for a given PR, follow that\n     and **defer** link fixes in files outside the diff; note the exception in\n     the task if the user relaxes that policy.\n\n2. Build checklists (see **Modes** for sweep vs area-of-work):\n   - path/URL mapping this run must honor (old source path → new; old published\n     → new, from the **PR’s** moves in PR-scoped mode, or the **declared** full\n     migration in full-site mode)\n   - **List 1 — old path, no fragment:** every **inbound** reference, found on\n     the **sweep** surface, to a moved **old** path that does **not** include a\n     `#...` fragment (e.g. `…/banana.md` in the repo’s link style for that\n     file).\n   - **List 2 — old path with fragment:** every **inbound** reference, found on\n     the same sweep, to a moved **old** path that **includes** a `#...` fragment\n     (e.g. `…/banana.md#anchor` or the published-style equivalent in context). The\n     **same** old path string may appear on **both** List 1 and List 2 for\n     different links; duplication across the two lists is OK.\n   - **Matching rules:** when recording List 1 / List 2, use **one** consistent\n     path representation for comparison (e.g. relative `../path/banana.md` vs\n     root-anchored) **per the conventions in this doc** and the **surrounding\n     file’s** established pattern. Agents compare and skip List 2 links in the\n     List 1 pass using the **same** representation rules.\n3. **Out of scope** for the lists: only include references whose **old** target\n   is in this run’s **mapping**. Do not build List 1/2 for unrelated **mango/**\n   (or other) problems unless those links also target an **old** path that this\n   migration renames. Defer those issues separately (see **Modes**).\n\n---\n\n## Phase 1 — `aliases` (old published URLs)\n\n1. On each **new** canonical page, add or merge **`aliases`** for every **real**\n   former public URL.\n2. Do not strip existing unrelated aliases.\n3. **PR-scoped:** add aliases only where the canonical file is in scope or the\n   project requires it; otherwise list missing alias targets for follow-up.\n\n---\n\n## Phase 2 — In-repo link reference updates\n\n1. **List 1 first (path only):** update references that belong to **List 1**\n   (old path, **no** fragment). Replace old source paths or old published URLs\n   with the **new** targets; preserve each file’s link pattern (relative vs\n   root-anchored `.md` paths). **Do not** apply the same bulk path replacement to\n   links that appear in **List 2** (old path **with** `#...`) during this\n   sub-step—**leave** every **List 2** link **unchanged** for now.\n2. **After List 1 is complete:** **re-scan** the **same** **sweep** surface as\n   in Phase 0.5 (e.g. all of `content/` plus config) or **print** a clear list of\n   all **remaining** **List 2** entries. Those links should still point at the\n   **old** path and **old** fragment until Phase 3.  \n3. **Full-site (extra sweep):** after steps 1–2, still use **AGENTS “Page\n   deletion checklist”**-style thoroughness for **config / front matter**\n   `link:` and similar so nav and grids are not left on old slugs. Apply the\n   **List 1 / List 2** rules there too: path-only old references first; defer\n   fragment-bearing rewrites in line with **List 2** until Phase 3.\n4. **PR-scoped (which files to change):** apply List 1 and later Phase 3 updates\n   to **every** file the **sweep** finds with a **migration-relevant** reference\n   (inbound to an **old** path in the mapping), including files **not** in\n   `PR_SCOPE_FILES`, per **Modes**. **Log** and **defer** (do not “fix”)\n   unrelated stragglers. If policy forbids out-of-PR file edits, defer per step 1\n   of Phase 0.5.  \n5. Include **shortcodes and layout partials** (see Conventions and **Modes** for\n   sweep vs focus).\n\n---\n\n## Phase 3 — List 2: interactive path and fragment resolution\n\n**Prerequisites:** Phase 2 has updated **List 1**; **List 2** still lists **old\npath + `#...`** (unchanged) for this migration. See **Modes** for which files\nmay be edited; **No guessing** applies.\n\n1. **Print List 2** to the user: every remaining **old path** + `#anchor` (in the\n   agreed representation), so nothing is hidden before the loop.\n2. **For each distinct** `old-path#oldAnchor` (or process in the order the user\n   prefers, one at a time):  \n   - Ask: **What is the new path (and fragment, if any) for this content?** The\n     user may give a new source path, published URL, and/or `#newAnchor` per\n     project conventions.  \n   - **Validate** the user’s answer: open the **target** page (or resolve the\n     target) and check that `#newAnchor` (if any) **exists** as a real heading\n     / generated id on that page, per **CLAUDE.md** / **AGENTS.md** (same rules\n     as the rest of the site). **Do not** replace the user’s fragment with a\n     “better” one from the file.  \n   - If validation **fails** (unknown target file, or `#newAnchor` not found on\n     the page): **warn** clearly (what failed: path vs missing fragment), then\n     **ask again** for a corrected path and/or fragment. **Repeat** until\n     validation passes or the user **defers** / **drops** the fragment (per\n     **AGENTS.md**). **Never** guess a new fragment to fix the problem.  \n   - When validation **passes:** update **all** in-repo references that match\n     that **same** `old-path#oldAnchor` to the user-approved `new-path#newAnchor`\n     (respect each file’s link style; include shortcodes/layouts on the same\n     **sweep** surface as Phase 2).  \n3. **Repeat** from step 1: **re-print** or **re-scan** for **List 2** until it is\n   **empty** or the user defers the remainder.  \n4. **PR-scoped / full-site:** the **loop** is the same. **Edits** follow **Modes**:\n   migration-relevant **inbound** links may live in any file on the sweep; do\n   not expand into **unrelated** link debt from other areas. Defer as in **Modes**\n   and Phase 0.5.\n\n---\n\n## Optional: scripts helper\n\nThis skill includes a small **scope helper** so agents do not re-derive Git\nrecipes. See [scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) — it prints\npaths in PR scope for a given target branch (default `main`).\n\n---\n\n## Verification\n\n```bash\ndocker buildx bake validate\n```\n\nUse the **Definition of done** in **Agent: required procedure (do not skip)**\nas the final bar: **validate** must pass, and the **sweep** must be clean for\n**plain** and **`#fragment`** old-path references, **or** the remainder must be\n**explicitly deferred** in **Phase 3** per **AGENTS.md** / the user. Mid-run,\n**Phase 2** may still leave **List 2** links unchanged **until** Phase 3; that\nintermediate state is **not** the finished migration.\n","frontmatter":{"name":"migrate-content-ia","description":"Handle Hugo docs information-architecture moves: discover old vs new URLs, add front matter aliases (Phase 1), update in-repo links (Phase 2), interactive List 2 resolution and fragment validation (Phase 3; no guessing). Supports PR-scoped mapping plus whole-content sweeps for inbound links to that mapping, or a full-site follow-up. Triggers on: \"IA migration\", \"redirects for moved pages\", \"fix links after content move\", \"PR-scoped link/anchor pass\", \"aliases for old URLs\". After branch work, chain the review-changes skill (main...HEAD) before a PR. Agents must run the in-file required procedure and definition of done, not the phases alone in isolation.\n"},"isInternal":false,"tokens":5530,"sizeBytes":20656},{"name":"SKILL.md","path":".agents/skills/research/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/research/SKILL.md","title":"research","category":"anthropic-skill","format":"markdown","content":"---\nname: research\ndescription: >\n  Research a documentation topic — locate affected files, understand the\n  problem, identify what to change. Use when investigating an issue, a\n  question, or a topic before writing a fix. Triggers on: \"research issue\n  1234\", \"investigate what needs changing for #500\", \"what files are\n  affected by #200\", \"where is X documented\", \"is our docs page about Y\n  accurate\", \"look into how we document Z\".\n---\n\n# Research\n\nThoroughly investigate the topic at hand and produce a clear plan for\nthe fix. The goal is to identify exact files, named targets within those\nfiles, and the verified content needed for the fix.\n\n## 1. Gather context\n\nIf the input is a GitHub issue number, fetch it:\n\n```bash\ngh issue view <number> --repo docker/docs \\\n  --json number,title,body,labels,comments\n```\n\nOtherwise, work from what was provided — a description, a URL, a question,\nor prior conversation context. Identify the topic, affected feature, or\npage to investigate.\n\n## 2. Locate affected files\n\nSearch `content/` using the URL or topic from the issue. Remember the\n`/manuals` prefix mapping when converting URLs to file paths.\n\nFor each candidate file, read the relevant section to confirm it contains\nthe reported problem.\n\n## 3. Check vendored ownership\n\nBefore planning any edit, verify the file is editable locally:\n\n- `_vendor/` — read-only, vendored via Hugo modules\n- `data/cli/` — read-only, generated from upstream YAML\n- `content/reference/cli/` — read-only, generated from `data/cli/`\n- Everything else in `content/` — editable\n\nIf the fix requires upstream changes, identify the upstream repo and note\nit as out of scope. See the vendored content table in CLAUDE.md.\n\n## 4. Find related content\n\nLook for pages that may need updating alongside the primary fix:\n\n- Pages that link to the affected content\n- Include files (`content/includes/`) referenced by the page\n- Related pages in the same section describing the same feature\n\n## 5. Verify facts\n\nIf the issue makes a factual claim about how a feature behaves, verify it.\nFollow external links, read upstream source, check release notes. Do not\nplan a fix based on an unverified claim.\n\nIf the fix requires a replacement URL and that URL cannot be verified (e.g.\nnetwork restrictions), report it as a blocker rather than guessing.\n\n## 6. Check the live site (if needed)\n\nFor URL or rendering issues, fetch the live page:\n\n```\nhttps://docs.docker.com/<path>/\n```\n\n## 7. Report findings\n\nSummarize what you found — files to change, the specific problem in each,\nwhat the fix should be, and any constraints. This context feeds directly\ninto the write step.\n\nBe specific: name the file, the section or element within it, and the\nverified content needed. \"Fix the broken link in networking.md\" is not\nspecific enough. \"In `compose/networking.md`, the 'Custom networks' section,\nremove the note about `driver_opts` being ignored — this was fixed in\nCompose 2.24\" is.\n\n## Notes\n\n- Research quality bounds write quality. Vague research produces broad\n  changes; precise research produces minimal ones.\n- Do not create standalone research files — findings stay in conversation\n  context for the write step.\n","frontmatter":{"name":"research","description":"Research a documentation topic — locate affected files, understand the problem, identify what to change. Use when investigating an issue, a question, or a topic before writing a fix. Triggers on: \"research issue 1234\", \"investigate what needs changing for #500\", \"what files are affected by #200\", \"where is X documented\", \"is our docs page about Y accurate\", \"look into how we document Z\".\n"},"isInternal":false,"tokens":734,"sizeBytes":3206},{"name":"SKILL.md","path":".agents/skills/review-changes/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/review-changes/SKILL.md","title":"review-changes","category":"anthropic-skill","format":"markdown","content":"---\nname: review-changes\ndescription: >\n  Review uncommitted or recently committed documentation changes for\n  correctness, coherence, and style compliance. Use before creating a PR\n  to catch issues. \"review my changes\", \"review the diff\", \"check the fix\n  before submitting\", \"does this look right\".\ncontext: fork\nmodel: opus\n---\n\n# Review Changes\n\nEvaluate whether the changes correctly and completely solve the stated\nproblem, without introducing new issues. Start with no assumptions — the\nchange may contain mistakes. Your job is to catch what the writer missed,\nnot to rubber-stamp the diff.\n\n## 1. Identify what changed\n\nDetermine the scope of changes to review:\n\n```bash\n# Uncommitted changes\ngit diff --name-only\n\n# Last commit\ngit diff --name-only HEAD~1\n\n# Entire branch vs main\ngit diff --name-only main...HEAD\n```\n\nPick the right comparison for what's being reviewed. If reviewing a branch,\nuse `main...HEAD` to see all changes since the branch diverged.\n\n## 2. Read each changed file in full\n\nDo not just read the diff. For every changed file, read the entire file to\nunderstand the full context the change lives in. A diff can look correct in\nisolation but contradict something earlier on the same page.\n\nThen read the diff for the detailed changes:\n\n```bash\n# Adjust the comparison to match step 1\ngit diff --unified=10              # uncommitted\ngit diff --unified=10 HEAD~1       # last commit\ngit diff --unified=10 main...HEAD  # branch\n```\n\n## 3. Follow cross-references\n\nFor each changed file, check what links to it and what it links to:\n\n- Search for other pages that reference the changed content (grep for the\n  filename, heading anchors, or key phrases)\n- Read linked pages to verify the change doesn't create contradictions\n  across pages\n- Check that anchor links in cross-references still match heading IDs\n\nA change that's correct on its own page can break the story told by a\nrelated page.\n\n## 4. Verify factual accuracy\n\nDon't assume the change is factually correct just because it reads well.\n\n- If the change describes how a feature behaves, verify against upstream\n  docs or source code\n- If the change includes a URL, check that it resolves\n- If the change references a CLI flag, option, or API field, confirm it\n  exists\n\n## 5. Evaluate as a reader\n\nConsider someone landing on this page from a search result, with no prior\ncontext:\n\n- Does the page make sense on its own?\n- Is the changed section clear without having read the issue or diff?\n- Would a reader be confused by anything the change introduces or leaves\n  out?\n\n## 6. Review code and template changes\n\nFor non-Markdown changes (JS, HTML, CSS, Hugo templates):\n\n- Trace through the common execution path\n- Trace through at least one edge case (no stored preference, Alpine fails\n  to load, first visit vs returning visitor)\n- Ask whether the change could produce unexpected browser or runtime\n  behavior that no automated tool would catch\n\n## 7. Decision\n\n**Approve** if the change is correct, coherent, complete, and factually\naccurate.\n\n**Request changes** if:\n- The change does not correctly solve the stated problem\n- There is a factual error or contradiction (on-page or cross-page)\n- A cross-reference is broken or misleading\n- A reader would be confused\n\nWhen requesting changes, be specific: quote the exact text that is wrong,\nexplain why, and suggest the correct fix.\n","frontmatter":{"name":"review-changes","description":"Review uncommitted or recently committed documentation changes for correctness, coherence, and style compliance. Use before creating a PR to catch issues. \"review my changes\", \"review the diff\", \"check the fix before submitting\", \"does this look right\".\n","context":"fork","model":"opus"},"isInternal":false,"tokens":767,"sizeBytes":3379},{"name":"openai.yaml","path":".agents/skills/review-pr/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/review-pr/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Review PR\"\n  short_description: \"Validate incoming documentation pull requests\"\n  default_prompt: \"Use $review-pr to validate this incoming documentation PR and draft maintainer feedback.\"\n","isInternal":false,"tokens":42,"sizeBytes":217},{"name":"SKILL.md","path":".agents/skills/review-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/review-pr/SKILL.md","title":"review-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: review-pr\ndescription: >\n  Review one or more incoming Docker documentation pull requests as a\n  maintainer. Independently validate technical claims, assess editorial fit\n  and information architecture, choose a verdict, and draft exact inline or\n  PR-wide feedback behind a confirmation gate. Use for requests such as\n  \"review PR 123\", \"is this PR correct?\", \"does this information belong\n  here?\", \"validate this PR\", or \"help review backlog PRs\". Do not use to\n  maintain or fix a PR you own; use maintain-pr for that.\n---\n\n# Review PR\n\nReview incoming contributions for factual correctness and whether they make\nthe documentation better as a whole. Treat a technically true addition as\ninsufficient when it is misplaced, overemphasized, redundant, or unhelpful\nto the page's intended reader.\n\n## Preserve the write boundary\n\nPerform the review in two phases:\n\n1. Research the PR, decide a verdict, and present the exact proposed\n   comment or review text.\n2. Wait for explicit user confirmation, then post only the confirmed text.\n\nBefore confirmation, do not post comments, submit a GitHub review, approve or\nrequest changes, resolve threads, push commits, edit labels, or otherwise\nmutate GitHub. A request to review or draft feedback is not confirmation to\npost it. Ask `Post these comments?` and stop. Treat revisions to a draft as\nunconfirmed until the user explicitly asks to post them.\n\n## 1. Gather the full context\n\nFor each PR, inspect its metadata, body, commits, changed files, checks,\nconversation, reviews, and linked issues. Always fetch inline comments\nseparately because `gh pr view --json reviews` omits them.\n\n```bash\ngh pr view <PR> --repo docker/docs \\\n  --json number,title,url,state,author,body,baseRefName,headRefName,headRefOid,commits,files,comments,reviews,reviewDecision,statusCheckRollup\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, side, commit_id}]'\ngh pr diff <PR> --repo docker/docs\n```\n\nRead linked issues and relevant discussion. An issue is evidence that a\nreader was confused, but it does not establish the reporter's diagnosis or\njustify a new highlighted note by itself. Green CI establishes only that automated\nchecks passed, not that the content is correct.\n\nFetch the PR head when local inspection is useful. Compare it with the\ncanonical upstream base rather than assuming the local branch is fresh.\nRead each changed file in full, not only its diff.\n\n## 2. Research independently\n\nVerify every material claim against authoritative sources such as product\nsource code, upstream documentation, specifications, release notes, or safe\nlocal reproduction. Do not accept the PR description, issue diagnosis, or\nexisting review feedback as fact.\n\nSearch the documentation for related explanations and canonical pages. Read\n`STYLE.md`, `COMPONENTS.md`, and applicable repository instructions. Check\nwhether a changed file is generated or maintained upstream and identify the correct\nupstream repository instead of proposing a local edit.\n\nDistinguish among:\n\n- a wrong fact\n- a correct fact expressed inaccurately\n- a correct fact placed on the wrong page\n- content already explained elsewhere\n- a real discovery problem better addressed with a short signpost and link\n- a request that needs no documentation change.\n\nIf an external claim or replacement URL cannot be verified, report that\nlimitation instead of guessing.\n\n## 3. Assess editorial fit\n\nApply these questions to each addition:\n\n- Does it change a reader's decision or next action on this page?\n- Is this the canonical page for the concept?\n- Is the fact general, or specific to this page, feature, or component?\n- Is the information already documented elsewhere?\n- Would a concise local signpost to canonical coverage solve the discovery\n  problem better than duplicating the explanation?\n- Is the visual and textual weight proportional to the information's value?\n- Does it preserve the page's scope, flow, and character?\n\nPrefer one coherent explanation in the canonical location. Add local context\nonly when it helps the reader complete the task at hand. Avoid stray notes,\ncallouts, and exhaustive edge cases whose prominence exceeds their value.\n\n## 4. Choose a decisive verdict\n\nLead with one of these outcomes:\n\n- **Approve**: correct, useful, well placed, and ready to merge.\n- **Approve with optional polish**: ready to merge; suggestions are genuinely\n  non-blocking.\n- **Focused rewrite**: the underlying need is valid, but wording, scope,\n  placement, or structure should change before merge.\n- **Close / no docs change**: incorrect, redundant, out of scope, or not a\n  documentation problem.\n\nExplain the verdict with evidence. When wording is the issue, provide exact\nreplacement text rather than a vague request to improve it.\n\n## 5. Place feedback deliberately\n\nUse an inline comment when the finding is anchored to a narrow changed line\nor range and acting on it is local. Examples include an inaccurate sentence,\nan ambiguous option description, a broken link, or a precise wording\nreplacement.\n\nUse a PR-wide comment for scope, information architecture, overall approach,\nmultiple intertwined edits, or a proposed replacement section. Do not attach\nholistic feedback to an arbitrary line.\n\nUse both when appropriate: put the overall direction in the PR-wide comment\nand line-specific corrections inline. Do not repeat the same point in both.\nConsolidate related feedback so the author receives the fewest comments that\nremain clear and actionable.\n\nFor every proposed inline comment, resolve and display the current changed\nfile path and right-side diff line. If the target line is not part of the\ncurrent diff or cannot be identified reliably, use a PR-wide comment that\nquotes the target text instead. Never guess a line number.\n\nEnd comments posted on the user's behalf with an accurate agent-disclosure\nfooter, such as `Generated by Codex`.\n\n## 6. Present drafts and stop\n\nBefore any GitHub write, show the review in this form, omitting empty\nsections:\n\n```markdown\n## Verdict\n\nFocused rewrite\n\n## Findings\n\n- <finding and evidence>\n\n## Proposed inline comments\n\n1. `path/to/file.md:42`\n   > Exact comment text\n\n## Proposed PR-wide comment\n\n> Exact comment text\n\nPost these comments?\n```\n\nFor multiple PRs, give each PR its own verdict and comment set. Make the\nconfirmation scope unambiguous. Do not interpret approval of one PR's drafts\nas approval to post comments on the others.\n\n## 7. Post only confirmed feedback\n\nImmediately before posting, re-fetch the PR head SHA and diff. If either the\nhead or an inline target changed, stop and show the updated draft or\nplacement for confirmation.\n\nPost confirmed inline comments as a single comment-only review when\npractical. Use the current head SHA and right-side diff lines:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/reviews --method POST --input <payload>\n```\n\nThe JSON payload contains `commit_id`, `event: \"COMMENT\"`, and a `comments`\narray whose entries contain `path`, `line`, `side: \"RIGHT\"`, and `body`.\nSubmitting a review with `APPROVE` or `REQUEST_CHANGES` requires separate,\nexplicit user authorization; a verdict alone does not grant it.\n\nPost confirmed holistic feedback separately:\n\n```bash\ngh pr comment <PR> --repo docker/docs --body-file <file>\n```\n\nUse a safely created temporary file or API input so Markdown, backticks, and\nshell substitutions are preserved literally. Post exactly the confirmed\ntext. Verify the resulting review/comments and report their URLs and\nplacements. If GitHub rejects an inline location, do not silently fall back\nto a PR-wide comment; report the failure and prepare a revised placement for\nconfirmation.\n\n## Definition of done\n\n- Verify technical claims with authoritative evidence.\n- Evaluate usefulness, placement, duplication, and proportionality.\n- Give a decisive verdict and exact actionable wording.\n- Choose inline and PR-wide placement based on the feedback's scope.\n- Show every exact draft and target before any GitHub mutation.\n- Post only after explicit confirmation and verify what was posted.\n","frontmatter":{"name":"review-pr","description":"Review one or more incoming Docker documentation pull requests as a maintainer. Independently validate technical claims, assess editorial fit and information architecture, choose a verdict, and draft exact inline or PR-wide feedback behind a confirmation gate. Use for requests such as \"review PR 123\", \"is this PR correct?\", \"does this information belong here?\", \"validate this PR\", or \"help review backlog PRs\". Do not use to maintain or fix a PR you own; use maintain-pr for that.\n"},"isInternal":false,"tokens":1722,"sizeBytes":8109},{"name":"SKILL.md","path":".agents/skills/testcontainers-guides-migrator/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/testcontainers-guides-migrator/SKILL.md","title":"testcontainers-guide-migrator","category":"anthropic-skill","format":"markdown","content":"---\nname: testcontainers-guide-migrator\ndescription: >\n  Migrate a Testcontainers guide from testcontainers.com into the Docker docs site (docs.docker.com).\n  Converts AsciiDoc to Hugo Markdown, updates code to the latest Testcontainers API, splits into\n  chapters with stepper navigation, verifies code compiles and tests pass, and validates against\n  Docker docs style rules. Use when asked to migrate a testcontainers guide, add a TC guide, or\n  port content from testcontainers.com to Docker docs.\n---\n\n# Migrate a Testcontainers Guide\n\nYou are migrating guides from https://testcontainers.com/guides/ into the Docker docs Hugo site.\nEach guide lives in its own GitHub repo under `testcontainers/tc-guide-*`, written in AsciiDoc.\nThe source repos are listed in the testcontainers-site build.sh:\nhttps://github.com/testcontainers/testcontainers-site/blob/main/build.sh#L23-L45\n\n## Inputs\n\nThe user provides one or more guides to migrate. Resolve these from the inventory below:\n\n- **REPO_NAME**: GitHub repo (e.g. `tc-guide-getting-started-with-testcontainers-for-java`)\n- **SLUG**: guide slug inside `guide/` dir (e.g. `getting-started-with-testcontainers-for-java`)\n- **LANG**: language identifier (go, java, dotnet, nodejs, python)\n- **GUIDE_ID**: short kebab-case name (e.g. `getting-started`)\n\n## Guide inventory\n\nThese are the 21 guides from testcontainers.com/guides/ and their source repos:\n\n| # | Title | Repo | Lang | GUIDE_ID |\n|---|-------|------|------|----------|\n| 1 | Introduction to Testcontainers | tc-guide-introducing-testcontainers | (none) | introducing |\n| 2 | Getting started for Java | tc-guide-getting-started-with-testcontainers-for-java | java | getting-started |\n| 3 | Testing Spring Boot REST API | tc-guide-testing-spring-boot-rest-api | java | spring-boot-rest-api |\n| 4 | Testcontainers lifecycle (JUnit 5) | tc-guide-testcontainers-lifecycle | java | lifecycle |\n| 5 | Configuration of services in container | tc-guide-configuration-of-services-running-in-container | java | service-configuration |\n| 6 | Replace H2 with real database | tc-guide-replace-h2-with-real-database-for-testing | java | replace-h2 |\n| 7 | Testing ASP.NET Core web app | tc-guide-testing-aspnet-core | dotnet | aspnet-core |\n| 8 | Testing Spring Boot Kafka Listener | tc-guide-testing-spring-boot-kafka-listener | java | spring-boot-kafka |\n| 9 | REST API integrations with MockServer | tc-guide-testing-rest-api-integrations-using-mockserver | java | mockserver |\n| 10 | Getting started for .NET | tc-guide-getting-started-with-testcontainers-for-dotnet | dotnet | getting-started |\n| 11 | AWS integrations with LocalStack | tc-guide-testing-aws-service-integrations-using-localstack | java | aws-localstack |\n| 12 | Testcontainers in Quarkus apps | tc-guide-testcontainers-in-quarkus-applications | java | quarkus |\n| 13 | Getting started for Go | tc-guide-getting-started-with-testcontainers-for-go | go | getting-started |\n| 14 | jOOQ and Flyway with Testcontainers | tc-guide-working-with-jooq-flyway-using-testcontainers | java | jooq-flyway |\n| 15 | Getting started for Node.js | tc-guide-getting-started-with-testcontainers-for-nodejs | nodejs | getting-started |\n| 16 | REST API integrations with WireMock | tc-guide-testing-rest-api-integrations-using-wiremock | java | wiremock |\n| 17 | Local dev with Testcontainers Desktop | tc-guide-simple-local-development-with-testcontainers-desktop | java | local-dev-desktop |\n| 18 | Micronaut REST API with WireMock | tc-guide-testing-rest-api-integrations-in-micronaut-apps-using-wiremock | java | micronaut-wiremock |\n| 19 | Micronaut Kafka Listener | tc-guide-testing-micronaut-kafka-listener | java | micronaut-kafka |\n| 20 | Getting started for Python | tc-guide-getting-started-with-testcontainers-for-python | python | getting-started |\n| 21 | Keycloak with Spring Boot | tc-guide-securing-spring-boot-microservice-using-keycloak-and-testcontainers | java | keycloak-spring-boot |\n\nAlready migrated: **#2 (Java getting-started)**, **#13 (Go getting-started)**, **#20 (Python getting-started)**\n\n## Step 0: Pre-flight\n\n1. Confirm `testing-with-docker` tag exists in `data/tags.yaml`. If not, add:\n   ```yaml\n   testing-with-docker:\n     title: Testing with Docker\n   ```\n2. Check if new terms need adding to `_vale/config/vocabularies/Docker/accept.txt`.\n3. Read `STYLE.md` and `COMPONENTS.md` to refresh on Docker docs conventions.\n\n## Step 1: Clone the guide repo\n\nClone the guide repo to a temporary directory. This gives you all source files locally — no HTTP calls needed.\n\n```bash\ngit clone --depth 1 https://github.com/testcontainers/{REPO_NAME}.git <tmpdir>/{REPO_NAME}\n```\n\nWhere `<tmpdir>` is a temporary directory on your system (e.g. the output of `mktemp -d`).\n\nThe repo structure is:\n- `<tmpdir>/{REPO_NAME}/guide/{SLUG}/index.adoc` — the AsciiDoc guide source\n- `<tmpdir>/{REPO_NAME}/src/` — application source code (referenced by `include::` directives)\n- `<tmpdir>/{REPO_NAME}/testdata/` — test data files (SQL scripts, configs, etc.)\n- `<tmpdir>/{REPO_NAME}/pom.xml` or `go.mod` — build config\n\n1. Read `guide/{SLUG}/index.adoc` to get the guide content.\n2. Find all `include::{codebase}/path/to/file[]` directives. The `{codebase}` attribute points to a remote URL, but since you have the repo cloned, read the files directly from disk instead (e.g. `include::{codebase}/src/main/java/Foo.java[]` → read `<tmpdir>/{REPO_NAME}/src/main/java/Foo.java`).\n3. If includes have `[lines=\"X..Y\"]`, extract only those lines from the local file.\n4. Note the `[source,lang]` block preceding each include — that determines the code fence language.\n\nThis cloned repo also serves as the base for Step 6 (code verification) — you can run the tests directly in it to confirm they pass before updating the code to the latest API.\n\n## Step 2: Convert AsciiDoc to Markdown\n\n| AsciiDoc | Markdown |\n|---|---|\n| `== Heading` | `## Heading` |\n| `=== Heading` | `### Heading` |\n| `*bold*` (AsciiDoc bold) | `**bold**` |\n| `https://url[Link text]` | `[Link text](url)` |\n| `[source,lang]\\n----\\ncode\\n----` | `` ```lang\\ncode\\n``` `` |\n| `[source,shell]` with `$` prompts | `` ```console `` |\n| `[NOTE]\\ntext` or `====\\n[NOTE]\\n...\\n====` | `> [!NOTE]\\n> text` |\n| `[TIP]\\ntext` | `> [!TIP]\\n> text` |\n| `:toc:`, `:toclevels:`, `:codebase:` | Remove entirely |\n| `include::{codebase}/path[]` | Replace with fetched code in a code fence |\n| YAML front matter (date, draft, repo) | Remove; transform to Docker docs format |\n\n## Step 3: Apply Docker docs style rules\n\nThese are mandatory (from STYLE.md and AGENTS.md):\n\n- **No \"we\"**: \"We are going to create\" → \"Create\" or \"Start by creating\"\n- **No \"let us\" / \"let's\"**: → imperative voice or \"You can...\"\n- **No hedge words**: remove \"simply\", \"easily\", \"just\", \"seamlessly\"\n- **No meta-commentary**: remove \"it's worth noting\", \"it's important to understand\"\n- **No \"allows you to\" / \"enables you to\"**: → \"lets you\" or rephrase\n- **No \"click\"**: → \"select\"\n- **No bold for emphasis or product names**: only bold UI elements\n- **No time-relative language**: remove \"currently\", \"new\", \"recently\", \"now\"\n- **No exclamations**: remove \"Voila!!!\" etc.\n- Use `console` language hint for interactive shell blocks with `$` prompts\n- Use contractions: \"it's\", \"you're\", \"don't\"\n\n## Step 4: Update code to latest Testcontainers API\n\nResearch the latest API version for the target language before writing code.\n\n**Best practices reference**: The Testcontainers team maintains Claude skills with up-to-date API patterns and best practices for each language at https://github.com/testcontainers/claude-skills/ — check the relevant language skill (testcontainers-go, testcontainers-node, testcontainers-dotnet) for current API signatures, cleanup patterns, wait strategies, and anti-patterns to avoid.\n\nFor each language, check the cloned repo's existing code, then update to the latest API. Key patterns per language:\n\n**Go** (testcontainers-go v0.41.0):\n- `postgres.RunContainer(ctx, opts...)` → `postgres.Run(ctx, \"image\", opts...)`\n- `testcontainers.WithImage(...)` → image is now the 2nd positional param to `Run()`\n- Manual `WithWaitStrategy(wait.ForLog(...))` → `postgres.BasicWaitStrategies()`\n- `t.Cleanup(func() { ctr.Terminate(ctx) })` → `testcontainers.CleanupContainer(t, ctr)`\n- `if err != nil { log.Fatal(err) }` → `require.NoError(t, err)` (use testify require/assert)\n- Helper functions should accept `t *testing.T` as first param, call `t.Helper()`\n- No `TearDownSuite()` needed if `CleanupContainer` is registered in the helper\n- Go version prerequisite: 1.25+\n\n**Java** (testcontainers-java 2.0.4):\n- Artifacts renamed in 2.x: `org.testcontainers:postgresql` → `org.testcontainers:testcontainers-postgresql`\n- Check the latest version at https://java.testcontainers.org/\n- Use `@Testcontainers` and `@Container` annotations for JUnit 5 lifecycle\n- Prefer module-specific containers (e.g. `PostgreSQLContainer`) over `GenericContainer`\n- Use `@DynamicPropertySource` for Spring Boot integration\n\n**.NET** (testcontainers-dotnet):\n- Check the latest NuGet package version\n- Use `IAsyncLifetime` for container lifecycle in xUnit\n- Use builder pattern: `new PostgreSqlBuilder().Build()`\n\n**Node.js** (testcontainers-node):\n- Check the latest npm version\n- Use module-specific packages (e.g. `@testcontainers/postgresql`)\n- Use `GenericContainer` for services without a dedicated module\n\n**Python** (testcontainers-python):\n- Check the latest PyPI version\n- Use context managers (`with PostgresContainer() as postgres:`)\n- Use module-specific containers when available\n\nFor all languages: consult the corresponding Testcontainers skill at https://github.com/testcontainers/claude-skills/ for current best practices and anti-patterns.\n\n## Step 5: Create guide directory structure\n\nDirectory: `content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n\nEach guide is its own top-level entry under `/guides/`. Do NOT nest guides inside a shared parent section — otherwise they won't appear individually in the tag/language filters on the guides listing page.\n\n### _index.md (landing page)\n\n```yaml\n---\ntitle: {Full guide title}\nlinkTitle: {Short title for guides listing}\ndescription: {One-line description}\nkeywords: testcontainers, {lang}, testing, {technologies used}\nsummary: |\n  {2-3 line summary for the guides listing card}\ntoc_min: 1\ntoc_max: 2\ntags: [testing-with-docker]\nlanguages: [{lang}]\nparams:\n  time: {estimated} minutes\n---\n\n<!-- Source: https://github.com/testcontainers/{REPO_NAME} -->\n```\n\nContent: what you'll learn (bulleted list), prerequisites, and a NOTE linking to `https://testcontainers.com/getting-started/` for newcomers.\n\n### Sub-pages (chapters)\n\nSplit the guide into logical chapters. Each sub-page:\n\n```yaml\n---\ntitle: {Chapter title}\nlinkTitle: {Short title for stepper}\ndescription: {One-line description}\nweight: {10, 20, 30, ...}\n---\n```\n\n**No `tags`, `languages`, or `params` on sub-pages** — only on `_index.md`.\n\nTypical chapter breakdown:\n| Weight | File | Content |\n|--------|------|---------|\n| 10 | `create-project.md` | Project setup, dependencies, business logic |\n| 20 | `write-tests.md` | First test using testcontainers |\n| 30 | `test-suites.md` | Reusing containers, test helpers, suites |\n| 40 | `run-tests.md` | Running tests, summary, further reading |\n\nAdapt the split to the guide's content — some guides may need fewer or more chapters.\n\n## Step 6: Verify code compiles and tests pass\n\nThis is CRITICAL. The code in the guide MUST compile and all tests MUST pass. Do not skip this step.\n\n### 6a: Use the cloned repo as the verification project\n\nThe repo you cloned in Step 1 (`<tmpdir>/{REPO_NAME}`) already contains a working project with all source files, build config, and tests. Use it as the starting point:\n\n```bash\ncd <tmpdir>/{REPO_NAME}\n```\n\nFirst, verify the **original** code compiles and tests pass before you change anything. This confirms a good baseline.\n\n### 6b: Update the code in the cloned repo\n\nAfter confirming the original works, apply the API updates (from Step 4) directly in the cloned repo's source files. This is the same code you're putting in the guide — keep them in sync.\n\n### 6c: Update dependencies and compile\n\nRun compilation inside a container for reproducibility — no need to install the language toolchain on the host. Use the appropriate language Docker image, mounting the cloned repo:\n\n```bash\ndocker run --rm -v \"<tmpdir>/{REPO_NAME}\":/app -w /app <language-image> sh -c \"<compile command>\"\n```\n\nPick the right image for the language (e.g. `golang:1.25-alpine`, `maven:3-eclipse-temurin-21`, `gradle:jdk21`, `mcr.microsoft.com/dotnet/sdk:9.0`, `node:22-alpine`, `python:3.13-alpine`). Update dependencies to the latest Testcontainers version and compile.\n\nIf compilation fails, fix the code and update the guide markdown to match.\n\n### 6d: Run tests in a container with Docker socket mounted\n\nRun tests in the same kind of container, but **mount the Docker socket** so Testcontainers can create sibling containers.\n\n#### macOS Docker Desktop workarounds\n\nWhen running on macOS with Docker Desktop, these environment variables and flags are **required**:\n\n- **`TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`** — On macOS, containers can't reach sibling containers via the Docker bridge IP (`172.17.0.x`). This tells Testcontainers (including Ryuk) to connect via `host.docker.internal` instead. **Do NOT disable Ryuk** — it is a core Testcontainers feature and the guides must demonstrate proper usage.\n- **`docker-java.properties`** with `api.version=1.47` — Docker Desktop's minimum API version is 1.44, but docker-java defaults to 1.24. Create this file in the project root and mount it to `/root/.docker-java.properties` inside Java containers.\n- **`-Dspotless.check.skip=true`** — The Spotless Maven plugin in the source repos is incompatible with JDK 21. Skip it since it's a code formatter, not part of the test.\n- **`-Dmicronaut.test.resources.enabled=false`** — Micronaut's Test Resources service starts a separate process that can't connect to Docker from inside a container. The guide tests use Testcontainers directly, not Test Resources. Only needed for Micronaut guides.\n#### Java guide test command\n\n```bash\n# Create docker-java.properties in the project root\necho \"api.version=1.47\" > <tmpdir>/{REPO_NAME}/docker-java.properties\n\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v \"<tmpdir>/{REPO_NAME}/docker-java.properties\":/root/.docker-java.properties \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  maven:3.9-eclipse-temurin-21 \\\n  mvn -B test -Dspotless.check.skip=true -Dspotless.apply.skip=true\n```\n\nFor Quarkus guides, use `maven:3.9-eclipse-temurin-17` instead (Quarkus 3.22.3 compiles for Java 17).\n\n#### Go guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  golang:1.25-alpine \\\n  sh -c \"apk add --no-cache gcc musl-dev && go test -v -count=1 ./...\"\n```\n\n#### Python guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  python:3.13-slim \\\n  sh -c \"pip install -r requirements.txt && python -m pytest\"\n```\n\n#### .NET guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  mcr.microsoft.com/dotnet/sdk:9.0 \\\n  dotnet test\n```\n\n#### Node.js guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  node:22-alpine \\\n  sh -c \"npm install && npm test\"\n```\n\n#### Important: run tests sequentially\n\nRun guide tests **one at a time**. Running multiple concurrent DinD or sibling-container tests can overwhelm Docker Desktop's containerd store and cause `meta.db: input/output error` corruption, requiring a Docker Desktop restart.\n\n### 6e: Fix until green\n\nIf any test fails, debug and fix the code in both the temporary project AND the guide markdown. Re-run until all tests pass. Do not proceed until verified.\n\n## Step 7: Update cross-references\n\n1. **`content/manuals/testcontainers.md`**: Add a bullet under the `## Guides` section:\n   ```markdown\n   - [Guide title](/guides/testcontainers-{LANG}-{GUIDE_ID}/)\n   ```\n2. **Do NOT update** `content/guides/testcontainers-cloud/_index.md` — keep its external links.\n3. Link to `https://testcontainers.com/getting-started/` for the Testcontainers overview.\n4. Use internal paths for already-migrated guides; keep `testcontainers.com` links for unmigrated ones.\n\n## Step 8: Validate\n\n**IMPORTANT**: Run ALL validation locally before committing. Vale checks run on CI and will block the PR if they fail — fixing after push wastes CI cycles and review time.\n\n1. `npx --no-install rumdl fmt content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n2. `npx --no-install rumdl fmt content/manuals/testcontainers.md`\n3. `docker buildx bake lint` — must pass with no errors\n4. `docker buildx bake vale` — then check for errors in the new files:\n   ```bash\n   grep -A2 \"testcontainers-{LANG}-{GUIDE_ID}\" tmp/vale.out\n   ```\n   Fix ALL errors before proceeding. Common issues:\n   - **Vale.Spelling**: tech terms (library names, tools) not in the dictionary → add to `_vale/config/vocabularies/Docker/accept.txt` (alphabetical order)\n   - **Vale.Terms**: wrong casing (e.g. \"python\" → \"Python\") → fix in the markdown. Watch for package names like `testcontainers-python` triggering false positives — rephrase to \"Testcontainers for Python\" in prose.\n   - **Docker.Avoid**: hedge words like \"very\", \"simply\" → reword\n   - **Docker.We**: first-person plural → rewrite to \"you\" or imperative\n   - Info-level suggestions (e.g. \"VS Code\" → \"versus\") are not blocking but review them\n\n   Re-run `docker buildx bake vale` after fixes until no errors remain in the new files.\n5. Verify in local dev server (`HUGO_PORT=1314 docker compose watch`):\n   - Guide appears when filtering by its language\n   - Guide appears when filtering by `Testing with Docker` tag\n   - Stepper navigation works across chapters\n   - All links resolve (no 404s)\n6. Verify all external URLs return 200:\n   ```bash\n   curl -s -o /dev/null -w \"%{http_code}\" -L \"{url}\"\n   ```\n\n## Step 9: Commit\n\nOne commit per guide. Message format:\n```\nfeat(guides): add testcontainers {lang} {guide-id} guide\n\nMigrated from https://github.com/testcontainers/{REPO_NAME}\nUpdated to testcontainers-{lang} v{version} API.\n```\n\n## Special cases\n\n- **introducing-testcontainers**: Language-agnostic, conceptual. May overlap with `content/manuals/testcontainers.md`. Review for deduplication before migrating.\n- **local-dev-testcontainers-desktop**: About Testcontainers Desktop (now part of Docker Desktop). May need significant rewriting rather than mechanical migration.\n- **Java guides**: Many share the same language. Each still gets its own `testcontainers-java-{GUIDE_ID}` directory.\n\n## Reference: completed migration (Go getting-started)\n\nUse `content/guides/testcontainers-go-getting-started/` as the reference implementation:\n- `_index.md` — landing page with frontmatter, prerequisites, learning objectives\n- `create-project.md` (weight: 10) — project setup and business logic\n- `write-tests.md` (weight: 20) — first test with testcontainers-go\n- `test-suites.md` (weight: 30) — container reuse with testify suites\n- `run-tests.md` (weight: 40) — running tests, summary, further reading\n","frontmatter":{"name":"testcontainers-guide-migrator","description":"Migrate a Testcontainers guide from testcontainers.com into the Docker docs site (docs.docker.com). Converts AsciiDoc to Hugo Markdown, updates code to the latest Testcontainers API, splits into chapters with stepper navigation, verifies code compiles and tests pass, and validates against Docker docs style rules. Use when asked to migrate a testcontainers guide, add a TC guide, or port content from testcontainers.com to Docker docs.\n"},"isInternal":false,"tokens":5180,"sizeBytes":20104},{"name":"SKILL.md","path":".agents/skills/triage-issue/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/triage-issue/SKILL.md","title":"triage-issue","category":"anthropic-skill","format":"markdown","content":"---\nname: triage-issue\ndescription: >\n  Analyze a single GitHub issue for docker/docs — check whether the problem\n  still exists, determine a verdict, and report findings. Use when asked to\n  triage, assess, or review an issue, even if the user doesn't say \"triage\"\n  explicitly: \"triage issue 1234\", \"is issue 500 still valid\", \"should we\n  close #200\", \"look at this issue\", \"what's going on with #200\".\nargument-hint: \"<issue-number>\"\ncontext: fork\n---\n\n# Triage Issue\n\nGiven GitHub issue **$ARGUMENTS** from docker/docs, figure out whether\nit's still a real problem and say what should happen next.\n\n## 1. Fetch the issue\n\n```bash\ngh issue view $ARGUMENTS --repo docker/docs \\\n  --json number,title,body,state,labels,createdAt,updatedAt,closedAt,assignees,author,comments\n```\n\n## 2. Understand the problem\n\nRead the issue body and all comments. Identify:\n\n- What is the reported problem?\n- What content, URL, or file does it reference?\n- Has anyone already proposed a fix or workaround in the comments?\n\nCheck for linked PRs in the issue timeline, not only in the issue body or\ncomments:\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/timeline --paginate \\\n  --jq '.[] | select(.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") | {event, created_at, source: .source.issue.html_url, title: .source.issue.title, state: .source.issue.state}'\n```\n\nIf an open PR already addresses the issue, don't open another PR. Review the\nexisting PR instead, and report that the issue already has an associated PR. A\nmerged PR is strong evidence the issue is fixed. A closed-without-merge PR means\nthe issue is likely still open.\n\n## 3. Follow URLs\n\nFind all `docs.docker.com` URLs in the issue body and comments. For each:\n\n- Fetch the URL to check if it still exists (404 = content removed or moved)\n- Check whether the content still contains the problem described\n- Note when the page was last updated relative to when the issue was filed\n\nFor non-docs URLs (GitHub links, external references), fetch them too if\nthey are central to understanding the issue.\n\n## 4. Check the repository\n\nIf the issue references specific files, content sections, or code:\n\n- Find and read the current version of that content\n- Check whether the problem has been fixed, content moved, or file removed\n- Remember the `/manuals` prefix mapping when looking up files\n\n## 5. Check for upstream ownership\n\nIf the issue is about content in `_vendor/` or `data/cli/`, it cannot be\nfixed here. Identify which upstream repo owns it (see the vendored content\ntable in CLAUDE.md).\n\n## 6. Decide and act\n\nAfter investigating, pick one of these verdicts and take the corresponding\naction on the issue:\n\n- **Close it** — the problem is already fixed, the content no longer exists,\n  or the issue is too outdated to be useful. Close the issue with a comment\n  explaining why:\n\n  ```bash\n  gh issue close $ARGUMENTS --repo docker/docs \\\n    --comment \"Closing: <one-sentence reason>\"\n  ```\n\n- **Fix it** — the problem is real and fixable in this repo. Name the\n  file(s) and what needs to change. Label the issue `status/confirmed` and\n  remove `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/confirmed'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Escalate upstream** — the problem is real but lives in vendored content.\n  Name the upstream repo. Label the issue `status/upstream` and remove\n  `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/upstream'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Leave it open** — you can't determine the current state, or the issue\n  needs human judgment. Label the issue `status/needs-analysis`:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/needs-analysis'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\nDon't overthink the classification. An old issue isn't stale if the problem\nstill exists. An upstream issue is still valid — it's just not fixable here.\n\nAlso apply the most relevant `area/` label based on the content affected.\nAvailable area labels: `area/accounts`, `area/admin`, `area/ai`,\n`area/api`, `area/billing`, `area/build`, `area/build-cloud`, `area/cli`,\n`area/compose`, `area/compose-spec`, `area/config`, `area/contrib`,\n`area/copilot`, `area/desktop`, `area/dhi`, `area/engine`,\n`area/enterprise`, `area/extensions`, `area/get-started`, `area/guides`,\n`area/hub`, `area/install`, `area/networking`, `area/offload`,\n`area/release-notes`, `area/samples`, `area/scout`, `area/security`,\n`area/storage`, `area/subscription`, `area/swarm`, `area/ux`. Pick one\n(or at most two if the issue clearly spans areas). Skip if none fit.\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n  --method POST --field 'labels[]=area/<name>'\n```\n\n## 7. Report\n\nWrite a short summary: what the issue reports, what you found, and what\nshould happen next. Reference the specific files, URLs, or PRs that support\nyour conclusion. Skip metadata fields — the issue itself has the dates and\nlabels. Mention the action you took (closed, labeled, etc.).\n\n## Notes\n\n- Always check timeline cross-references before deciding to fix an issue\n- Do not narrate your process — produce the final report\n- End every issue comment with an accurate agent-disclosure footer that names\n  the active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n.\n","frontmatter":{"name":"triage-issue","description":"Analyze a single GitHub issue for docker/docs — check whether the problem still exists, determine a verdict, and report findings. Use when asked to triage, assess, or review an issue, even if the user doesn't say \"triage\" explicitly: \"triage issue 1234\", \"is issue 500 still valid\", \"should we close #200\", \"look at this issue\", \"what's going on with #200\".\n","argument-hint":"<issue-number>","context":"fork"},"isInternal":false,"tokens":1428,"sizeBytes":5729},{"name":"SKILL.md","path":".agents/skills/write/SKILL.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/.agents/skills/write/SKILL.md","title":"write","category":"anthropic-skill","format":"markdown","content":"---\nname: write\ndescription: >\n  Write a documentation fix on a branch. Makes the minimal change, formats,\n  self-reviews, and commits. Use after research has identified what to change.\n  \"write the fix\", \"make the changes\", \"implement the fix for #1234\".\nhooks:\n  PostToolUse:\n    - matcher: \"Edit|Write\"\n      hooks:\n        - type: command\n          command: \"bash ${CLAUDE_SKILL_DIR}/scripts/post-edit.sh\"\n---\n\n# Write\n\nMake the minimal change that resolves the issue. Research has already\nidentified what to change — this skill handles the edit, formatting,\nself-review, and commit.\n\n## 1. Create a branch\n\n```bash\ngit checkout -b fix/issue-<number>-<short-desc> main\n```\n\nUse a short kebab-case description derived from the issue title (3-5 words).\n\n## 2. Read then edit\n\nAlways read each file before modifying it. Make the minimal change that\nfixes the issue. Do not improve surrounding content, add comments, or\naddress adjacent problems.\n\nFollow the writing guidelines in CLAUDE.md, STYLE.md, and COMPONENTS.md.\n\n## 3. Front matter check\n\nEvery content page requires `title`, `description`, and `keywords` in its\nfront matter. If any are missing from a file you touch, add them.\n\n## 4. Validate\n\nrumdl runs automatically after each edit via the PostToolUse hook.\nRun lint manually after all edits are complete:\n\n```bash\nscripts/lint.sh <changed-files>\n```\n\nThe lint script runs rumdl and Vale on only the files you pass it,\nso the output is scoped to your changes. Fix any errors it reports.\n\n## 5. Self-review\n\nRe-read each changed file: right file, right lines, change is complete,\nfront matter is present. Run `git diff` and verify only intended changes\nare present.\n\n## 6. Commit\n\nStage only the changed files:\n\n```bash\ngit add <files>\ngit diff --cached --name-only  # verify — no package-lock.json or other noise\ngit commit -m \"$(cat <<'EOF'\ndocs: <short description under 72 chars> (fixes #NNNN)\n\n<What was wrong: one sentence citing the specific problem.>\n<What was changed: one sentence describing the exact edit.>\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\"\n```\n\nThe commit body is mandatory. A reviewer reading only the commit should\nunderstand the problem and the fix without opening the issue.\n\n## Notes\n\n- Never edit `_vendor/` or `data/cli/` — these are vendored\n- If a file doesn't exist, check for renames:\n  `git log --all --full-history -- \"**/filename.md\"`\n- If the fix requires a URL that cannot be verified, stop and report a\n  blocker rather than guessing\n","frontmatter":{"name":"write","description":"Write a documentation fix on a branch. Makes the minimal change, formats, self-reviews, and commits. Use after research has identified what to change. \"write the fix\", \"make the changes\", \"implement the fix for #1234\".\n","hooks":{"PostToolUse":[{"matcher":"Edit|Write","hooks":[{"type":"command","command":"bash ${CLAUDE_SKILL_DIR}/scripts/post-edit.sh"}]}]}},"isInternal":false,"tokens":616,"sizeBytes":2504},{"name":"_index.md","path":"content/manuals/dhi/tools/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/_index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Tools\ndescription: Interfaces and tools for browsing, managing, and automating Docker Hardened Images.\nweight: 25\nparams:\n  grid_tools:\n    - title: Use Docker Hub\n      description: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\n      icon: squares-2x2\n      link: /dhi/tools/hub/\n    - title: CLI\n      description: Install and use the `docker dhi` command-line interface to browse the catalog, inspect images, and manage mirrors from your terminal.\n      icon: command-line\n      link: /dhi/tools/cli/\n    - title: MCP server\n      description: Connect an AI assistant to the DHI catalog to search repositories, inspect images, retrieve SBOMs, and check CVEs using plain language.\n      icon: cpu-chip\n      link: /dhi/tools/mcp/\n    - title: Use the DHI Terraform provider\n      description: Use the DHI Terraform provider to manage mirrors and automate DHI configuration as infrastructure as code.\n      icon: wrench-screwdriver\n      link: /dhi/tools/terraform/\n    - title: Use the DHI API\n      description: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\n      icon: code-bracket\n      link: /dhi/tools/api/\n---\n\nDocker Hardened Images can be accessed and managed through several interfaces.\nChoose the tool that fits your workflow.\n\n{{< grid items=\"grid_tools\" >}}\n","frontmatter":{"title":"Tools","description":"Interfaces and tools for browsing, managing, and automating Docker Hardened Images.","weight":25,"params":{"grid_tools":[{"title":"Use Docker Hub","description":"Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.","icon":"squares-2x2","link":"/dhi/tools/hub/"},{"title":"CLI","description":"Install and use the `docker dhi` command-line interface to browse the catalog, inspect images, and manage mirrors from your terminal.","icon":"command-line","link":"/dhi/tools/cli/"},{"title":"MCP server","description":"Connect an AI assistant to the DHI catalog to search repositories, inspect images, retrieve SBOMs, and check CVEs using plain language.","icon":"cpu-chip","link":"/dhi/tools/mcp/"},{"title":"Use the DHI Terraform provider","description":"Use the DHI Terraform provider to manage mirrors and automate DHI configuration as infrastructure as code.","icon":"wrench-screwdriver","link":"/dhi/tools/terraform/"},{"title":"Use the DHI API","description":"Query Docker Hardened Images data programmatically using the DHI GraphQL API.","icon":"code-bracket","link":"/dhi/tools/api/"}]}},"isInternal":false,"tokens":330,"sizeBytes":1394},{"name":"api.md","path":"content/manuals/dhi/tools/api.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/api.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use the DHI API\nlinktitle: API\ndescription: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\nweight: 50\nkeywords: dhi api, docker hardened images api, graphql api, dhi endpoint, dhi authentication\n---\n\nThe DHI API is a GraphQL API for querying Docker Hardened Images data\nprogrammatically, for use cases like building automation or dashboards on\ntop of DHI data.\n\n## Endpoint\n\nSend requests as `POST` requests to:\n\n```text\nhttps://api.dso.docker.com/v1/graphql\n```\n\n## Request format\n\nThe API accepts standard GraphQL requests: a JSON body with a `query` and,\noptionally, `variables`.\n\n```console\n$ curl https://api.dso.docker.com/v1/graphql \\\n  -H \"Authorization: Bearer <token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"...\", \"variables\": { ... }}'\n```\n\nEvery query takes a `Context` argument (conventionally named `ctx` in the\n`variables` object) alongside its query-specific arguments:\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `ctx` | `Context` | Yes | Scopes the request to an organization. |\n| `ctx.organization` | `String` | Yes | The Docker organization the token belongs to. |\n\n## Authentication\n\nAn [organization access token](/manuals/enterprise/security/access-tokens.md)\n(OAT) or personal access token (PAT) isn't used directly as the bearer\ntoken. Exchange it first for an access token:\n\n```console\n$ curl -X POST https://hub.docker.com/v2/auth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"identifier\": \"<identifier>\", \"secret\": \"<token>\"}'\n```\n\nFor `identifier`, use your Docker Hub username with a PAT, or the\norganization name with an OAT. The response contains the access token:\n\n```json\n{ \"access_token\": \"...\" }\n```\n\nPass that `access_token` as `Authorization: Bearer <access_token>`. Also set\n`ctx.organization` in `variables` to the organization the token belongs to\n(see [Request format](#request-format)).\n\n## Response format\n\nResponses follow the standard GraphQL envelope:\n\n| Key | Description |\n|---|---|\n| `data` | The requested fields. A field is `null` if it couldn't be resolved, for example due to an authorization failure. |\n| `errors` | Present when a field failed to resolve. Includes a `message` and a `path` identifying which field failed. |\n| `extensions` | Metadata such as a `correlation_id`, useful when reporting an issue. |\n\nFor example, an unauthenticated request, or a request for data your token\ncan't access, returns a `null` result under `data` alongside an authorization\nerror in `errors`, rather than an HTTP-level failure:\n\n```json\n{\n  \"errors\": [\n    {\n      \"message\": \"You are not allowed to read data for this team\",\n      \"path\": [\"someQuery\"],\n      \"extensions\": { \"code\": \"DOWNSTREAM_SERVICE_ERROR\", \"status\": 403 }\n    }\n  ],\n  \"data\": { \"someQuery\": null },\n  \"extensions\": { \"correlation_id\": \"...\" }\n}\n```\n\n## Queries\n\n### `imagePackagesForImageCoords`\n\nFetches every package in an image, every CVE reported against it, and\nwhether Docker suppresses that CVE, by digest. See [Query VEX for a Docker\nHardened Image](/manuals/dhi/how-to/vex-api.md) for a guided example.\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `digest` | `String` | Yes | The image's platform manifest digest, not the multi-arch index digest. |\n| `hostName` | `String` | Yes | `hub.docker.com` or `docker.io`. |\n| `repoName` | `String` | Yes | Repository name, with or without the namespace prefix. |\n| `includeExcepted` | `Boolean` | No | Include suppressed CVEs in the response alongside the reason for suppression. Without it, the response only shows the netted list, with no visibility into what was suppressed. |\n| `includeNodsa` | `Boolean` | No | Include Debian NODSA exclusions, which make up most suppressions on a Debian-based image. |\n| `includePublic` | `Boolean` | No | Also include public images when `ctx.organization` scopes the request to an organization. Not needed for a typical lookup. |\n\nKeep the requested response fields limited to what you plan to render.\nFields such as `locations`, `description`, `vulnerableRange`, and `epss`\nincrease response size substantially and aren't needed for a CVE-count or\nsuppressed-CVE view.\n\n#### Response fields\n\n`vulnerabilityExceptions` only contains records that actually suppress a\nCVE, so it always lines up with `isExcepted`: an empty array means the CVE\nis live. Use `isExcepted` as your filter for \"is this CVE suppressed.\"\n\n| Field | Meaning |\n|---|---|\n| `isExcepted` | Docker suppresses this CVE for this image. Use this to filter. |\n| `sourceType` | `EXTERNAL` (Debian NODSA), `MANUAL_EXCEPTION` (Docker analyst exception), or `VEX_STATEMENT` (an ingested VEX document). |\n| `type` | `FALSE_POSITIVE` and `ACCEPTED_RISK` suppress the CVE. `UNDER_INVESTIGATION` and `AFFECTED` don't. |\n| `justification` | The OpenVEX justification value. Always `null` for NODSA exclusions. |\n| `additionalDetails` | Free-text rationale for the suppression. |\n| `isDhiStatement` | Whether the statement is inherited from the DHI base image. |\n| `id` | Stable identifier for the statement. |\n\n#### Mapping to OpenVEX\n\nIf your pipeline consumes OpenVEX documents (for example, Trivy's `--vex`\nflag), each suppressed record maps as follows:\n\n| OpenVEX field | Source |\n|---|---|\n| `vulnerability.name` | `sourceId` |\n| `products[].@id` | The parent package's `purl` |\n| `status` | `not_affected` (from `type: FALSE_POSITIVE`) |\n| `justification` | `justification`, defaulting to `vulnerable_code_cannot_be_controlled_by_adversary` for NODSA exclusions |\n| `status_notes` | `additionalDetails` |\n| `@id` | `id` |\n","frontmatter":{"title":"Use the DHI API","linktitle":"API","description":"Query Docker Hardened Images data programmatically using the DHI GraphQL API.","weight":50,"keywords":"dhi api, docker hardened images api, graphql api, dhi endpoint, dhi authentication"},"isInternal":false,"tokens":1439,"sizeBytes":5610},{"name":"cli.md","path":"content/manuals/dhi/tools/cli.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/cli.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use the DHI CLI\nlinkTitle: CLI\nweight: 20\nkeywords: docker dhi, CLI, command line, docker hardened images\ndescription: Learn how to install and use docker dhi, the command-line interface for managing Docker Hardened Images.\naliases:\n  - /dhi/how-to/cli/\n---\n\nThe `docker dhi` command-line interface (CLI) is a tool for managing Docker Hardened Images:\n- Browse the catalog of available DHI images and their metadata\n- View attestations for DHI images, including SBOMs and provenance\n- Mirror DHI images to your Docker Hub organization\n- Create and manage customizations of DHI images\n- Generate authentication for enterprise package repositories\n- Monitor customization builds\n\n## Installation\n\nThe `docker dhi` CLI is available in [Docker Desktop](https://docs.docker.com/desktop/) version 4.65 and later.\nYou can also install the standalone `dhictl` binary.\n\n### Docker Desktop\n\nThe `docker dhi` command is included in Docker Desktop 4.65 and later. No additional installation is required.\n\n### Standalone binary\n\n1. Download the `dhictl` binary for your platform from the\n   [releases](https://github.com/docker-hardened-images/dhictl/releases) page.\n2. Move it to a directory in your `PATH`:\n    - `mv dhictl /usr/local/bin/` on _Linux_ and _macOS_\n    - Move `dhictl.exe` to a directory in your `PATH` on _Windows_\n\n## Usage\n\nEvery command has built-in help accessible with the `--help` flag:\n\n```console\n$ docker dhi --help\n$ docker dhi catalog list --help\n```\n\n### Browse the DHI catalog\n\nList all available DHI images:\n\n```console\n$ docker dhi catalog list\n```\n\nFilter by type, name, or compliance:\n\n```console\n$ docker dhi catalog list --type image\n$ docker dhi catalog list --filter golang\n$ docker dhi catalog list --fips\n$ docker dhi catalog list --stig\n```\n\nGet details of a specific image, including available tags and CVE counts:\n\n```console\n$ docker dhi catalog get <image-name>\n```\n\n### View attestations\n\nList all attestations attached to a DHI image:\n\n```console\n$ docker dhi attestation list dhi/nginx:1.27\n$ docker dhi attestation list dhi/nginx:1.27 --platform linux/amd64\n$ docker dhi attestation list dhi/nginx:1.27 --predicate-type https://slsa.dev/provenance/v1\n$ docker dhi attestation list dhi/nginx:1.27 --json\n```\n\nGet a specific attestation by its referrer digest:\n\n```console\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest>\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest> -o provenance.json\n```\n\nDisplay the SPDX SBOM for an image:\n\n```console\n$ docker dhi attestation sbom dhi/nginx:1.27\n$ docker dhi attestation sbom dhi/nginx:1.27 --platform linux/amd64\n```\n\n### Mirror DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nStart mirroring one or more DHI images to your Docker Hub organization:\n\n```console\n$ docker dhi mirror start --org my-org \\\n  dhi/golang,my-org/dhi-golang \\\n  dhi/nginx,my-org/dhi-nginx \\\n  dhi/prometheus-chart,my-org/dhi-prometheus-chart\n```\n\nMirror with dependencies:\n\n```console\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --dependencies\n```\n\nList mirrored images in your organization:\n\n```console\n$ docker dhi mirror list --org my-org\n```\n\nFilter mirrored images by name or type:\n\n```console\n$ docker dhi mirror list --org my-org --filter python\n$ docker dhi mirror list --org my-org --type image\n$ docker dhi mirror list --org my-org --type helm-chart\n```\n\nStop mirroring one or more images:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org\n$ docker dhi mirror stop dhi-python dhi-golang --org my-org\n```\n\nStop mirroring and delete the repositories:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org --delete\n$ docker dhi mirror stop dhi-golang --org my-org --delete --force\n```\n\n### Customize DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nThe CLI can be used to create and manage DHI image customizations. For detailed\ninstructions on creating customizations using the GUI, see [Customize a Docker\nHardened Image](../how-to/customize.md).\n\nThe following is a quick reference for CLI commands. For complete details on all\noptions and flags, see the\n[CLI reference](/reference/cli/docker/dhi/).\n\n```console\n# Prepare a single customization scaffold\n$ docker dhi customization prepare golang 1.25 \\\n  --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\" \\\n  > my-customization.yaml\n\n# Prepare a bulk customization scaffold (pipe JSON array via stdin)\n$ echo '[{\"destination\":\"my-org/dhi-golang\",\"tag-definition-id\":\"golang/alpine-3.23/1.24-dev\"}]' \\\n  | docker dhi customization prepare --name \"golang with git\" --org my-org \\\n  > my-customization.yaml\n\n# Create a customization\n$ docker dhi customization create my-customization.yaml --org my-org\n\n# Create with flag overrides (flags take precedence over the YAML file)\n$ docker dhi customization create my-customization.yaml --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\"\n\n# List customizations\n$ docker dhi customization list --org my-org\n\n# Filter customizations by name, repository, or source\n$ docker dhi customization list --org my-org --filter git\n$ docker dhi customization list --org my-org --repo dhi-golang\n$ docker dhi customization list --org my-org --source golang\n\n# Get a customization by ID\n$ docker dhi customization get <id> --org my-org\n\n# Update a customization\n# The YAML file must include the 'id' field to identify the customization to update\n$ docker dhi customization edit my-customization.yaml --org my-org\n\n# Delete a customization by ID\n$ docker dhi customization delete <id> --org my-org\n\n# Delete multiple customizations\n$ docker dhi customization delete <id1> <id2> --org my-org\n\n# Delete without confirmation prompt\n$ docker dhi customization delete <id> --org my-org --force\n```\n\nFor a complete reference of all YAML fields, see\n[Image customization YAML file](/dhi/how-to/customize/#image-customization-yaml-file).\n\n### Enterprise package authentication\n\n{{< summary-bar feature_name=\"Docker Hardened Images Enterprise\" >}}\n\nGenerate authentication credentials for accessing the enterprise hardened\npackage repository. These credentials are used when configuring your package\nmanager to install compliance and security-patched packages in your own images. For detailed\ninstructions, see [Enterprise\nrepository](../how-to/hardened-packages.md#enterprise-repository).\n\nFor Alpine-based images:\n\n```console\n$ docker dhi auth apk\n```\n\nFor Debian-based images:\n\n```console\n$ docker dhi auth deb\n```\n\n### Monitor customization builds\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nList builds for a customization:\n\n```console\n$ docker dhi customization build list <customization-id> --org my-org\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\nGet details of a specific build:\n\n```console\n$ docker dhi customization build get <customization-id> <build-id> --org my-org\n$ docker dhi customization build get <customization-id> <build-id> --org my-org --json\n```\n\nView build logs:\n\n```console\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org --json\n```\n\n### JSON output\n\nMost list and get commands support a `--json` flag for machine-readable output:\n\n```console\n$ docker dhi catalog list --json\n$ docker dhi catalog get golang --json\n$ docker dhi attestation list dhi/nginx:1.27 --json\n$ docker dhi mirror list --org my-org --json\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --json\n$ docker dhi customization list --org my-org --json\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\n## Configuration\n\nThe `docker dhi` CLI can be configured with a YAML file located at:\n- `$HOME/.config/dhictl/config.yaml` on _Linux_ and _macOS_\n- `%USERPROFILE%\\.config\\dhictl\\config.yaml` on _Windows_\n\nIf `$XDG_CONFIG_HOME` is set, the configuration file is located at `$XDG_CONFIG_HOME/dhictl/config.yaml`.\n\nAvailable configuration options:\n\n| Option      | Environment Variable | Description                                                                                                               |\n|-------------|----------------------|---------------------------------------------------------------------------------------------------------------------------|\n| `org`       | `DHI_ORG`            | Default Docker Hub organization for mirror and customization commands.                                                    |\n| `api_token` | `DHI_API_TOKEN`      | Docker token for authentication. You can generate a token in your [Docker Hub account settings](https://hub.docker.com/). |\n\nEnvironment variables take precedence over configuration file values.\n","frontmatter":{"title":"Use the DHI CLI","linkTitle":"CLI","weight":20,"keywords":"docker dhi, CLI, command line, docker hardened images","description":"Learn how to install and use docker dhi, the command-line interface for managing Docker Hardened Images.","aliases":["/dhi/how-to/cli/"]},"isInternal":false,"tokens":2135,"sizeBytes":8790},{"name":"hub.md","path":"content/manuals/dhi/tools/hub.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/hub.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use Docker Hub\nlinktitle: Docker Hub\ndescription: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\nweight: 10\nkeywords: docker hub dhi catalog, hardened images hub, dhi repository details, image variants hub\n---\n\nThe [Docker Hardened Images catalog](https://hub.docker.com/hardened-images/catalog)\non Docker Hub is the primary web interface for browsing, searching, and inspecting\nDHI repositories and their metadata.\n\n## Catalog page\n\nThe catalog lists all available DHI repositories. You can filter by name,\nimage type, or compliance requirements (FIPS, STIG) to find the image you need.\n\n## Repository details page\n\nWhen you select a repository from the catalog, the repository details page\nprovides the following:\n\n- Overview: A brief explanation of the image.\n- Guides: Several guides on how to use the image and migrate your existing application.\n- Images: Select this option to [view image variants](#images-page).\n- Security summary: Select a tag name to view a quick security summary,\n  including package count and total known vulnerabilities.\n- Recently pushed tags: A list of recently updated image variants and when they\n  were last updated.\n- Use this image: After selecting an image variant, you can select this option to\n  view instructions on how to pull and use the image variant, or select **Mirror\n  repository** to mirror it to your organization.\n\n## Images page\n\nFrom the repository details page, select **Images** to see all available image\nvariants for that repository. The table includes:\n\n- Image version: The image name with its base distribution (for example, `debian\n  13`) and associated tags.\n- Type: The support lifecycle status of the variant.\n- Compliance: Relevant compliance designations, for example `CIS`, `FIPS`, or\n  `STIG (100%)`.\n- Package manager: Whether a package manager is available. A checkmark indicates\n  a package manager is present (for example, `apt` or `apk`), a dash indicates\n  none.\n- Shell: Whether a shell is available. A checkmark indicates a shell is present\n  (for example, `bash` or `busybox`), a dash indicates none.\n- User: The user that the container runs as, for example `root` or `nonroot\n  (65532)`.\n- Last pushed: When the image variant was last updated.\n- Vulnerabilities: Vulnerability counts by severity level.\n\n## Image variant details page\n\nSelect an image version from the Images table to view detailed information about\nthat specific variant:\n\n- Packages: A list of all packages included in the image variant, with each\n  package's name, version, distribution, and licensing information.\n- Specifications:\n  - Source and build information: The Dockerfile and Git commit used to build the image.\n  - Build parameters, entrypoint, CMD, user, working directory, environment\n    variables, labels, and platform.\n- Vulnerabilities: A list of known CVEs for the image variant, including CVE ID,\n  severity, affected package, fix version, last detected date, status, and\n  suppressed CVEs.\n- Attestations: Signed security attestations covering the image's build process,\n  contents, and security posture. For the full list, see\n  [Attestations](/dhi/explore/security-concepts/attestations/).\n\n## Manage page\n\nThe Manage page (**My Hub** > **Hardened Images** > **Manage**) is the central\nplace for administering your organization's mirrored DHI repositories. It has\ntwo tabs:\n\n- Mirrored Images: Lists all image repositories currently mirrored to your\n  organization, with their source DHI repository, destination repository name,\n  and mirroring status. From here you can stop mirroring or open a repository's\n  settings.\n- Mirrored Helm charts: The same view for Helm chart repositories.\n\nSelecting a mirrored repository opens its settings, where you can enable or\ndisable Extended Lifecycle Support (ELS) and access customizations.\n\nFor step-by-step instructions, see [Mirror a Docker Hardened Image\nrepository](/dhi/how-to/mirror/).\n\n## Customizations\n\nCustomizations are accessible from **My Hub** > **Hardened Images** > **Manage** > **Mirrored Images**.\nSelect the menu icon next to a mirrored repository and\nthen **Customize**. Each customization defines\nadditional packages, OCI artifacts, environment variables, or labels to layer\nonto the base DHI during a rebuild.\n\nThe customizations view shows each customization's name, status, and last build\ntime. Selecting a customization opens its configuration, where you can edit the\ndefinition, trigger a rebuild, or delete it.\n\nFor step-by-step instructions, see [Customize a Docker Hardened\nImage](/dhi/how-to/customize/).\n","frontmatter":{"title":"Use Docker Hub","linktitle":"Docker Hub","description":"Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.","weight":10,"keywords":"docker hub dhi catalog, hardened images hub, dhi repository details, image variants hub"},"isInternal":false,"tokens":1027,"sizeBytes":4636},{"name":"mcp.md","path":"content/manuals/dhi/tools/mcp.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/mcp.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use the DHI MCP server\nlinktitle: MCP server\ndescription: Connect an AI assistant to the Docker Hardened Images catalog using the DHI MCP server to search repositories, inspect images, view SBOMs, and check CVEs.\nweight: 30\nkeywords: docker hardened images mcp, ai assistant dhi, mcp server docker, dhi catalog ai, claude cursor docker images, sbom mcp, cve mcp\naliases:\n  - /dhi/how-to/mcp/\n---\n\nThe Docker Hardened Images (DHI) MCP server exposes the DHI catalog through the\nModel Context Protocol (MCP), letting you query repositories, inspect image\nmetadata, retrieve SBOMs, and check CVEs directly from your AI assistant in\nplain language.\n\nThe MCP server is:\n\n- Remote. No local binary to install. Your AI assistant connects directly to\n  `https://dhi.io/mcp`.\n- Compatible with any MCP-capable AI assistant, including Claude,\n  Cursor, and others.\n\nMost tools are public and require no credentials. The mirror management tools\n(`dhi_list_mirrors`, `dhi_create_mirror`, `dhi_remove_mirror`) require a Docker\nHub username and personal access token (PAT) with owner access to the target\norganization. Credentials are passed as an HTTP Basic auth header in the MCP\nclient configuration — they are never passed as tool arguments.\n\n## Connect your AI assistant\n\nConfiguration varies by client. Select the tab for your AI assistant.\n\n{{< tabs >}}\n{{< tab name=\"Claude Desktop\" >}}\n\nAdd the following to your Claude Desktop configuration file:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\nThe configuration file is located at:\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n{{< /tab >}}\n{{< tab name=\"Cursor\" >}}\n\nAdd the following to `.cursor/mcp.json` in your project, or\n`~/.cursor/mcp.json` globally:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Claude Code\" >}}\n\nRun the following command to add the DHI MCP server:\n\n```console\n$ claude mcp add dhi --url https://dhi.io/mcp\n```\n\nOr add it manually to `.claude/mcp.json` in your project:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Docker Agent\" >}}\n\nIn your [Docker Agent](/manuals/ai/docker-agent/_index.md) YAML configuration, add the\nDHI MCP server as a remote toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://dhi.io/mcp\"\n      transport_type: streamable\n```\n\nFor example, to create an agent that can answer questions about the DHI catalog:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: DHI catalog assistant\n    instruction: |\n      Help me find and evaluate Docker Hardened Images.\n      Search the DHI catalog, inspect image details, check CVEs,\n      and retrieve SBOMs and attestations as needed.\n    toolsets:\n      - type: mcp\n        remote:\n          url: \"https://dhi.io/mcp\"\n          transport_type: streamable\n```\n\nRun the agent with:\n\n```console\n$ docker agent run dhi-agent.yaml\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Available tools\n\nThe DHI MCP server provides ten tools that your AI assistant calls automatically\nbased on what you ask:\n\n| Tool | What it does |\n|------|-------------|\n| `dhi_list_repositories` | Search and filter the DHI catalog by name, type, category, FIPS, or STIG compliance |\n| `dhi_get_repository` | Get full details for a repository: tag definitions, build config, platforms, and per-manifest vulnerability counts |\n| `dhi_get_tag_definition` | Get the deep view of a single tag definition |\n| `dhi_get_image_details` | Get per-digest details: tags, platform, size, layer and package counts, vulnerability severity counts, and attestation types |\n| `dhi_get_image_packages` | Retrieve the full software bill of materials (SBOM): package name, version, type, purl, licenses, and file locations |\n| `dhi_get_image_cves` | List CVEs with severity, CVSS score, fix version, EPSS score, and CISA-exploited flag; filter by minimum severity or fixable-only |\n| `dhi_get_image_attestations` | List SBOM, provenance, signature, and other attestations for a specific image digest |\n| `dhi_list_mirrors` | List mirrored DHI repositories for a Docker Hub organization — requires authentication |\n| `dhi_create_mirror` | Start mirroring a DHI repository into a Docker Hub organization — requires authentication |\n| `dhi_remove_mirror` | Stop mirroring a repository by its mirror ID — requires authentication |\n\n## Authenticate for mirror tools\n\nThe mirror tools require a Docker Hub username and [personal access token\n(PAT)](/security/access-tokens/) with owner access to the target organization,\npassed as an HTTP Basic auth header. Generate the value with:\n\n```console\n$ printf 'USERNAME:dckr_pat_...' | base64 | tr -d '\\n'\n```\n\nThen add it to your MCP client configuration:\n\n> [!WARNING]\n> Base64 encoding is not encryption. The value in your configuration file\n> is effectively a plaintext password. Do not commit this file to version\n> control or share it.\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Basic <base64-value>\"\n      }\n    }\n  }\n}\n```\n\nWithout credentials, the read-only catalog tools work normally and the mirror\ntools return an authentication error.\n\n## What the tools return\n\nEach tool returns structured data that your AI assistant can summarize,\ncompare, or act on:\n\n- `dhi_list_repositories` returns a list of repositories with display\n  name, distributions, platforms, FIPS/STIG flags, included tools, and category.\n- `dhi_get_repository` returns the full repository record, including all tag\n  definitions with their tags, build configuration, image indexes, and\n  per-platform manifest digests with vulnerability counts.\n- `dhi_get_tag_definition` returns tags, build parameters, entrypoint,\n  environment variables, run-as user, and per-platform manifests for a single\n  tag definition.\n- `dhi_get_image_details` returns the image platform, compressed size, layer\n  count, package count, vulnerability severity counts by level, labels, and\n  a list of attestation predicate types.\n- `dhi_get_image_packages` returns each package in the image with its name,\n  version, type (`deb`, `rpm`, `apk`, etc.), purl, licenses, and the file paths where\n  it was found.\n- `dhi_get_image_cves` returns each CVE affecting the image with its\n  severity, CVSS score and vector, affected package, fix version (if any), EPSS\n  probability score, and a flag indicating whether CISA lists it as\n  actively exploited.\n- `dhi_get_image_attestations` returns the predicate type and OCI reference\n  for each attestation attached to the image digest.\n- `dhi_list_mirrors` returns each mirror's ID, source DHI repository,\n  destination repository, and mirroring status for the given organization.\n- `dhi_create_mirror` starts mirroring a DHI source repository into the\n  specified organization and destination repository name.\n- `dhi_remove_mirror` stops mirroring for the given mirror ID. It does not\n  delete the destination repository — only stops new images from being synced.\n","frontmatter":{"title":"Use the DHI MCP server","linktitle":"MCP server","description":"Connect an AI assistant to the Docker Hardened Images catalog using the DHI MCP server to search repositories, inspect images, view SBOMs, and check CVEs.","weight":30,"keywords":"docker hardened images mcp, ai assistant dhi, mcp server docker, dhi catalog ai, claude cursor docker images, sbom mcp, cve mcp","aliases":["/dhi/how-to/mcp/"]},"isInternal":false,"tokens":1788,"sizeBytes":7211},{"name":"terraform.md","path":"content/manuals/dhi/tools/terraform.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/dhi/tools/terraform.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use the DHI Terraform provider\nlinktitle: Terraform\ndescription: Use the DHI Terraform provider to manage mirrors and customizations as infrastructure as code.\nweight: 40\nkeywords: dhi terraform, docker hardened images terraform, infrastructure as code, dhi mirror terraform, dhi provider\n---\n\nThe [DHI Terraform provider](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs)\nlets you manage Docker Hardened Image mirrors and customizations as\ninfrastructure as code.\n\n## Install and configure the provider\n\nAdd the provider to your Terraform configuration:\n\n```hcl\nterraform {\n  required_providers {\n    dhi = {\n      source = \"docker-hardened-images/dhi\"\n    }\n  }\n}\n\nprovider \"dhi\" {\n  docker_hub_username = var.docker_username\n  docker_hub_password = var.docker_password\n  organization        = var.org_name\n}\n```\n\nInstead of specifying credentials in the provider block, you can set environment\nvariables:\n\n| Variable | Description |\n|----------|-------------|\n| `DOCKER_USERNAME` | Docker Hub username or organization namespace |\n| `DOCKER_PASSWORD` | Docker Hub password or personal/organization access token |\n| `DHI_ORG` | Target organization namespace |\n\nYou can authenticate using a personal access token (PAT) or an organization\naccess token (OAT) in place of a password. When using an OAT, permission scopes\napply:\n\n- Read (pull) access is required to list mirrors.\n- Push access is required to create or delete mirrors.\n\n## Resources\n\n### `dhi_mirror`\n\nManages a mirrored DHI repository in your organization. See [Mirror a Docker\nHardened Image repository](/dhi/how-to/mirror/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/mirror).\n\n### `dhi_customization`\n\nManages image customizations applied to a mirrored repository. See [Customize a\nDocker Hardened Image](/dhi/how-to/customize/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/customization).\n","frontmatter":{"title":"Use the DHI Terraform provider","linktitle":"Terraform","description":"Use the DHI Terraform provider to manage mirrors and customizations as infrastructure as code.","weight":40,"keywords":"dhi terraform, docker hardened images terraform, infrastructure as code, dhi mirror terraform, dhi provider"},"isInternal":false,"tokens":484,"sizeBytes":2208},{"name":"_index.md","path":"content/manuals/extensions/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/_index.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Docker Extensions\nweight: 60\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows\nparams:\n  sidebar:\n    group: Application development\naliases:\n - /desktop/extensions/\n---\n\nDocker Extensions let you use third-party tools within Docker Desktop to extend its functionality.\n\nYou can seamlessly connect your favorite development tools to your application development and deployment workflows. Augment Docker Desktop with debugging, testing, security, and networking functionalities, and create custom add-ons using the Extensions [SDK](extensions-sdk/_index.md).\n\nAnyone can use Docker Extensions and there is no limit to the number of extensions you can install.\n\n![Extensions Marketplace](/assets/images/extensions.webp)\n\n## What extensions are available?\n\nThere is a mix of partner and community-built extensions and Docker-built extensions.\nYou can explore the list of available extensions in [Docker Hub](https://hub.docker.com/search?q=&type=extension) or in the Extensions Marketplace within Docker Desktop.\n\n## Security and trust\n\nDocker Extensions run with elevated privileges on your host machine. They have direct access to the Docker Engine, can read and write files on your filesystem, and can install and run native binaries. \n\nDocker reviews extensions submitted to the Marketplace, but does not guarantee the security of any extension. Extensions installed outside the Marketplace have not been reviewed at all. Only install extensions from publishers you trust. \n\nIf you're an organization admin, see [Configure a private marketplace](private-marketplace.md) to control which extensions your team can install.","frontmatter":{"title":"Docker Extensions","weight":60,"description":"Extensions","keywords":"Docker Extensions, Docker Desktop, Linux, Mac, Windows","params":{"sidebar":{"group":"Application development"}},"aliases":["/desktop/extensions/"]},"isInternal":false,"tokens":311,"sizeBytes":1671},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/_index.md","title":"Extensions-sdk Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Overview of the Extensions SDK\nlinkTitle: Extensions SDK\ndescription: Overall index for Docker Extensions SDK documentation\nkeywords: Docker, Extensions, sdk\naliases:\n - /desktop/extensions-sdk/dev/overview/\n - /desktop/extensions-sdk/\ngrid:\n  - title: \"The build and publish process\"\n    description: Understand the process for building and publishing an extension.\n    icon: clipboard-document-check\n    link: \"/extensions/extensions-sdk/process/\"\n  - title: \"Quickstart guide\"\n    description: Follow the quickstart guide to create a basic Docker extension quickly.\n    icon: magnifying-glass-plus\n    link: \"/extensions/extensions-sdk/quickstart/\"\n  - title: \"View the design guidelines\"\n    description: Ensure your extension aligns to Docker's design guidelines and principles.\n    icon: paint-brush\n    link: \"/extensions/extensions-sdk/design/design-guidelines/\"\n  - title: \"Publish your extension\"\n    description: Understand how to publish your extension to the Marketplace.\n    icon: arrow-up-tray\n    link: \"/extensions/extensions-sdk/extensions/\"\n  - title: \"Interacting with Kubernetes\"\n    description: Find information on how to interact indirectly with a Kubernetes cluster from your Docker extension.\n    icon: arrows-right-left\n    link: \"/extensions/extensions-sdk/guides/kubernetes/\"\n  - title: \"Multi-arch extensions\"\n    description: Build your extension for multiple architectures.\n    icon: document-duplicate\n    link: \"/extensions/extensions-sdk/extensions/multi-arch/\"\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nThe resources in this section help you create your own Docker extension.\n\nThe Docker CLI tool provides a set of commands to help you build and publish your extension, packaged as a \nspecially formatted Docker image.\n\nAt the root of the image filesystem is a `metadata.json` file which describes the content of the extension. \nIt's a fundamental element of a Docker extension.\n\nAn extension can contain a UI part and backend parts that run either on the host or in the Desktop virtual machine.\nFor further information, see [Architecture](architecture/_index.md).\n\nYou distribute extensions through Docker Hub. However, you can develop them locally without the need to push \nthe extension to Docker Hub. See [Extensions distribution](extensions/DISTRIBUTION.md) for further details.\n\n{{% include \"extensions-form.md\" %}}\n\n{{< grid >}}\n","frontmatter":{"title":"Overview of the Extensions SDK","linkTitle":"Extensions SDK","description":"Overall index for Docker Extensions SDK documentation","keywords":"Docker, Extensions, sdk","aliases":["/desktop/extensions-sdk/dev/overview/","/desktop/extensions-sdk/"],"grid":[{"title":"The build and publish process","description":"Understand the process for building and publishing an extension.","icon":"clipboard-document-check","link":"/extensions/extensions-sdk/process/"},{"title":"Quickstart guide","description":"Follow the quickstart guide to create a basic Docker extension quickly.","icon":"magnifying-glass-plus","link":"/extensions/extensions-sdk/quickstart/"},{"title":"View the design guidelines","description":"Ensure your extension aligns to Docker's design guidelines and principles.","icon":"paint-brush","link":"/extensions/extensions-sdk/design/design-guidelines/"},{"title":"Publish your extension","description":"Understand how to publish your extension to the Marketplace.","icon":"arrow-up-tray","link":"/extensions/extensions-sdk/extensions/"},{"title":"Interacting with Kubernetes","description":"Find information on how to interact indirectly with a Kubernetes cluster from your Docker extension.","icon":"arrows-right-left","link":"/extensions/extensions-sdk/guides/kubernetes/"},{"title":"Multi-arch extensions","description":"Build your extension for multiple architectures.","icon":"document-duplicate","link":"/extensions/extensions-sdk/extensions/multi-arch/"}]},"isInternal":false,"tokens":523,"sizeBytes":2630},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/architecture/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/architecture/_index.md","title":"Architecture Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension architecture\nlinkTitle: Architecture\ndescription: Docker extension architecture\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/architecture/\nweight: 50\n---\n\nExtensions are applications that run inside the Docker Desktop. They're packaged as Docker images, distributed\nthrough Docker Hub, and installed by users either through the Marketplace within the Docker Desktop Dashboard or the\nDocker Extensions CLI.\n\nExtensions can be composed of three (optional) components:\n- A frontend (or User Interface): A web application displayed in a tab of the dashboard in Docker Desktop\n- A backend: One or many containerized services running in the Docker Desktop VM\n- Executables: Shell scripts or binaries that Docker Desktop copies on the host when installing the extension\n\n![Overview of the three components of an extension](images/extensions-architecture.png?w=600h=400)\n\nAn extension doesn't necessarily need to have all these components, but at least one of them depending on the extension features. \nTo configure and run those components, Docker Desktop uses a `metadata.json` file. See the\n[metadata](metadata) section for more details.\n\n## The frontend\n\nThe frontend is basically a web application made from HTML, Javascript, and CSS. It can be built with a simple HTML\nfile, some vanilla Javascript or any frontend framework, such as React or Vue.js.\n\nWhen Docker Desktop installs the extension, it extracts the UI folder from the extension image, as defined by the \n`ui` section in the `metadata.json`. See the [ui metadata section](metadata.md#ui-section) for more details.\n\nEvery time users click on the **Extensions** tab, Docker Desktop initializes the extension's UI as if it was the first time. When they navigate away from the tab, both the UI itself and all the sub-processes started by it (if any) are terminated.\n\nThe frontend can invoke `docker` commands, communicate with the extension backend, or invoke extension executables\ndeployed on the host, through the [Extensions SDK](https://www.npmjs.com/package/@docker/extension-api-client).\n\n> [!TIP]\n>\n> The `docker extension init` generates a React based extension. But you can still use it as a starting point for\n> your own extension and use any other frontend framework, like Vue, Angular, Svelte, etc. or event stay with\n> vanilla Javascript.\n\nLearn more about [building a frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) for your extension.\n\n## The backend\n\nAlongside a frontend application, extensions can also contain one or many backend services. In most cases, the Extension does not need a backend, and features can be implemented just by invoking docker commands through the SDK. However, there are some cases when an extension requires a backend\n\tservice, for example:\n- To run long-running processes that must outlive the frontend\n- To store data in a local database and serve them back with a REST API\n- To store the extension state, like when a button starts a long-running process, so that if you navigate away\n  from the extension and come back, the frontend can pick up where it left off\n- To access specific resources in the Docker Desktop VM, for example by mounting folders in the compose\nfile\n\n> [!TIP]\n>\n> The `docker extension init` generates a Go backend. But you can still use it as a starting point for\n> your own extension and use any other language like Node.js, Python, Java, .Net, or any other language and framework.\n\nUsually, the backend is made of one container that runs within the Docker Desktop VM. Internally, Docker Desktop creates\na Docker Compose project, creates the container from the `image` option of the `vm` section of the `metadata.json`, and\nattaches it to the Compose project. See the [`vm` metadata section](metadata.md#vm-section) for more details.\n\nIn some cases, a `compose.yaml` file can be used instead of an `image`. This is useful when the backend container\nneeds more specific options, such as mounting volumes or requesting [capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)\nthat can't be expressed just with a Docker image. The `compose.yaml` file can also be used to add multiple containers\nneeded by the extension, like a database or a message broker. \nNote that, if the Compose file defines many services, the SDK can only contact the first of them.\n\n> [!NOTE]\n>\n> In some cases, it is useful to also interact with the Docker engine from the backend.\n> See [How to use the Docker socket](../guides/use-docker-socket-from-backend.md) from the backend.\n\nTo communicate with the backend, the Extension SDK provides [functions](../dev/api/backend.md#get) to make `GET`,\n`POST`, `PUT`, `HEAD`, and `DELETE` requests from the frontend. Under the hood, the communication is done through a socket\nor named pipe, depending on the operating system. If the backend was listening to a port, it would be difficult to\nprevent collision with other applications running on the host or in a container already. Also, some users are\nrunning Docker Desktop in constrained environments where they can't open ports on their machines.\n\n![Backend and frontend communication](images/extensions-arch-2.png?w=500h=300)\n\nFinally, the backend can be built with any technology, as long as it can run in a container and listen on a socket.\n\nLearn more about [adding a backend](/manuals/extensions/extensions-sdk/build/backend-extension-tutorial.md) to your extension.\n\n## Executables\n\nIn addition to the frontend and the backend, extensions can also contain executables. Executables are binaries or shell scripts\nthat are installed on the host when the extension is installed. The frontend can invoke them with [the extension SDK](../dev/api/backend.md#invoke-an-extension-binary-on-the-host).\n\nThese executables are useful when the extension needs to interact with a third-party CLI tool, like AWS, `kubectl`, etc.\nShipping those executables with the extension ensure that the CLI tool is always available, at the right version, on\nthe users' machine.\n\nWhen Docker Desktop installs the extension, it copies the executables on the host as defined by the `host` section in\nthe `metadata.json`. See the [`host` metadata section](metadata.md#host-section) for more details.\n\n![Executable and frontend communication](images/extensions-arch-3.png?w=250h=300)\n\nHowever, since they're executed on the users' machine, they have to be available to the platform they're running on.\nFor example, if you want to ship the `kubectl` executable, you need to provide a different version for Windows, Mac,\nand Linux. Multi arch images will also need to include binaries built for the right arch (AMD / ARM)\n\n\nSee the [host metadata section](metadata.md#host-section) for more details.\n\nLearn how to [invoke host binaries](../guides/invoke-host-binaries.md).\n","frontmatter":{"title":"Extension architecture","linkTitle":"Architecture","description":"Docker extension architecture","keywords":"Docker, extensions, sdk, metadata","aliases":["/desktop/extensions-sdk/architecture/"],"weight":50},"isInternal":false,"tokens":1476,"sizeBytes":6878},{"name":"metadata.md","path":"content/manuals/extensions/extensions-sdk/architecture/metadata.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/architecture/metadata.md","title":"Architecture Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension metadata\nlinkTitle: Metadata\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases:\n - /desktop/extensions-sdk/extensions/METADATA\n - /desktop/extensions-sdk/architecture/metadata/\n---\n\n## The metadata.json file\n\nThe `metadata.json` file is the entry point for your extension. It contains the metadata for your extension, such as the\nname, version, and description. It also contains the information needed to build and run your extension. The image for\na Docker extension must include a `metadata.json` file at the root of its filesystem.\n\nThe format of the `metadata.json` file must be:\n\n```json\n{\n    \"icon\": \"extension-icon.svg\",\n    \"ui\": ...\n    \"vm\": ...\n    \"host\": ...\n}\n```\n\nThe `ui`, `vm`, and `host` sections are optional and depend on what a given extension provides. They describe the extension content to be installed.\n\n### UI section\n\nThe `ui` section defines a new tab that's added to the dashboard in Docker Desktop. It follows the form:\n\n```json\n\"ui\":{\n    \"dashboard-tab\":\n    {\n        \"title\":\"MyTitle\",\n        \"root\":\"/ui\",\n        \"src\":\"index.html\"\n    }\n}\n```\n\n`root` specifies the folder where the UI code is within the extension image filesystem.\n`src` specifies the entrypoint that should be loaded in the extension tab.\n\nOther UI extension points will be available in the future.\n\n### VM section\n\nThe `vm` section defines a backend service that runs inside the Desktop VM. It must define either an `image` or a\n`compose.yaml` file that specifies what service to run in the Desktop VM.\n\n```json\n\"vm\": {\n    \"image\":\"${DESKTOP_PLUGIN_IMAGE}\"\n},\n```\n\nWhen you use `image`, a default compose file is generated for the extension.\n\n> `${DESKTOP_PLUGIN_IMAGE}` is a specific keyword that allows an easy way to refer to the image packaging the extension.\n> It is also possible to specify any other full image name here. However, in many cases using the same image makes\n> things easier for extension development.\n\n```json\n\"vm\": {\n    \"composefile\": \"compose.yaml\"\n},\n```\n\nThe Compose file, with a volume definition for example, would look like:\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /host/path:/container/path\n```\n\n### Host section\n\nThe `host` section defines executables that Docker Desktop copies on the host.\n\n```json\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/myBinary\"\n          },\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/myBinary.exe\"\n          },\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/myBinary\"\n          },\n        ]\n      }\n    ]\n  }\n```\n\n`binaries` defines a list of binaries Docker Desktop copies from the extension image to the host.\n\n`path` specifies the binary path in the image filesystem. Docker Desktop is responsible for copying these files in its own location, and the JavaScript API allows invokes these binaries.\n\nLearn how to [invoke executables](../guides/invoke-host-binaries.md).\n","frontmatter":{"title":"Extension metadata","linkTitle":"Metadata","description":"Docker extension metadata","keywords":"Docker, extensions, sdk, metadata","aliases":["/desktop/extensions-sdk/extensions/METADATA","/desktop/extensions-sdk/architecture/metadata/"]},"isInternal":false,"tokens":693,"sizeBytes":3059},{"name":"security.md","path":"content/manuals/extensions/extensions-sdk/architecture/security.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/architecture/security.md","title":"Architecture Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension security\nlinkTitle: Security\ndescription: Aspects of the security model of extensions\nkeywords: Docker, extensions, sdk, security\naliases:\n - /desktop/extensions-sdk/guides/security/\n - /desktop/extensions-sdk/architecture/security/\n---\n\n## Extension capabilities\n\nAn extension can have the following optional parts: \n* A user interface in HTML or JavaScript, displayed in Docker Desktop Dashboard\n* A backend part that runs as a container\n* Executables deployed on the host machine.\n\nExtensions are executed with the same permissions as the Docker Desktop user. Extension capabilities include running any Docker commands (including running containers and mounting folders), running extension binaries, and accessing files on your machine that are accessible by the user running Docker Desktop.\nNote that extensions are not restricted to execute binaries that they list in the [host section](../architecture/metadata.md#host-section) of the extension metadata: since these binaries can contain any code running as user, they can in turn execute any other commands as long as the user has rights to execute them.\n\nThe Extensions SDK provides a set of JavaScript APIs to invoke commands or invoke these binaries from the extension UI code. Extensions can also provide a backend part that starts a long-lived running container in the background.\n\n> [!IMPORTANT]\n>\n> Make sure you trust the publisher or author of the extension when you install it, as the extension has the same access rights as the user running Docker Desktop.\n","frontmatter":{"title":"Extension security","linkTitle":"Security","description":"Aspects of the security model of extensions","keywords":"Docker, extensions, sdk, security","aliases":["/desktop/extensions-sdk/guides/security/","/desktop/extensions-sdk/architecture/security/"]},"isInternal":false,"tokens":291,"sizeBytes":1546},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/design/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/design/_index.md","title":"Design Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: UI styling overview for Docker extensions\nlinkTitle: Design and UI styling\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases:\n - /desktop/extensions-sdk/design/design-overview/\n - /desktop/extensions-sdk/design/overview/\n - /desktop/extensions-sdk/design/\nweight: 60\n---\n\nOur Design System is a constantly evolving set of specifications that aim to ensure visual consistency across Docker products, and meet [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance). We've opened parts of it to extension authors, documenting basic styles (color, typography) and components. See: [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771).\n\nWe require extensions to match the wider Docker Desktop UI to a certain degree, and reserve the right to make this stricter in the future.\n\nTo get started on your UI, follow the steps below.\n\n## Step one: Choose your framework\n\n### Recommended: React+MUI, using our theme\n\nDocker Desktop's UI is written in React and [MUI](https://mui.com/) (using Material UI specifically). This is the only officially supported framework for building extensions, and the one that the `init` command automatically configures for you. Using it brings significant benefits to authors:\n\n- You can use our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme) to automatically replicate Docker Desktop's look and feel.\n- In future, we'll release utilities and components specifically targeting this combination (e.g. custom MUI components, or React hooks for interacting with Docker).\n\nRead our [MUI best practices](mui-best-practices.md) guide to learn future-proof ways to use MUI with Docker Desktop.\n\n### Not recommended: Some other framework\n\nYou may prefer to use another framework, perhaps because you or your team are more familiar with it or because you have existing assets you want to reuse. This is possible, but highly discouraged. It means that:\n\n- You'll need to manually replicate the look and feel of Docker Desktop. This takes a lot of effort, and if you don't match our theme closely enough, users will find your extension jarring and we may ask you to make changes during a review process.\n- You'll have a higher maintenance burden. Whenever Docker Desktop's theme changes (which could happen in any release), you'll need to manually change your extension to match it.\n- If your extension is open-source, deliberately avoiding common conventions will make it harder for the community to contribute to it.\n\n## Step two: Follow the below recommendations\n\n### Follow our MUI best practices (if applicable)\n\nSee our [MUI best practices](mui-best-practices.md) article.\n\n### Only use colors from our palette\n\nWith minor exceptions, displaying your logo for example, you should only use colors from our palette. These can be found in our [style guide document](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771), and will also soon be available in our MUI theme and via CSS variables.\n\n### Use counterpart colors in light/dark mode\n\nOur colors have been chosen so that the counterpart colors in each variant of the palette should have the same essential characteristics. Anywhere you use `red-300` in light mode, you should use `red-300` in dark mode too.\n\n## What's next?\n\n- Take a look at our [MUI best practices](mui-best-practices.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","frontmatter":{"title":"UI styling overview for Docker extensions","linkTitle":"Design and UI styling","description":"Docker extension design","keywords":"Docker, extensions, design","aliases":["/desktop/extensions-sdk/design/design-overview/","/desktop/extensions-sdk/design/overview/","/desktop/extensions-sdk/design/"],"weight":60},"isInternal":false,"tokens":781,"sizeBytes":3536},{"name":"design-guidelines.md","path":"content/manuals/extensions/extensions-sdk/design/design-guidelines.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/design/design-guidelines.md","title":"Design Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Design guidelines for Docker extensions\nlinkTitle: Guidelines\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-guidelines/\nweight: 10\n---\n\nAt Docker, we aim to build tools that integrate into a user's existing workflows rather than requiring them to adopt new ones. We strongly recommend that you follow these guidelines when creating extensions. We review and approve your Marketplace publication based on these requirements.\n\nHere is a simple checklist to go through when creating your extension:\n- Is it easy to get started?\n- Is it easy to use?\n- Is it easy to get help when needed?\n\n\n## Create a consistent experience with Docker Desktop\n\nUse the [Docker Material UI Theme](https://www.npmjs.com/package/@docker/docker-mui-theme) and the [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771) to ensure that your extension feels like it is part of Docker Desktop to create a seamless experience for users.\n\n- Ensure the extension has both a light and dark theme. Using the components and styles as per the Docker style guide ensures that your extension meets the [level AA accessibility standard.](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n  ![Light and dark mode](images/light_dark_mode.webp)\n\n- Ensure that your extension icon is visible both in light and dark mode.\n\n  ![Icon colors in light and dark mode](images/icon_colors.webp)\n\n- Ensure that the navigational behavior is consistent with the rest of Docker Desktop. Add a header to set the context for the extension.\n\n  ![Header that sets the context](images/header.webp)\n\n- Avoid embedding terminal windows. The advantage we have with Docker Desktop over the CLI is that we have the opportunity to provide rich information to users. Make use of this interface as much as possible. \n\n  ![Terminal window used incorrectly](images/terminal_window_dont.webp)\n\n  ![Terminal window used correctly](images/terminal_window_do.webp)\n\n## Build features natively\n\n- In order not to disrupt the flow of users, avoid scenarios where the user has to navigate outside Docker Desktop, to the CLI or a webpage for example, in order to carry out certain functionalities. Instead, build features that are native to Docker Desktop.\n\n  ![Incorrect way to switch context](images/switch_context_dont.webp)\n\n  ![Correct way to switch context](images/switch_context_do.webp)\n\n## Break down complicated user flows\n\n- If a flow is too complicated or the concept is abstract, break down the flow into multiple steps with one simple call-to-action in each step. This helps when onboarding novice users to your extension\n\n  ![A complicated flow](images/complicated_flows.webp)\n\n- Where there are multiple call-to-actions, ensure you use the primary (filled button style) and secondary buttons (outline button style) to convey the importance of each action.\n\n  ![Call to action](images/cta.webp)\n\n## Onboarding new users\n\nWhen creating your extension, ensure that first time users of the extension and your product can understand its value-add and adopt it easily. Ensure you include contextual help within the extension.\n\n- Ensure that all necessary information is added to the extensions Marketplace as well as the extensions detail page. This should include:\n  - Screenshots of the extension. Note that the recommended size for screenshots is 2400x1600 pixels. \n  - A detailed description that covers what the purpose of the extension is, who would find it useful and how it works.\n  - Link to necessary resources such as documentation.\n- If your extension has particularly complex functionality, add a demo or video to the start page. This helps onboard a first time user quickly.\n\n  ![start page](images/start_page.webp)\n\n## What's next?\n\n- Explore our [design principles](design-principles.md).\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","frontmatter":{"title":"Design guidelines for Docker extensions","linkTitle":"Guidelines","description":"Docker extension design","keywords":"Docker, extensions, design","aliases":["/desktop/extensions-sdk/design/design-guidelines/"],"weight":10},"isInternal":false,"tokens":856,"sizeBytes":4016},{"name":"design-principles.md","path":"content/manuals/extensions/extensions-sdk/design/design-principles.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/design/design-principles.md","title":"Design Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Docker design principles\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-principles/\nweight: 20\n---\n\n## Provide actionable guidance\n\nWe anticipate needs and provide simple explanations with clear actions so people are never lost and always know what to do next. Recommendations lead users to functionality that enhances the experience and extends their knowledge.\n\n## Create value through confidence\n\nPeople from all levels of experience should feel they know how to use our product. Experiences are familiar, unified, and easy to use so all users feel like experts.\n\n## Infuse productivity with delight\n\nWe seek out moments of purposeful delight that elevate rather than distract, making work easier and more gratifying. Simple tasks are automated and users are left with more time for innovation.\n\n## Build trust through transparency\n\nWe always provide clarity on what is happening and why. No amount of detail is withheld; the right information is shown at the right time and is always accessible.\n\n## Scale with intention\n\nOur products focus on inclusive growth and are continuously useful and adapt to match changing individual needs. We support all levels of expertise by meeting users where they are with conscious personalization.\n\n## What's next?\n\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","frontmatter":{"title":"Docker design principles","description":"Docker extension design","keywords":"Docker, extensions, design","aliases":["/desktop/extensions-sdk/design/design-principles/"],"weight":20},"isInternal":false,"tokens":278,"sizeBytes":1467},{"name":"mui-best-practices.md","path":"content/manuals/extensions/extensions-sdk/design/mui-best-practices.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/design/mui-best-practices.md","title":"Design Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: MUI best practices\ndescription: Guidelines for using MUI to maximize compatibility with Docker Desktop\nkeywords: Docker, extensions, mui, theme, theming, material-ui, material\naliases: \n - /desktop/extensions-sdk/design/mui-best-practices/\n---\n\nThis article assumes you're following our recommended practice by using our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme).\nFollowing the steps below maximizes compatibility with Docker Desktop and minimizes the work you need to do as an\nextension author. They should be considered supplementary to the non-MUI-specific guidelines found in the\n[UI Styling overview](index.md).\n\n## Assume the theme can change at any time\n\nResist the temptation to fine-tune your UI with precise colors, offsets and font sizings to make it look as attractive as possible. Any specializations you make today will be relative to the current MUI theme, and may look worse when the theme changes. Any part of the theme might change without warning, including (but not limited to):\n\n-  The font, or font sizes\n-  Border thicknesses or styles\n-  Colors:\n   -  Our palette members (e.g. `red-100`) could change their RGB values\n   -  The semantic colors (e.g. `error`, `primary`, `textPrimary`, etc) could be changed to use a different member of our palette\n   -  Background colors (e.g. those of the page, or of dialogs) could change\n-  Spacings:\n   -  The size of the basic unit of spacing,(exposed via `theme.spacing`. For instance, we may allow users to customize the density of the UI\n   -  The default spacing between paragraphs or grid items\n\nThe best way to build your UI, so that it’s robust against future theming changes, is to:\n\n-  Override the default styling as little as possible.\n-  Use semantic typography. e.g. use `Typography`s or `Link`s with appropriate `variant`s instead of using typographical HTML elements (`<a>`, `<p>`, `<h1>`, etc) directly.\n-  Use canned sizes. e.g. use `size=\"small\"` on buttons, or `fontSize=\"small\"` on icons, instead of specifying sizes in pixels.\n-  Prefer semantic colors. e.g. use `error` or `primary` over explicit color codes.\n-  Write as little CSS as possible. Write semantic markup instead. For example, if you want to space out paragraphs of text, use the `paragraph` prop on your `Typography` instances. If you want to space out something else, use a `Stack` or `Grid` with the default spacing.\n-  Use visual idioms you’ve seen in the Docker Desktop UI, since these are the main ones we’ll test any theme changes against.\n\n## When you go custom, centralize it\n\nSometimes you’ll need a piece of UI that doesn’t exist in our design system. If so, we recommend that you first reach out to us. We may already have something in our internal design system, or we may be able to expand our design system to accommodate your use case.\n\nIf you still decide to build it yourself after contacting us, try and define the new UI in a reusable fashion. If you define your custom UI in just one place, it’ll make it easier to change in the future if our core theme changes. You could use:\n\n-  A new `variant` of an existing component - see [MUI docs](https://mui.com/material-ui/customization/theme-components/#creating-new-component-variants)\n-  A MUI mixin (a freeform bundle of reusable styling rules defined inside a theme)\n-  A new [reusable component](https://mui.com/material-ui/customization/how-to-customize/#2-reusable-component)\n\nSome of the above options require you to extend our MUI theme. See the MUI documentation on [theme composition](https://mui.com/material-ui/customization/theming/#nesting-the-theme).\n\n## What's next?\n\n- Take a look at our [UI styling guide](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","frontmatter":{"title":"MUI best practices","description":"Guidelines for using MUI to maximize compatibility with Docker Desktop","keywords":"Docker, extensions, mui, theme, theming, material-ui, material","aliases":["/desktop/extensions-sdk/design/mui-best-practices/"]},"isInternal":false,"tokens":873,"sizeBytes":3775},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/dev/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/_index.md","title":"Dev Skill","category":"anthropic-skill","format":"markdown","content":"---\nbuild:\n  render: never\ntitle: Developer SDK tools\n---\n","frontmatter":{"build":{"render":"never"},"title":"Developer SDK tools"},"isInternal":false,"tokens":15,"sizeBytes":58},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/dev/api/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/_index.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\nbuild:\n  render: never\ntitle: Extension APIs\n---\n","frontmatter":{"build":{"render":"never"},"title":"Extension APIs"},"isInternal":false,"tokens":14,"sizeBytes":53},{"name":"backend.md","path":"content/manuals/extensions/extensions-sdk/dev/api/backend.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/backend.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension Backend\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/backend/\n---\n\nThe `ddClient.extension.vm` object can be used to communicate with the backend defined in the [vm section](../../architecture/metadata.md#vm-section) of the extension metadata.\n\n## get\n\n▸ **get**(`url`): `Promise`<`unknown`\\>\n\nPerforms an HTTP GET request to a backend service.\n\n```typescript\nddClient.extension.vm.service\n .get(\"/some/service\")\n .then((value: any) => console.log(value)\n```\n\nSee [Service API Reference](/reference/api/extensions-sdk/HttpService.md) for other HTTP methods.\n\n> Deprecated extension backend communication\n>\n> The methods below that use `window.ddClient.backend` are deprecated and will be removed in a future version. Use the methods specified above.\n\nThe `window.ddClient.backend` object can be used to communicate with the backend\ndefined in the [vm section](../../architecture/metadata.md#vm-section) of the\nextension metadata. The client is already connected to the backend.\n\nExample usages:\n\n```typescript\nwindow.ddClient.backend\n  .get(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .post(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .put(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .patch(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .delete(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .head(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .request({ url: \"/url\", method: \"GET\", headers: { 'header-key': 'header-value' }, data: { ... }})\n  .then((value: any) => console.log(value));\n```\n\n## Run a command in the extension backend container\n\nFor example, execute the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"]);\n```\n\nStream the output of the command executed in the backend container. For example, spawn the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nFor more details, refer to the [Extension VM API Reference](/reference/api/extensions-sdk/ExtensionVM.md)\n\n> Deprecated extension backend command execution\n>\n> This method is deprecated and will be removed in a future version. Use the specified method above.\n\nIf your extension ships with additional binaries that should be run inside the\nbackend container, you can use the `execInVMExtension` function:\n\n```typescript\nconst output = await window.ddClient.backend.execInVMExtension(\n  `cliShippedInTheVm xxx`\n);\nconsole.log(output);\n```\n\n## Invoke an extension binary on the host\n\nInvoke a binary on the host. The binary is typically shipped with your extension using the [host section](../../architecture/metadata.md#host-section) in the extension metadata. Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nFor example, execute the shipped binary `kubectl -h` command in the host:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"]);\n```\n\nAs long as the `kubectl` binary is shipped as part of your extension, you can spawn the `kubectl -h` command in the host and get the output stream:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"], {\n  stream: {\n    onOutput(data: { stdout: string } | { stderr: string }): void {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error: any): void {\n      console.error(error);\n    },\n    onClose(exitCode: number): void {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nYou can stream the output of the command executed in the backend container or in the host.\n\nFor more details, refer to the [Extension Host API Reference](/reference/api/extensions-sdk/ExtensionHost.md)\n\n> Deprecated invocation of extension binary\n>\n> This method is deprecated and will be removed in a future version. Use the method specified above.\n\nTo execute a command in the host:\n\n```typescript\nwindow.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((cmdResult: any) => {\n  console.log(cmdResult);\n});\n```\n\nTo stream the output of the command executed in the backend container or in the host:\n\n```typescript\nwindow.ddClient.spawnHostCmd(\n  `cliShippedOnHost`,\n  [`arg1`, `arg2`],\n  (data: any, err: any) => {\n    console.log(data.stdout, data.stderr);\n    // Once the command exits we get the status code\n    if (data.code) {\n      console.log(data.code);\n    }\n  }\n);\n```\n\n> [!NOTE]\n> \n>You cannot use this to chain commands in a single `exec()` invocation (like `cmd1 $(cmd2)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n","frontmatter":{"title":"Extension Backend","description":"Docker extension API","keywords":"Docker, extensions, sdk, API","aliases":["/desktop/extensions-sdk/dev/api/backend/"]},"isInternal":false,"tokens":1292,"sizeBytes":5592},{"name":"dashboard-routes-navigation.md","path":"content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Navigation\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard-routes-navigation/\n---\n\n`ddClient.desktopUI.navigate` enables navigation to specific screens of Docker Desktop such as the containers tab, the images tab, or a specific container's logs.\n\nFor example, navigate to a given container logs:\n\n```typescript\nconst id = '8c7881e6a107';\ntry {\n  await ddClient.desktopUI.navigate.viewContainerLogs(id);\n} catch (e) {\n  console.error(e);\n  ddClient.desktopUI.toast.error(\n    `Failed to navigate to logs for container \"${id}\".`\n  );\n}\n```\n\n#### Parameters\n\n| Name | Type     | Description                                                                                                                                                                                            |\n| :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id` | `string` | The full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`. You can use the `--no-trunc` flag as part of the `docker ps` command to display the full container id. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\nA promise that fails if the container doesn't exist.\n\nFor more details about all navigation methods, see the [Navigation API reference](/reference/api/extensions-sdk/NavigationIntents.md).\n\n> Deprecated navigation methods\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.navigateToContainers();\n// id - the full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`\nwindow.ddClient.navigateToContainer(id);\nwindow.ddClient.navigateToContainerLogs(id);\nwindow.ddClient.navigateToContainerInspect(id);\nwindow.ddClient.navigateToContainerStats(id);\n\nwindow.ddClient.navigateToImages();\nwindow.ddClient.navigateToImage(id, tag);\n\nwindow.ddClient.navigateToVolumes();\nwindow.ddClient.navigateToVolume(volume);\n\nwindow.ddClient.navigateToDevEnvironments();\n```\n","frontmatter":{"title":"Navigation","description":"Docker extension API","keywords":"Docker, extensions, sdk, API","aliases":["/desktop/extensions-sdk/dev/api/dashboard-routes-navigation/"]},"isInternal":false,"tokens":445,"sizeBytes":2220},{"name":"dashboard.md","path":"content/manuals/extensions/extensions-sdk/dev/api/dashboard.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/dashboard.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Dashboard\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard/\n---\n\n## User notifications\n\nToasts provide a brief notification to the user. They appear temporarily and\nshouldn't interrupt the user experience. They also don't require user input to disappear.\n\n### success\n\n▸ **success**(`msg`): `void`\n\nUse to display a toast message of type success.\n\n```typescript\nddClient.desktopUI.toast.success(\"message\");\n```\n\n### warning\n\n▸ **warning**(`msg`): `void`\n\nUse to display a toast message of type warning.\n\n```typescript\nddClient.desktopUI.toast.warning(\"message\");\n```\n\n### error\n\n▸ **error**(`msg`): `void`\n\nUse to display a toast message of type error.\n\n```typescript\nddClient.desktopUI.toast.error(\"message\");\n```\n\nFor more details about method parameters and the return types available, see [Toast API reference](/reference/api/extensions-sdk/Toast.md).\n\n> Deprecated user notifications\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.toastSuccess(\"message\");\nwindow.ddClient.toastWarning(\"message\");\nwindow.ddClient.toastError(\"message\");\n```\n\n## Open a file selection dialog\n\nThis function opens a file selector dialog that asks the user to select a file or folder.\n\n▸ **showOpenDialog**(`dialogProperties`): `Promise`<[`OpenDialogResult`](/reference/api/extensions-sdk/OpenDialogResult.md)\\>:\n\nThe `dialogProperties` parameter is a list of flags passed to Electron to customize the dialog's behaviour. For example, you can pass `multiSelections` to allow a user to select multiple files. See [Electron's documentation](https://www.electronjs.org/docs/latest/api/dialog) for a full list.\n\n```typescript\nconst result = await ddClient.desktopUI.dialog.showOpenDialog({\n  properties: [\"openDirectory\"],\n});\nif (!result.canceled) {\n  console.log(result.paths);\n}\n```\n\n## Open a URL\n\nThis function opens an external URL with the system default browser.\n\n▸ **openExternal**(`url`): `void`\n\n```typescript\nddClient.host.openExternal(\"https://docker.com\");\n```\n\n> The URL must have the protocol `http` or `https`.\n\nFor more details about method parameters and the return types available, see [Desktop host API reference](/reference/api/extensions-sdk/Host.md).\n\n> Deprecated external URL opening\n>\n> This method is deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.openExternal(\"https://docker.com\");\n```\n\n## Navigation to Dashboard routes\n\nFrom your extension, you can also [navigate](dashboard-routes-navigation.md) to other parts of the Docker Desktop Dashboard.\n","frontmatter":{"title":"Dashboard","description":"Docker extension API","keywords":"Docker, extensions, sdk, API","aliases":["/desktop/extensions-sdk/dev/api/dashboard/"]},"isInternal":false,"tokens":594,"sizeBytes":2716},{"name":"docker.md","path":"content/manuals/extensions/extensions-sdk/dev/api/docker.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/docker.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Docker\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/docker/\n---\n\n## Docker objects\n\n▸ **listContainers**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of containers:\n\n```typescript\nconst containers = await ddClient.docker.listContainers();\n```\n\n▸ **listImages**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of local container images:\n\n```typescript\nconst images = await ddClient.docker.listImages();\n```\n\nSee the [Docker API reference](/reference/api/extensions-sdk/Docker.md) for details about these methods.\n\n> Deprecated access to Docker objects\n>\n> The methods below are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nconst containers = await window.ddClient.listContainers();\n\nconst images = await window.ddClient.listImages();\n```\n\n## Docker commands\n\nExtensions can also directly execute the `docker` command line.\n\n▸ **exec**(`cmd`, `args`): `Promise`<[`ExecResult`](/reference/api/extensions-sdk/ExecResult.md)\\>\n\n```typescript\nconst result = await ddClient.docker.cli.exec(\"info\", [\n  \"--format\",\n  '\"{{ json . }}\"',\n]);\n```\n\nThe result contains both the standard output and the standard error of the executed command:\n\n```json\n{\n  \"stderr\": \"...\",\n  \"stdout\": \"...\"\n}\n```\n\nIn this example, the command output is JSON.\nFor convenience, the command result object also has methods to easily parse it:\n\n- `result.lines(): string[]` splits output lines.\n- `result.parseJsonObject(): any` parses a well-formed json output.\n- `result.parseJsonLines(): any[]` parses each output line as a json object.\n\n▸ **exec**(`cmd`, `args`, `options`): `void`\n\nThe command above streams the output as a result of the execution of a Docker command.\nThis is useful if you need to get the output as a stream or the output of the command is too long.\n\n```typescript\nawait ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n    splitOutputLines: true,\n  },\n});\n```\n\nThe child process created by the extension is killed (`SIGTERM`) automatically when you close the dashboard in Docker Desktop or when you exit the extension UI.\nIf needed, you can also use the result of the `exec(streamOptions)` call in order to kill (`SIGTERM`) the process.\n\n```typescript\nconst logListener = await ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    // ...\n  },\n});\n\n// when done listening to logs or before starting a new one, kill the process\nlogListener.close();\n```\n\nThis `exec(streamOptions)` API can also be used to listen to docker events:\n\n```typescript\nawait ddClient.docker.cli.exec(\n  \"events\",\n  [\"--format\", \"{{ json . }}\", \"--filter\", \"container=my-container\"],\n  {\n    stream: {\n      onOutput(data) {\n        if (data.stdout) {\n          const event = JSON.parse(data.stdout);\n          console.log(event);\n        } else {\n          console.log(data.stderr);\n        }\n      },\n      onClose(exitCode) {\n        console.log(\"onClose with exit code \" + exitCode);\n      },\n      splitOutputLines: true,\n    },\n  }\n);\n```\n\n> [!NOTE]\n>\n>You cannot use this to chain commands in a single `exec()` invocation (like `docker kill $(docker ps -q)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n\nSee the [Exec API reference](/reference/api/extensions-sdk/Exec.md) for details about these methods.\n\n> Deprecated execution of Docker commands\n>\n> This method is deprecated and will be removed in a future version. Use the one specified just below.\n\n```typescript\nconst output = await window.ddClient.execDockerCmd(\n  \"info\",\n  \"--format\",\n  '\"{{ json . }}\"'\n);\n\nwindow.ddClient.spawnDockerCmd(\"logs\", [\"-f\", \"...\"], (data, error) => {\n  console.log(data.stdout);\n});\n```\n","frontmatter":{"title":"Docker","description":"Docker extension API","keywords":"Docker, extensions, sdk, API","aliases":["/desktop/extensions-sdk/dev/api/docker/"]},"isInternal":false,"tokens":964,"sizeBytes":4124},{"name":"overview.md","path":"content/manuals/extensions/extensions-sdk/dev/api/overview.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/api/overview.md","title":"Api Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension UI API\ndescription: Docker extension development overview\nkeywords: Docker, extensions, sdk, development\naliases:\n - /desktop/extensions-sdk/dev/api/overview/\n---\n\nThe extensions UI runs in a sandboxed environment and doesn't have access to any\nelectron or nodejs APIs.\n\nThe extension UI API provides a way for the frontend to perform different actions\nand communicate with the Docker Desktop dashboard or the underlying system.\n\nJavaScript API libraries, with Typescript support, are available in order to get all the API definitions in to your extension code.\n\n- [@docker/extension-api-client](https://www.npmjs.com/package/@docker/extension-api-client) gives access to the extension API entrypoint `DockerDesktopClient`.\n- [@docker/extension-api-client-types](https://www.npmjs.com/package/@docker/extension-api-client-types) can be added as a dev dependency in order to get types auto-completion in your IDE.\n\n```Typescript\nimport { createDockerDesktopClient } from '@docker/extension-api-client';\n\nexport function App() {\n  // obtain Docker Desktop client\n  const ddClient = createDockerDesktopClient();\n  // use ddClient to perform extension actions\n}\n```\n\nThe `ddClient` object gives access to various APIs:\n\n- [Extension Backend](backend.md)\n- [Docker](docker.md)\n- [Dashboard](dashboard.md)\n- [Navigation](dashboard-routes-navigation.md)\n\nSee also the [Extensions API reference](/reference/api/extensions-sdk/_index.md).\n","frontmatter":{"title":"Extension UI API","description":"Docker extension development overview","keywords":"Docker, extensions, sdk, development","aliases":["/desktop/extensions-sdk/dev/api/overview/"]},"isInternal":false,"tokens":311,"sizeBytes":1451},{"name":"continuous-integration.md","path":"content/manuals/extensions/extensions-sdk/dev/continuous-integration.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/continuous-integration.md","title":"Dev Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Continuous Integration (CI)\ndescription: Automatically test and validate your extension.\nkeywords: Docker, Extensions, sdk, CI, test, regression\naliases: \n - /desktop/extensions-sdk/dev/continuous-integration/\nweight: 20\n---\n\nIn order to help validate your extension and ensure it's functional, the Extension SDK provides tools to help you setup continuous integration for your extension.\n\n> [!IMPORTANT]\n>\n> The [Docker Desktop Action](https://github.com/docker/desktop-action) and the [extension-test-helper library](https://www.npmjs.com/package/@docker/extension-test-helper) are both [experimental](https://docs.docker.com/release-lifecycle/#experimental).\n\n## Setup CI environment with GitHub Actions\n\nYou need Docker Desktop to be able to install and validate your extension.\nYou can start Docker Desktop in GitHub Actions using the [Docker Desktop Action](https://github.com/docker/desktop-action), by adding the following to a workflow file:\n\n```yaml\nsteps:\n  - id: start_desktop\n    uses: docker/desktop-action/start@v0.1.0\n```\n\n> [!NOTE]\n>\n> This action supports only GitHub Actions macOS runners at the moment. You need to specify `runs-on: macOS-latest` for your end to end tests.\n\nOnce the step has executed, the next steps use Docker Desktop and the Docker CLI to install and test the extension.\n\n## Validating your extension with Puppeteer\n\nOnce Docker Desktop starts in CI, you can build, install, and validate your extension with Jest and Puppeteer.\n\nFirst, build and install the extension from your test:\n\n```ts\nimport { DesktopUI } from \"@docker/extension-test-helper\";\nimport { exec as originalExec } from \"child_process\";\nimport * as util from \"util\";\n\nexport const exec = util.promisify(originalExec);\n\n// keep a handle on the app to stop it at the end of tests\nlet dashboard: DesktopUI;\n\nbeforeAll(async () => {\n  await exec(`docker build -t my/extension:latest .`, {\n    cwd: \"my-extension-src-root\",\n  });\n\n  await exec(`docker extension install -f my/extension:latest`);\n});\n```\n\nThen open the Docker Desktop Dashboard and run some tests in your extension's UI:\n\n```ts\ndescribe(\"Test my extension\", () => {\n  test(\"should be functional\", async () => {\n    dashboard = await DesktopUI.start();\n\n    const eFrame = await dashboard.navigateToExtension(\"my/extension\");\n\n    // use puppeteer APIs to manipulate the UI, click on buttons, expect visual display and validate your extension\n    await eFrame.waitForSelector(\"#someElementId\");\n  });\n});\n```\n\nFinally, close the Docker Desktop Dashboard and uninstall your extension:\n\n```ts\nafterAll(async () => {\n  dashboard?.stop();\n  await exec(`docker extension uninstall my/extension`);\n});\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n","frontmatter":{"title":"Continuous Integration (CI)","description":"Automatically test and validate your extension.","keywords":"Docker, Extensions, sdk, CI, test, regression","aliases":["/desktop/extensions-sdk/dev/continuous-integration/"],"weight":20},"isInternal":false,"tokens":644,"sizeBytes":2949},{"name":"test-debug.md","path":"content/manuals/extensions/extensions-sdk/dev/test-debug.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/test-debug.md","title":"Dev Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Test and debug\ndescription: Test and debug your extension.\nkeywords: Docker, Extensions, sdk, preview, update, Chrome DevTools\naliases:\n - /desktop/extensions-sdk/build/test-debug/\n - /desktop/extensions-sdk/dev/test-debug/\nweight: 10\n---\n\nIn order to improve the developer experience, Docker Desktop provides a set of tools to help you test and debug your extension.\n\n### Open Chrome DevTools\n\nIn order to open the Chrome DevTools for your extension when you select the **Extensions** tab, run:\n\n```console\n$ docker extension dev debug <name-of-your-extensions>\n```\n\nEach subsequent click on the extension tab also opens Chrome DevTools. To stop this behaviour, run:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\nAfter an extension is deployed, it is also possible to open Chrome DevTools from the UI extension part using a variation of the [Konami Code](https://en.wikipedia.org/wiki/Konami_Code). Select the **Extensions** tab, and then hit the key sequence `up, up, down, down, left, right, left, right, p, d, t`.\n\n### Hot reloading whilst developing the UI\n\nDuring UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\nextension. To do this, you can configure Docker Desktop to load your UI from a development server, such as the one\n[Vite](https://vitejs.dev/) starts when invoked with `npm start`.\n\nAssuming your app runs on the default port, start your UI app and then run:\n\n```console\n$ cd ui\n$ npm run dev\n```\n\nThis starts a development server that listens on port 3000.\n\nYou can now tell Docker Desktop to use this as the frontend source. In another terminal run:\n\n```console\n$ docker extension dev ui-source <name-of-your-extensions> http://localhost:3000\n```\n\nClose and reopen the Docker Desktop dashboard and go to your extension. All the changes to the frontend code are immediately visible.\n\nOnce finished, you can reset the extension configuration to the original settings. This will also reset opening Chrome DevTools if you used `docker extension dev debug <name-of-your-extensions>`:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\n## Show the extension containers\n\nIf your extension is composed of one or more services running as containers in the Docker Desktop VM, you can access them easily from the dashboard in Docker Desktop.\n\n1. In Docker Desktop, navigate to **Settings**.\n2. Under the **Extensions** tab, select the **Show Docker Desktop Extensions system containers** option. You can now view your extension containers and their logs.\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Explore our [design principles](../design/design-principles.md).\n- Take a look at our [UI styling guidelines](../design/_index.md).\n- Learn how to [setup CI for your extension](continuous-integration.md).\n","frontmatter":{"title":"Test and debug","description":"Test and debug your extension.","keywords":"Docker, Extensions, sdk, preview, update, Chrome DevTools","aliases":["/desktop/extensions-sdk/build/test-debug/","/desktop/extensions-sdk/dev/test-debug/"],"weight":10},"isInternal":false,"tokens":675,"sizeBytes":3096},{"name":"usage.md","path":"content/manuals/extensions/extensions-sdk/dev/usage.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/dev/usage.md","title":"Dev Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: CLI reference\ndescription: Docker extension CLI\nkeywords: Docker, extensions, sdk, CLI\naliases:\n - /desktop/extensions-sdk/dev/cli/usage/\n - /desktop/extensions-sdk/dev/usage/\nweight: 30\n---\n\nThe Extensions CLI is an extension development tool that is used to manage Docker extensions. Actions include install, list, remove, and validate extensions.\n\n- `docker extension enable` turns on Docker extensions.\n- `docker extension dev` commands for extension development.\n- `docker extension disable` turns off Docker extensions.\n- `docker extension init` creates a new Docker extension.\n- `docker extension install` installs a Docker extension with the specified image.\n- `docker extension ls` list installed Docker extensions.\n- `docker extension rm` removes a Docker extension.\n- `docker extension update` removes and re-installs a Docker extension.\n- `docker extension validate` validates the extension metadata file against the JSON schema.\n","frontmatter":{"title":"CLI reference","description":"Docker extension CLI","keywords":"Docker, extensions, sdk, CLI","aliases":["/desktop/extensions-sdk/dev/cli/usage/","/desktop/extensions-sdk/dev/usage/"],"weight":30},"isInternal":false,"tokens":190,"sizeBytes":953},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/extensions/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/_index.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: \"Part two: Publish\"\ndescription: General steps in how to publish an extension\nkeywords: Docker, Extensions, sdk, publish\naliases: \n - /desktop/extensions-sdk/extensions/\nweight: 40\n---\n\nThis section describes how to make your extension available and more visible, so users can discover it and install it with a single click.\n\n## Release your extension\n\nAfter you have developed your extension and tested it locally, you are ready to release the extension and make it available for others to install and use (either internally with your team, or more publicly).\n\nReleasing your extension consists of:\n\n- Providing information about your extension: description, screenshots, etc. so users can decide to install your extension\n- [Validating](validate.md) that the extension is built in the right format and includes the required information\n- Making the extension image available on [Docker Hub](https://hub.docker.com/)\n\nSee [Package and release your extension](DISTRIBUTION.md) for more details about the release process.\n\n## Promote your extension\n\nOnce your extension is available on Docker Hub, users who have access to the extension image can install it using the Docker CLI.\n\n### Use a share extension link\n\nYou can also [generate a share URL](share.md) in order to share your extension within your team, or promote your extension on the internet. The share link lets users view the extension description and screenshots.\n\n### Publish your extension in the Marketplace\n\nYou can publish your extension in the Extensions Marketplace to make it more discoverable. You must [submit your extension](publish.md) if you want to have it published in the Marketplace.\n\n## What happens next\n\n### New releases\n\nOnce you have released your extension, you can push a new release just by pushing a new version of the extension image, with an incremented tag (still using `semver` conventions).\nExtensions published in the Marketplace benefit from update notifications to all Desktop users that have installed the extension. For more details, see [new releases and updates](DISTRIBUTION.md#new-releases-and-updates).\n\n### Extension support and user feedback\n\nIn addition to providing a description of your extension's features and screenshots, you should also specify additional URLs using [extension labels](labels.md). This direct users to your website for reporting bugs and feedback, and accessing documentation and support.\n\n{{% include \"extensions-form.md\" %}}\n","frontmatter":{"title":"Part two: Publish","description":"General steps in how to publish an extension","keywords":"Docker, Extensions, sdk, publish","aliases":["/desktop/extensions-sdk/extensions/"],"weight":40},"isInternal":false,"tokens":484,"sizeBytes":2467},{"name":"DISTRIBUTION.md","path":"content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Package and release your extension\ndescription: Docker extension distribution\nkeywords: Docker, extensions, sdk, distribution\naliases: \n - /desktop/extensions-sdk/extensions/DISTRIBUTION/\nweight: 30\n---\n\nThis page contains additional information on how to package and distribute extensions.\n\n## Package your extension\n\nDocker extensions are packaged as Docker images. The entire extension runtime including the UI, backend services (host or VM), and any necessary binary must be included in the extension image.\nEvery extension image must contain a `metadata.json` file at the root of its filesystem that defines the [contents of the extension](../architecture/metadata.md).\n\nThe Docker image must have several [image labels](labels.md), providing information about the extension. See how to use [extension labels](labels.md) to provide extension overview information.\n\nTo package and release an extension, you must build a Docker image (`docker build`), and push the image to [Docker Hub](https://hub.docker.com/) (`docker push`) with a specific tag that lets you manage versions of the extension.\n\n## Release your extension\n\nDocker image tags must follow semver conventions in order to allow fetching the latest version of the extension, and to know if there are updates available. See [semver.org](https://semver.org/) to learn more about semantic versioning.\n\nExtension images must be multi-arch images so that users can install extensions on ARM/AMD hardware. These multi-arch images can include ARM/AMD specific binaries. Mac users will automatically use the right image based on their architecture.\nExtensions that install binaries on the host must also provide Windows binaries in the same extension image. See how to [build a multi-arch image](multi-arch.md) for your extension.\n\nYou can implement extensions without any constraints on the code repository. Docker doesn't need access to the code repository in order to use the extension. Also, you can manage new releases of your extension, without any dependency on Docker Desktop releases.\n\n## New releases and updates\n\nYou can release a new version of your Docker extension by pushing a new image with a new tag to Docker Hub.\n\nAny new image pushed to an image repository corresponding to an extension defines a new version of that extension. Image tags are used to identify version numbers. Extension versions must follow semver to make it easy to understand and compare versions.\n\nDocker Desktop scans the list of extensions published in the marketplace for new versions, and provides notifications to users when they can upgrade a specific extension. Extensions that aren't part of the Marketplace don't have automatic update notifications at the moment.\n\nUsers can download and install the newer version of any extension without updating Docker Desktop itself.\n\n## Extension API dependencies\n\nExtensions must specify the Extension API version they rely on. Docker Desktop checks the extension's required version, and only proposes to install extensions that are compatible with the current Docker Desktop version installed. Users might need to update Docker Desktop in order to install the latest extensions available.\n\nExtension image labels must specify the API version that the extension relies upon. This allows Docker Desktop to inspect newer versions of extension images without downloading the full extension image upfront.\n\n## License on extensions and the extension SDK\n\nThe [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use.\n\nThere is no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n","frontmatter":{"title":"Package and release your extension","description":"Docker extension distribution","keywords":"Docker, extensions, sdk, distribution","aliases":["/desktop/extensions-sdk/extensions/DISTRIBUTION/"],"weight":30},"isInternal":false,"tokens":704,"sizeBytes":3730},{"name":"labels.md","path":"content/manuals/extensions/extensions-sdk/extensions/labels.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/labels.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Extension image labels\nlinkTitle: Add labels\ndescription: Docker extension labels\nkeywords: Docker, extensions, sdk, labels\naliases: \n - /desktop/extensions-sdk/extensions/labels/\nweight: 10\n---\n\nExtensions use image labels to provide additional information such as a title, description, screenshots, and more.\n\nThis information is then displayed as an overview of the extension, so users can choose to install it.\n\n![An extension overview, generated from labels](images/marketplace-details.png)\n\nYou can define [image labels](/reference/dockerfile.md#label) in the extension's `Dockerfile`.\n\n> [!IMPORTANT]\n>\n> If any of the **required** labels are missing in the `Dockerfile`, Docker Desktop considers the extension invalid and doesn't list it in the Marketplace.\n\n\nHere is the list of labels you can or need to specify when building your extension:\n\n| Label                                       | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Example                                                                                                                                                                                                                                                         |\n| ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `org.opencontainers.image.title`            | Yes      | Human-readable title of the image (string). This appears in the UI for Docker Desktop.                                                                                                                                                                                                                                                                                                                                                                                                                | my-extension                                                                                                                                                                                                                                                    |\n| `org.opencontainers.image.description`      | Yes      | Human-readable description of the software packaged in the image (string)                                                                                                                                                                                                                                                                                                                                                                                                                             | This extension is cool.                                                                                                                                                                                                                                         |\n| `org.opencontainers.image.vendor`           | Yes      | Name of the distributing entity, organization, or individual.                                                                                                                                                                                                                                                                                                                                                                                                                                         | Acme, Inc.                                                                                                                                                                                                                                                      |\n| `com.docker.desktop.extension.api.version`  | Yes      | Version of the Docker Extension manager that the extension is compatible with. It must follow [semantic versioning](https://semver.org/).                                                                                                                                                                                                                                                                                                                                                             | A specific version like `0.1.0` or, a constraint expression: `>= 0.1.0`, `>= 1.4.7, < 2.0` . For your first extension, you can use `docker extension version` to know the SDK API version and specify `>= <SDK_API_VERSION>`.                                   |\n| `com.docker.desktop.extension.icon`         | Yes      | The extension icon (format: .svg .png .jpg)                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com/assets/image.svg`                                                                                                                                                                                                                          |\n| `com.docker.extension.screenshots`          | Yes      | A JSON array of image URLs and an alternative text displayed to users (in the order they appear in your metadata) in your extension's details page. **Note:** The recommended size for screenshots is 2400x1600 pixels.                                                                                                                                                                                                                                                                               | `[{\"alt\":\"alternative text for image 1\",` `\"url\":\"https://example.com/image1.png\"},` `{\"alt\":\"alternative text for image2\",` `\"url\":\"https://example.com/image2.jpg\"}]`                                                                                         |\n| `com.docker.extension.detailed-description` | Yes      | Additional information in plain text or HTML about the extension to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                    | `My detailed description` or `<h1>My detailed description</h1>`                                                                                                                                                                                                 |\n| `com.docker.extension.publisher-url`        | Yes      | The publisher website URL to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com`                                                                                                                                                                                                                                           |\n| `com.docker.extension.additional-urls`      | No       | A JSON array of titles and additional URLs displayed to users (in the order they appear in your metadata) in your extension's details page. Docker recommends you display the following links if they apply: documentation, support, terms of service, and privacy policy links.                                                                                                                                                                                                                      | `[{\"title\":\"Documentation\",\"url\":\"https://example.com/docs\"},` `{\"title\":\"Support\",\"url\":\"https://example.com/bar/support\"},` `{\"title\":\"Terms of Service\",\"url\":\"https://example.com/tos\"},` `{\"title\":\"Privacy policy\",\"url\":\"https://example.com/privacy\"}]` |\n| `com.docker.extension.changelog`            | Yes      | Changelog in plain text or HTML containing the change for the current version only.                                                                                                                                                                                                                                                                                                                                                                                                                   | `Extension changelog` or `<p>Extension changelog<ul>` `<li>New feature A</li>` `<li>Bug fix on feature B</li></ul></p>`                                                                                                                                         |\n| `com.docker.extension.account-info`         | No       | Whether the user needs to register to a SaaS platform to use some features of the extension.                                                                                                                                                                                                                                                                                                                                                                                                          | `required` in case it does, leave it empty otherwise.                                                                                                                                                                                                           |\n| `com.docker.extension.categories`           | No       | The list of Marketplace categories that your extension belongs to: `ci-cd`, `container-orchestration`, `cloud-deployment`, `cloud-development`, `database`, `kubernetes`, `networking`, `image-registry`, `security`, `testing-tools`, `utility-tools`,`volumes`. If you don't specify this label, users won't be able to find your extension in the Extensions Marketplace when filtering by a category. Extensions published to the Marketplace before the 22nd of September 2022 have been auto-categorized by Docker. | Specified as comma separated values in case of having multiple categories e.g: `kubernetes,security` or a single value e.g. `kubernetes`.                                                                                                   |\n\n> [!TIP]\n>\n> Docker Desktop applies CSS styles to the provided HTML content. You can make sure that it renders correctly \n> [within the Marketplace](#preview-the-extension-in-the-marketplace). It is recommended that you follow the \n> [styling guidelines](../design/_index.md).\n\n## Preview the extension in the Marketplace\n\nYou can validate that the image labels render as you expect.\n\nWhen you create and install your unpublished extension, you can preview the extension in the Marketplace's **Managed** tab. You can see how the extension labels render in the list and in the details page of the extension.\n\n> Preview extensions already listed in Marketplace\n>\n> When you install a local image of an extension already published in the Marketplace, for example with the tag `latest`, your local image is not detected as \"unpublished\".\n>\n> You can re-tag your image in order to have a different image name that's not listed as a published extension.\n> Use `docker tag org/published-extension unpublished-extension` and then `docker extension install unpublished-extension`.\n\n![List preview](images/list-preview.png)\n","frontmatter":{"title":"Extension image labels","linkTitle":"Add labels","description":"Docker extension labels","keywords":"Docker, extensions, sdk, labels","aliases":["/desktop/extensions-sdk/extensions/labels/"],"weight":10},"isInternal":false,"tokens":1365,"sizeBytes":13249},{"name":"multi-arch.md","path":"content/manuals/extensions/extensions-sdk/extensions/multi-arch.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/multi-arch.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Build multi-arch extensions\ndescription: Step three in creating an extension.\nkeywords: Docker, Extensions, sdk, build, multi-arch\naliases: \n - /desktop/extensions-sdk/extensions/multi-arch/\n---\n\nIt is highly recommended that, at a minimum, your extension is supported for the following architectures:\n\n- `linux/amd64`\n- `linux/arm64`\n\nDocker Desktop retrieves the extension image according to the user’s system architecture. If the extension does not provide an image that matches the user’s system architecture, Docker Desktop is not able to install the extension. As a result, users can’t run the extension in Docker Desktop.\n\n## Build and push for multiple architectures\n\nIf you created an extension from the `docker extension init` command, the\n`Makefile` at the root of the directory includes a target with name\n`push-extension`.\n\nYou can run `make push-extension` to build your extension against both\n`linux/amd64` and `linux/arm64` platforms, and push them to Docker Hub.\n\nFor example:\n\n```console\n$ make push-extension\n```\n\nAlternatively, if you started from an empty directory, use the command below\nto build your extension for multiple architectures:\n\n```console\n$ docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=username/my-extension:0.0.1 .\n```\n\nYou can then check the image manifest to see if the image is available for both\narchitectures using the [`docker buildx imagetools` command](/reference/cli/docker/buildx/imagetools/):\n\n```console\n$ docker buildx imagetools inspect username/my-extension:0.0.1\nName:      docker.io/username/my-extension:0.0.1\nMediaType: application/vnd.docker.distribution.manifest.list.v2+json\nDigest:    sha256:f3b552e65508d9203b46db507bb121f1b644e53a22f851185d8e53d873417c48\n\nManifests:\n  Name:      docker.io/username/my-extension:0.0.1@sha256:71d7ecf3cd12d9a99e73ef448bf63ae12751fe3a436a007cb0969f0dc4184c8c\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/amd64\n\n  Name:      docker.io/username/my-extension:0.0.1@sha256:5ba4ceea65579fdd1181dfa103cc437d8e19d87239683cf5040e633211387ccf\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/arm64\n```\n\n> [!TIP]\n>\n> If you're having trouble pushing the image, make sure you're signed in to Docker Hub. Otherwise, run `docker login` to authenticate.\n\nFor more information, see [Multi-platform images](/manuals/build/building/multi-platform.md) page.\n\n## Adding multi-arch binaries\n\nIf your extension includes some binaries that deploy to the host, it’s important that they also have the right architecture when building the extension against multiple architectures.\n\nCurrently, Docker does not provide a way to explicitly specify multiple binaries for every architecture in the `metadata.json` file. However, you can add architecture-specific binaries depending on the `TARGETARCH` in the extension’s `Dockerfile`.\n\nThe following example shows an extension that uses a binary as part of its operations. The extension needs to run both in Docker Desktop for Mac and Windows.\n\nIn the `Dockerfile`, download the binary depending on the target architecture:\n\n```Dockerfile\n#syntax=docker/dockerfile:1.3-labs\n\nFROM alpine AS dl\nWORKDIR /tmp\nRUN apk add --no-cache curl tar\nARG TARGETARCH\nRUN <<EOT ash\n    mkdir -p /out/darwin\n    curl -fSsLo /out/darwin/kubectl \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/darwin/${TARGETARCH}/kubectl\"\n    chmod a+x /out/darwin/kubectl\nEOT\nRUN <<EOT ash\n    if [ \"amd64\" = \"$TARGETARCH\" ]; then\n        mkdir -p /out/windows\n        curl -fSsLo /out/windows/kubectl.exe \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe\"\n    fi\nEOT\n\nFROM alpine\nLABEL org.opencontainers.image.title=\"example-extension\" \\\n    org.opencontainers.image.description=\"My Example Extension\" \\\n    org.opencontainers.image.vendor=\"Docker Inc.\" \\\n    com.docker.desktop.extension.api.version=\">= 0.3.3\"\n\nCOPY --from=dl /out /\n```\n\nIn the `metadata.json` file, specify the path for every binary on every platform:\n\n```json\n{\n  \"icon\": \"docker.svg\",\n  \"ui\": {\n    \"dashboard-tab\": {\n      \"title\": \"Example Extension\",\n      \"src\": \"index.html\",\n      \"root\": \"ui\"\n    }\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/kubectl\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/kubectl.exe\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nAs a result, when `TARGETARCH` equals:\n\n- `arm64`, the `kubectl` binary fetched corresponds to the `arm64` architecture, and is copied to `/darwin/kubectl` in the final stage.\n- `amd64`, two `kubectl` binaries are fetched. One for Darwin and another for Windows. They are copied to `/darwin/kubectl` and `/windows/kubectl.exe` respectively, in the final stage.\n\n> [!NOTE]\n>\n> The binary destination path for Darwin is `darwin/kubectl` in both cases. The only change is the architecture-specific binary that is downloaded.\n\nWhen the extension is installed, the extension framework copies the binaries from the extension image at `/darwin/kubectl` for Darwin, or `/windows/kubectl.exe` for Windows, to a specific location in the user’s host filesystem.\n\n## Can I develop extensions that run Windows containers?\n\nAlthough Docker Extensions is supported on Docker Desktop for Windows, Mac, and Linux, the extension framework only supports Linux containers. Therefore, you must target `linux` as the OS when you build your extension image.\n","frontmatter":{"title":"Build multi-arch extensions","description":"Step three in creating an extension.","keywords":"Docker, Extensions, sdk, build, multi-arch","aliases":["/desktop/extensions-sdk/extensions/multi-arch/"]},"isInternal":false,"tokens":1404,"sizeBytes":5564},{"name":"publish.md","path":"content/manuals/extensions/extensions-sdk/extensions/publish.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/publish.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Publish in the Marketplace\ndescription: Docker extension distribution\nkeywords: Docker, extensions, publish\naliases: \n - /desktop/extensions-sdk/extensions/publish/\nweight: 50\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\n## Submit your extension to the Marketplace\n\nDocker Desktop displays published extensions in the Extensions Marketplace on [Docker Desktop](https://open.docker.com/extensions/marketplace) and [Docker Hub](https://hub.docker.com/search?q=&type=extension).\nThe Extensions Marketplace is a space where developers can discover extensions to improve their developer experience and propose their own extension to be available for all Desktop users.\n\nWhenever you are [ready to publish](DISTRIBUTION.md) your extension in the Marketplace, you can [self-publish your extension](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n> [!NOTE]\n>\n> As the Extension Marketplace continues to add new features for both Extension users and publishers, you are expected\n> to maintain your extension over time to ensure it stays available in the Marketplace.\n\n> [!IMPORTANT]\n>\n> The Docker manual review process for extensions is paused at the moment. Submit your extension through the [automated submission process](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n### Before you submit\n\nBefore you submit your extension, it must pass the [validation](validate.md) checks.\n\nIt is highly recommended that your extension follows the guidelines outlined in this section before submitting your\nextension. If you request a review from the Docker Extensions team and have not followed the guidelines, the review process may take longer. \n\nThese guidelines don't replace Docker's terms of service or guarantee approval:\n- Review the [design guidelines](../design/design-guidelines.md)\n- Ensure the [UI styling](../design/_index.md) is in line with Docker Desktop guidelines\n- Ensure your extensions support both light and dark mode\n- Consider the needs of both new and existing users of your extension\n- Test your extension with potential users\n- Test your extension for crashes, bugs, and performance issues\n- Test your extension on various platforms (Mac, Windows, Linux)\n- Read the [Terms of Service](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)\n\n#### Validation process\n\nSubmitted extensions go through an automated validation process. If all the validation checks pass successfully, the extension is\npublished on the Marketplace and accessible to all users within a few hours.\nIt is the fastest way to get developers the tools they need and to get feedback from them as you work to\nevolve/polish your extension.\n\n> [!IMPORTANT]\n>\n> Docker Desktop caches the list of extensions available in the Marketplace for 12 hours. If you don't see your\n> extension in the Marketplace, you can restart Docker Desktop to force the cache to refresh.\n","frontmatter":{"title":"Publish in the Marketplace","description":"Docker extension distribution","keywords":"Docker, extensions, publish","aliases":["/desktop/extensions-sdk/extensions/publish/"],"weight":50},"isInternal":false,"tokens":660,"sizeBytes":3303},{"name":"share.md","path":"content/manuals/extensions/extensions-sdk/extensions/share.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/share.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Share your extension\ndescription: Share your extension with a share link\nkeywords: Docker, extensions, share\naliases: \n - /desktop/extensions-sdk/extensions/share/\nweight: 40\n---\n\nOnce your extension image is accessible on Docker Hub, anyone with access to the image can install the extension.\n\nPeople can install your extension by typing `docker extension install my/awesome-extension:latest` in to the terminal.\n\nHowever, this option doesn't provide a preview of the extension before it's installed.\n\n## Create a share URL\n\nDocker lets you share your extensions using a URL.\n\nWhen people navigate to this URL, it opens Docker Desktop and displays a preview of your extension in the same way as an extension in the Marketplace. From the preview, users can then select **Install**.\n\n![Navigate to extension link](images/open-share.png)\n\nTo generate this link you can either:\n\n- Run the following command:\n\n  ```console\n  $ docker extension share my/awesome-extension:0.0.1\n  ```\n\n- Once you have installed your extension locally, navigate to the **Manage** tab and select **Share**.\n\n  ![Share button](images/list-preview.png)\n\n> [!NOTE]\n>\n> Previews of the extension description or screenshots, for example, are created using [extension labels](labels.md).\n","frontmatter":{"title":"Share your extension","description":"Share your extension with a share link","keywords":"Docker, extensions, share","aliases":["/desktop/extensions-sdk/extensions/share/"],"weight":40},"isInternal":false,"tokens":270,"sizeBytes":1269},{"name":"validate.md","path":"content/manuals/extensions/extensions-sdk/extensions/validate.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/extensions/validate.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Validate your extension\nlinkTitle: Validate\ndescription: Step three in the extension creation process\nkeywords: Docker, Extensions, sdk, validate, install\naliases:\n - /desktop/extensions-sdk/extensions/validation/\n - /desktop/extensions-sdk/build/build-install/\n - /desktop/extensions-sdk/dev/cli/build-test-install-extension/\n - /desktop/extensions-sdk/extensions/validate/\nweight: 20\n---\n\nValidate your extension before you share or publish it. Validating the extension ensures that the extension:\n\n- Is built with the [image labels](labels.md) it requires to display correctly in the marketplace\n- Installs and runs without problems\n\nThe Extensions CLI lets you validate your extension before installing and running it locally.\n\nThe validation checks if the extension’s `Dockerfile` specifies all the required labels and if the metadata file is valid against the JSON schema file.\n\nTo validate, run:\n\n```console\n$ docker extension validate <name-of-your-extension>\n```\n\nIf your extension is valid, the following message displays:\n\n```console\nThe extension image \"name-of-your-extension\" is valid\n```\n\nBefore the image is built, it's also possible to validate only the `metadata.json` file:\n\n```console\n$ docker extension validate /path/to/metadata.json\n```\n\nThe JSON schema used to validate the `metadata.json` file against can be found under the [releases page](https://github.com/docker/extensions-sdk/releases/latest).\n","frontmatter":{"title":"Validate your extension","linkTitle":"Validate","description":"Step three in the extension creation process","keywords":"Docker, Extensions, sdk, validate, install","aliases":["/desktop/extensions-sdk/extensions/validation/","/desktop/extensions-sdk/build/build-install/","/desktop/extensions-sdk/dev/cli/build-test-install-extension/","/desktop/extensions-sdk/extensions/validate/"],"weight":20},"isInternal":false,"tokens":290,"sizeBytes":1438},{"name":"_index.md","path":"content/manuals/extensions/extensions-sdk/guides/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/guides/_index.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"---\nbuild:\n  render: never\ntitle: Developer Guides\n---\n","frontmatter":{"build":{"render":"never"},"title":"Developer Guides"},"isInternal":false,"tokens":14,"sizeBytes":55},{"name":"invoke-host-binaries.md","path":"content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Invoke host binaries\ndescription: Add invocations to host binaries from the frontend with the extension\n  SDK.\nkeywords: Docker, extensions, sdk, build\naliases:\n - /desktop/extensions-sdk/guides/invoke-host-binaries/\n---\n\nIn some cases, your extension may need to invoke some command from the host. For example, you\nmight want to invoke the CLI of your cloud provider to create a new resource, or the CLI of a tool your extension\nprovides, or even a shell script that you want to run on the host. \n\nYou could do that by executing the CLI from a container with the extension SDK. But this CLI needs to access the host's filesystem, which isn't easy nor fast if it runs in a container.\n\nThis page describes how to run executables on the host (binaries, shell scripts) that are shipped as part of your extension and deployed to the host. As extensions can run on multiple platforms, this\nmeans that you need to ship the executables for all the platforms you want to support.\n\nLearn more about extensions [architecture](../architecture/_index.md).\n\n> [!NOTE]\n>\n>  Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nIn this example, the CLI is a simple `Hello world` script that must be invoked with a parameter and returns a \nstring.\n\n## Add the executables to the extension\n\n{{< tabs >}}\n{{< tab name=\"Mac and Linux\" >}}\n\nCreate a `bash` script for macOS and Linux, in the file `binaries/unix/hello.sh` with the following content:\n\n```bash\n#!/bin/sh\necho \"Hello, $1!\"\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\nCreate a `batch script` for Windows in another file `binaries/windows/hello.cmd` with the following content:\n\n```bash\n@echo off\necho \"Hello, %1!\"\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThen update the `Dockerfile` to copy the `binaries` folder into the extension's container filesystem and make the\nfiles executable.\n\n```dockerfile\n# Copy the binaries into the right folder\nCOPY --chmod=0755 binaries/windows/hello.cmd /windows/hello.cmd\nCOPY --chmod=0755 binaries/unix/hello.sh /linux/hello.sh\nCOPY --chmod=0755 binaries/unix/hello.sh /darwin/hello.sh\n```\n\n## Invoke the executable from the UI\n\nIn your extension, use the Docker Desktop Client object to [invoke the shell script](../dev/api/backend.md#invoke-an-extension-binary-on-the-host)\nprovided by the extension with the `ddClient.extension.host.cli.exec()` function.\nIn this example, the binary returns a string as result, obtained by `result?.stdout`, as soon as the extension view is rendered.\n\n{{< tabs group=\"framework\" >}}\n{{< tab name=\"React\" >}}\n\n```typescript\nexport function App() {\n  const ddClient = createDockerDesktopClient();\n  const [hello, setHello] = useState(\"\");\n\n  useEffect(() => {\n    const run = async () => {\n      let binary = \"hello.sh\";\n      if (ddClient.host.platform === 'win32') {\n        binary = \"hello.cmd\";\n      }\n\n      const result = await ddClient.extension.host?.cli.exec(binary, [\"world\"]);\n      setHello(result?.stdout);\n\n    };\n    run();\n  }, [ddClient]);\n    \n  return (\n    <div>\n      {hello}\n    </div>\n  );\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Vue\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Vue yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Vue)\n> and let us know if you'd like a sample with Vue.\n\n{{< /tab >}}\n{{< tab name=\"Angular\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Angular yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Angular)\n> and let us know if you'd like a sample with Angular.\n\n{{< /tab >}}\n{{< tab name=\"Svelte\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Svelte yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Svelte)\n> and let us know if you'd like a sample with Svelte.\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Configure the metadata file\n\nThe host binaries must be specified in the `metadata.json` file so that Docker Desktop copies them on to the host when installing\nthe extension. Once the extension is uninstalled, the binaries that were copied are removed as well.\n\n```json\n{\n  \"vm\": {\n    ...\n  },\n  \"ui\": {\n    ...\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/hello.sh\"\n          }\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/hello.sh\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/hello.cmd\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nThe `path` must reference the path of the binary inside the container.\n","frontmatter":{"title":"Invoke host binaries","description":"Add invocations to host binaries from the frontend with the extension SDK.","keywords":"Docker, extensions, sdk, build","aliases":["/desktop/extensions-sdk/guides/invoke-host-binaries/"]},"isInternal":false,"tokens":1324,"sizeBytes":5011},{"name":"kubernetes.md","path":"content/manuals/extensions/extensions-sdk/guides/kubernetes.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/guides/kubernetes.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Interacting with Kubernetes from an extension\nlinkTitle: Interacting with Kubernetes\ndescription: How to connect to a Kubernetes cluster from an extension\nkeywords: Docker, Extensions, sdk, Kubernetes\naliases:\n - /desktop/extensions-sdk/dev/kubernetes/\n - /desktop/extensions-sdk/guides/kubernetes/\n---\n\nThe Extensions SDK does not provide any API methods to directly interact with the Docker Desktop managed Kubernetes cluster or any other created using other tools such as KinD. However, this page provides a way for you to use other SDK APIs to interact indirectly with a Kubernetes cluster from your extension.\n\nTo request an API that directly interacts with Docker Desktop-managed Kubernetes, you can upvote [this issue](https://github.com/docker/extensions-sdk/issues/181) in the Extensions SDK GitHub repository.\n\n## Prerequisites\n\n### Turn on Kubernetes\n\nYou can use the built-in Kubernetes in Docker Desktop to start a Kubernetes single-node cluster.\nA `kubeconfig` file is used to configure access to Kubernetes when used in conjunction with the `kubectl` command-line tool, or other clients.\nDocker Desktop conveniently provides the user with a local preconfigured `kubeconfig` file and `kubectl` command within the user’s home area. It is a convenient way to fast-tracking access for those looking to leverage Kubernetes from Docker Desktop.\n\n## Ship the `kubectl` as part of the extension\n\nIf your extension needs to interact with Kubernetes clusters, it is recommended that you include the `kubectl` command line tool as part of your extension. By doing this, users who install your extension get `kubectl` installed on their host.\n\nTo find out how to ship the `kubectl` command line tool for multiple platforms as part of your Docker Extension image, see [Build multi-arch extensions](../extensions/multi-arch.md#adding-multi-arch-binaries).\n\n## Examples\n\nThe following code snippets have been put together in the [Kubernetes Sample Extension](https://github.com/docker/extensions-sdk/tree/main/samples/kubernetes-sample-extension). It shows how to interact with a Kubernetes cluster by shipping the `kubectl` command-line tool.\n\n### Check the Kubernetes API server is reachable\n\nOnce the `kubectl` command-line tool is added to the extension image in the `Dockerfile`, and defined in the `metadata.json`, the Extensions framework deploys `kubectl` to the users' host when the extension is installed.\n\nYou can use the JS API `ddClient.extension.host?.cli.exec` to issue `kubectl` commands to, for instance, check whether the Kubernetes API server is reachable given a specific context:\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"cluster-info\",\n  \"--request-timeout\",\n  \"2s\",\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n### List Kubernetes contexts\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"-o\",\n  \"jsonpath='{.contexts}'\",\n]);\n```\n\n### List Kubernetes namespaces\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"get\",\n  \"namespaces\",\n  \"--no-headers\",\n  \"-o\",\n  'custom-columns=\":metadata.name\"',\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n## Persisting the kubeconfig file\n\nBelow there are different ways to persist and read the `kubeconfig` file from the host filesystem. Users can add, edit, or remove Kubernetes context to the `kubeconfig` file at any time.\n\n> Warning\n>\n> The `kubeconfig` file is very sensitive and if found can give an attacker administrative access to the Kubernetes Cluster.\n\n### Extension's backend container\n\nIf you need your extension to persist the `kubeconfig` file after it's been read, you can have a backend container that exposes an HTTP POST endpoint to store the content of the file either in memory or somewhere within the container filesystem. This way, if the user navigates out of the extension to another part of Docker Desktop and then comes back, you don't need to read the `kubeconfig` file again.\n\n```typescript\nexport const updateKubeconfig = async () => {\n  const kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n    \"config\",\n    \"view\",\n    \"--raw\",\n    \"--minify\",\n    \"--context\",\n    \"docker-desktop\",\n  ]);\n  if (kubeConfig?.stderr) {\n    console.log(\"error\", kubeConfig?.stderr);\n    return false;\n  }\n\n  // call backend container to store the kubeconfig retrieved into the container's memory or filesystem\n  try {\n    await ddClient.extension.vm?.service?.post(\"/store-kube-config\", {\n      data: kubeConfig?.stdout,\n    });\n  } catch (err) {\n    console.log(\"error\", JSON.stringify(err));\n  }\n};\n```\n\n### Docker volume\n\nVolumes are the preferred mechanism for persisting data generated by and used by Docker containers. You can make use of them to persist the `kubeconfig` file.\nBy persisting the `kubeconfig` in a volume you won't need to read the `kubeconfig` file again when the extension pane closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nconst kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"--raw\",\n  \"--minify\",\n  \"--context\",\n  \"docker-desktop\",\n]);\nif (kubeConfig?.stderr) {\n  console.log(\"error\", kubeConfig?.stderr);\n  return false;\n}\n\nawait ddClient.docker.cli.exec(\"run\", [\n  \"--rm\",\n  \"-v\",\n  \"my-vol:/tmp\",\n  \"alpine\",\n  \"/bin/sh\",\n  \"-c\",\n  `\"touch /tmp/.kube/config && echo '${kubeConfig?.stdout}' > /tmp/.kube/config\"`,\n]);\n```\n\n### Extension's `localStorage`\n\n`localStorage` is one of the mechanisms of a browser's web storage. It allows users to save data as key-value pairs in the browser for later use.\n`localStorage` does not clear data when the browser (the extension pane) closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nlocalStorage.setItem(\"kubeconfig\", kubeConfig);\n```\n\n```typescript\nlocalStorage.getItem(\"kubeconfig\");\n```\n","frontmatter":{"title":"Interacting with Kubernetes from an extension","linkTitle":"Interacting with Kubernetes","description":"How to connect to a Kubernetes cluster from an extension","keywords":"Docker, Extensions, sdk, Kubernetes","aliases":["/desktop/extensions-sdk/dev/kubernetes/","/desktop/extensions-sdk/guides/kubernetes/"]},"isInternal":false,"tokens":1324,"sizeBytes":6018},{"name":"oauth2-flow.md","path":"content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Authentication\ndescription: Docker extension OAuth 2.0 flow\nkeywords: Docker, extensions, sdk, OAuth 2.0\naliases:\n - /desktop/extensions-sdk/dev/oauth2-flow/\n - /desktop/extensions-sdk/guides/oauth2-flow/\n---\n\n> [!NOTE]\n>\n> This page assumes that you already have an Identity Provider (IdP), such as Google, Entra ID (formerly Azure AD) or Okta, which handles the authentication process and returns an access token.\n\nLearn how you can let users authenticate from your extension using OAuth 2.0 via a web browser, and return to your extension.\n\nIn OAuth 2.0, the term \"grant type\" refers to the way an application gets an access token. Although OAuth 2.0 defines several grant types, this page only describes how to authorize users from your extension using the Authorization Code grant type.\n\n## Authorization code grant flow\n\nThe Authorization Code grant type is used by confidential and public clients to exchange an authorization code for an access token.\n\nAfter the user returns to the client via the redirect URL, the application gets the authorization code from the URL and uses it to request an access token.\n\n![Flow for OAuth 2.0](images/oauth.png)\n\nThe image above shows that:\n\n- The Docker extension asks the user to authorize access to their data.\n- If the user grants access, the extension then requests an access token from the service provider, passing the access grant from the user and authentication details to identify the client.\n- The service provider then validates these details and returns an access token.\n- The extension uses the access token to request the user data with the service provider.\n\n### OAuth 2.0 terminology\n\n- Auth URL: The endpoint for the API provider authorization server, to retrieve the auth code.\n- Redirect URI: The client application callback URL to redirect to after auth. This must be registered with the API provider.\n\nOnce the user enters the username and password, they're successfully authenticated.\n\n## Open a browser page to authenticate the user\n\nFrom the extension UI, you can provide a button that, when selected, opens a new window in a browser to authenticate the user.\n\nUse the [ddClient.host.openExternal](../dev/api/dashboard.md#open-a-url) API to open a browser to the auth URL. For\nexample:\n\n```typescript\nwindow.ddClient.openExternal(\"https://authorization-server.com/authorize?\n  response_type=code\n  &client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n  &redirect_uri=${REDIRECT_URI});\n```\n\n## Get the authorization code and access token\n\nYou can get the authorization code from the extension UI by listing `docker-desktop://dashboard/extension-tab?extensionId=awesome/my-extension` as the `redirect_uri` in the OAuth app you're using and concatenating the authorization code as a query parameter. The extension UI code will then be able to read the corresponding code query-param.\n\n> [!IMPORTANT]\n>\n> Using this feature requires the extension SDK 0.3.3 in Docker Desktop. You need to ensure that the required SDK version for your extension set with `com.docker.desktop.extension.api.version` in [image labels](../extensions/labels.md) is higher than 0.3.3.\n\n#### Authorization\n\nThis step is where the user enters their credentials in the browser. After the authorization is complete, the user is redirected back to your extension user interface, and the extension UI code can consume the authorization code that's part of the query parameters in the URL.\n\n#### Exchange the Authorization Code\n\nNext, you exchange the authorization code for an access token.\n\nThe extension must send a `POST` request to the 0Auth authorization server with the following parameters:\n\n```text\nPOST https://authorization-server.com/token\n&client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n&client_secret=YABbyHQShPeO1T3NDQZP8q5m3Jpb_UPNmIzqhLDCScSnRyVG\n&redirect_uri=${REDIRECT_URI}\n&code=N949tDLuf9ai_DaOKyuFBXStCNMQzuQbtC1QbvLv-AXqPJ_f\n```\n\n> [!NOTE]\n>\n> The client's credentials are included in the `POST` query params in this example. OAuth authorization servers may require that the credentials are sent as a HTTP Basic Authentication header or might support different formats. See your OAuth provider docs for details.\n\n### Store the access token\n\nThe Docker Extensions SDK doesn't provide a specific mechanism to store secrets.\n\nIt's highly recommended that you use an external source of storage to store the access token.\n\n> [!NOTE]\n>\n> The user interface Local Storage is isolated between extensions (an extension can't access another extension's local storage), and each extension's local storage gets deleted when users uninstall an extension.\n\n## What's next\n\nLearn how to [publish and distribute your extension](../extensions/_index.md)\n","frontmatter":{"title":"Authentication","description":"Docker extension OAuth 2.0 flow","keywords":"Docker, extensions, sdk, OAuth 2.0","aliases":["/desktop/extensions-sdk/dev/oauth2-flow/","/desktop/extensions-sdk/guides/oauth2-flow/"]},"isInternal":false,"tokens":1035,"sizeBytes":4679},{"name":"use-docker-socket-from-backend.md","path":"content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use the Docker socket from the extension backend\nlinkTitle: Use the Docker socket\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/guides/use-docker-socket-from-backend/\n---\n\nExtensions can invoke Docker commands directly from the frontend with the SDK. \n\nIn some cases, it is useful to also interact with Docker Engine from the backend. \n\nExtension backend containers can mount the Docker socket and use it to\ninteract with Docker Engine from the extension backend logic. Learn more about the [Docker Engine socket](/reference/cli/dockerd/#examples)\n\nHowever, when mounting the Docker socket from an extension container that lives in the Desktop virtual machine, you want\nto mount the Docker socket from inside the VM, and not mount `/var/run/docker.sock` from the host filesystem (using\nthe Docker socket from the host can lead to permission issues in containers).\n\nIn order to do so, you can use `/var/run/docker.sock.raw`. Docker Desktop mounts the socket that lives in the Desktop VM, and not from the host.\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /var/run/docker.sock.raw:/var/run/docker.sock\n```\n","frontmatter":{"title":"Use the Docker socket from the extension backend","linkTitle":"Use the Docker socket","description":"Docker extension metadata","keywords":"Docker, extensions, sdk, metadata","aliases":["/desktop/extensions-sdk/guides/use-docker-socket-from-backend/"]},"isInternal":false,"tokens":263,"sizeBytes":1235},{"name":"process.md","path":"content/manuals/extensions/extensions-sdk/process.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/process.md","title":"Extensions-sdk Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Understand the process of creating an extension.\ntitle: The build and publish process\nkeyword: Docker Extensions, sdk, build, create, publish\naliases:\n - /desktop/extensions-sdk/process/\nweight: 10\n---\n\nThis documentation is structured so that it matches the steps you need to take when creating your extension. \n\nThere are two main parts to creating a Docker extension:\n\n1. Build the foundations\n2. Publish the extension\n\n> [!NOTE]\n>\n> You do not need to pay to create a Docker extension. The [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use. Anyone can create new extensions and share them without constraints.\n> \n> There is also no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n\n## Part one: Build the foundations\n\nThe build process consists of:\n\n- Installing the latest version of Docker Desktop.\n- Setting up the directory with files, including the extension’s source code and the required extension-specific files.\n- Creating the `Dockerfile` to build, publish, and run your extension in Docker Desktop.\n- Configuring the metadata file which is required at the root of the image filesystem.\n- Building and installing the extension.\n\nFor further inspiration, see the other examples in the [samples folder](https://github.com/docker/extensions-sdk/tree/main/samples).\n\n> [!TIP]\n>\n> Whilst creating your extension, make sure you follow the [design](design/design-guidelines.md) and [UI styling](design/_index.md) guidelines to ensure visual consistency and [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n## Part two: Publish and distribute your extension\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nDocker Desktop displays published extensions in the Extensions Marketplace. The Extensions Marketplace is a curated space where developers can discover extensions to improve their developer experience and upload their own extension to share with the world.\n\nIf you want your extension published in the Marketplace, read the [publish documentation](extensions/publish.md).\n\n{{% include \"extensions-form.md\" %}}\n\n## What’s next?\n\nIf you want to get up and running with creating a Docker Extension, see the [Quickstart guide](quickstart.md).\n\nAlternatively, get started with reading the \"Part one: Build\" section for more in-depth information about each step of the extension creation process.\n\nFor an in-depth tutorial of the entire build process, we recommend the following video walkthrough from DockerCon 2022.\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Yv7OG-EGJsg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n","frontmatter":{"description":"Understand the process of creating an extension.","title":"The build and publish process","keyword":"Docker Extensions, sdk, build, create, publish","aliases":["/desktop/extensions-sdk/process/"],"weight":10},"isInternal":false,"tokens":639,"sizeBytes":3120},{"name":"quickstart.md","path":"content/manuals/extensions/extensions-sdk/quickstart.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/extensions-sdk/quickstart.md","title":"Extensions-sdk Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Quickstart\ndescription: Guide on how to build an extension quickly\nkeywords: quickstart, extensions\naliases:\n - desktop/extensions-sdk/tutorials/initialize/\n - /desktop/extensions-sdk/quickstart/\nweight: 20\n---\n\nFollow this guide to get started with creating a basic Docker extension. The Quickstart guide automatically generates boilerplate files for you.\n\n## Prerequisites\n\n- [Docker Desktop](/manuals/desktop/release-notes.md)\n- [NodeJS](https://nodejs.org/)\n- [Go](https://go.dev/dl/)\n\n> [!NOTE]\n>\n> NodeJS and Go are only required when you follow the quickstart guide to create an extension. It uses the `docker extension init` command to automatically generate boilerplate files. This command uses a template based on a ReactJS and Go application.\n\nIn Docker Desktop settings, ensure you can install the extension you're developing. You may need to navigate to the **Extensions** tab in Docker Desktop settings and deselect **Allow only extensions distributed through the Docker Marketplace**.\n\n## Step one: Set up your directory\n\nTo set up your directory, use the `init` subcommand and provide a name for your extension.\n\n```console\n$ docker extension init <my-extension>\n```\n\nThe command asks a series of questions about your extension, such as its name, a description, and the name of your Hub repository. This helps the CLI generate a set of boilerplate files for you to get started. It stores the boilerplate files in the `my-extension` directory.\n\nThe automatically generated extension contains:\n\n- A Go backend service in the `backend` folder that listens on a socket. It has one endpoint `/hello` that returns a JSON payload.\n- A React frontend in the `frontend` folder that can call the backend and output the backend’s response.\n\nFor more information and guidelines on building the UI, see the [Design and UI styling section](design/design-guidelines.md).\n\n## Step two: Build the extension\n\nTo build the extension, move into the newly created directory and run:\n\n```console\n$ docker build -t <name-of-your-extension> .\n```\n\n`docker build` builds the extension and generates an image named the same as the chosen hub repository. For example, if you typed `john/my-extension` as the answer to the following question:\n\n```console\n? Hub repository (eg. namespace/repository on hub): john/my-extension`\n```\n\nThe `docker build` generates an image with name `john/my-extension`.\n\n## Step three: Install and preview the extension\n\nTo install the extension in Docker Desktop, run:\n\n```console\n$ docker extension install <name-of-your-extension>\n```\n\nTo preview the extension in Docker Desktop, once the installation is complete and you should\nsee a **Quickstart** item underneath the **Extensions** menu. Selecting this item opens the extension's frontend.\n\n> [!TIP]\n>\n> During UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\n> extension. See [Preview whilst developing the UI](dev/test-debug.md#hot-reloading-whilst-developing-the-ui) for more information.\n\nYou may also want to inspect the containers that belong to the extension. By default, extension containers are\nhidden from the Docker Dashboard. You can change this in **Settings**, see\n[how to show extension containers](dev/test-debug.md#show-the-extension-containers) for more information.\n\n## Step four: Submit and publish your extension to the Marketplace\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nIf you want to make your extension available to all Docker Desktop users, you can submit it for publication in the Marketplace. For more information, see [Publish](extensions/_index.md).\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build a more [advanced frontend](build/frontend-extension-tutorial.md) for your extension.\n- Learn how to [test and debug](dev/test-debug.md) your extension.\n- Learn how to [setup CI for your extension](dev/continuous-integration.md).\n- Learn more about extensions [architecture](architecture/_index.md).\n- Learn more about [designing the UI](design/design-guidelines.md).\n","frontmatter":{"title":"Quickstart","description":"Guide on how to build an extension quickly","keywords":"quickstart, extensions","aliases":["desktop/extensions-sdk/tutorials/initialize/","/desktop/extensions-sdk/quickstart/"],"weight":20},"isInternal":false,"tokens":916,"sizeBytes":4366},{"name":"marketplace.md","path":"content/manuals/extensions/marketplace.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/marketplace.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace\ntitle: Marketplace extensions\nweight: 10\naliases:\n - /desktop/extensions/marketplace/\n---\n\nThere are two types of extensions available in the Extensions Marketplace:\n- Docker-reviewed extensions\n- Self-published extensions\n\nDocker-reviewed extensions are manually reviewed by the Docker Extensions team to ensure an extra level of trust\nand quality. They appear as **Reviewed** in the Marketplace.\n\nSelf-published extensions are autonomously published by extension developers and go through an automated validation process. They appear as **Not reviewed** in the Marketplace.\n\n> [!IMPORTANT]\n>\n> Marketplace extensions are reviewed by Docker, but are not subject to a full security audit. Extensions run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Only install extensions from publishers you trust.\n\n## Install an extension\n\n> [!NOTE]\n>\n> For some extensions, a separate account needs to be created before use.\n\nTo install an extension:\n\n1. Open Docker Desktop.\n2. From the Docker Desktop Dashboard, select the **Extensions** tab.\n   The Extensions Marketplace opens on the **Browse** tab.\n3. Browse the available extensions.\n   You can sort the list of extensions by **Recently added**, **Most installed**, or alphabetically. Alternatively, use the **Content** or **Categories** drop-down menu to search for extensions by whether they have been reviewed or not, or by category.\n4. Choose an extension and select **Install**.\n\nFrom here, you can select **Open** to access the extension or install additional extensions. The extension also appears in the left-hand menu and in the **Manage** tab.\n\n## Update an extension\n\nYou can update any extension outside of Docker Desktop releases. To update an extension to the latest version, navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n\nThe **Manage** tab displays with all your installed extensions. If an extension has a new version available, it displays an **Update** button.\n\n\n## Uninstall an extension\n\nYou can uninstall an extension at any time.\n\n> [!NOTE]\n>\n> Any data used by the extension that's stored in a volume must be manually deleted.\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the ellipsis to the right of extension you want to uninstall.\n3. Select **Uninstall**.\n","frontmatter":{"description":"Extensions","keywords":"Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace","title":"Marketplace extensions","weight":10,"aliases":["/desktop/extensions/marketplace/"]},"isInternal":false,"tokens":513,"sizeBytes":2538},{"name":"non-marketplace.md","path":"content/manuals/extensions/non-marketplace.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/non-marketplace.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows,\ntitle: Non-marketplace extensions\nweight: 20\n---\n\n## Install an extension not available in the Marketplace\n\n> [!WARNING]\n>\n> Extensions installed outside the Marketplace have not gone through Docker's review process. Like all Docker extensions, they run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Install only if you trust the publisher and have verified the source.\n\nThe Extensions Marketplace is the trusted and official place to install extensions from within Docker Desktop. These extensions have gone through a review process by Docker. However, other extensions can also be installed in Docker Desktop if you trust the extension author.\n\nGiven the nature of a Docker Extension (i.e. a Docker image) you can find other places where users have their extension's source code published. For example on GitHub, GitLab or even hosted in image registries like DockerHub or GHCR.\nYou can install an extension that has been developed by the community or internally at your company from a teammate. You are not limited to installing extensions just from the Marketplace.\n\n> [!NOTE]\n>\n> Ensure the option **Allow only extensions distributed through the Docker Marketplace** is disabled. Otherwise, this prevents any extension not listed in the Marketplace, via the Extension SDK tools from, being installed.\n> You can change this option in **Settings**. \n\nTo install an extension which is not present in the Marketplace, you can use the Extensions CLI that is bundled with Docker Desktop.\n\nIn a terminal, type `docker extension install IMAGE[:TAG]` to install an extension by its image reference and optionally a tag. Use the `-f` or `--force` flag to avoid interactive confirmation.\n\nGo to the Docker Desktop Dashboard to see the new extension installed.\n\n## List installed extensions\n\nRegardless whether the extension was installed from the Marketplace or manually by using the Extensions CLI, you can use the `docker extension ls` command to display the list of extensions installed.\nAs part of the output you'll see the extension ID, the provider, version, the title and whether it runs a backend container or has deployed binaries to the host, for example:\n\n```console\n$ docker extension ls\nID                  PROVIDER            VERSION             UI                    VM                  HOST\njohn/my-extension   John                latest              1 tab(My-Extension)   Running(1)          -\n```\n\nGo to the Docker Desktop Dashboard, select **Add Extensions** and on the **Managed** tab to see the new extension installed.\nNotice that an `UNPUBLISHED` label displays which indicates that the extension has not been installed from the Marketplace.\n\n## Update an extension \n\nTo update an extension which isn't present in the Marketplace, in a terminal type `docker extension update IMAGE[:TAG]` where the `TAG` should be different from the extension that's already installed.\n\nFor instance, if you installed an extension with `docker extension install john/my-extension:0.0.1`, you can update it by running `docker extension update john/my-extension:0.0.2`.\nGo to the Docker Desktop Dashboard to see the new extension updated.\n\n> [!NOTE]\n>\n> Extensions that aren't installed through the Marketplace don't receive update notifications from Docker Desktop.\n\n## Uninstall an extension\n\nTo uninstall an extension which is not present in the Marketplace, you can either navigate to the **Managed** tab in the Marketplace and select the **Uninstall** button, or from a terminal type `docker extension uninstall IMAGE[:TAG]`.\n","frontmatter":{"description":"Extensions","keywords":"Docker Extensions, Docker Desktop, Linux, Mac, Windows,","title":"Non-marketplace extensions","weight":20},"isInternal":false,"tokens":727,"sizeBytes":3705},{"name":"private-marketplace.md","path":"content/manuals/extensions/private-marketplace.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/private-marketplace.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: How to configure and use Docker Extensions' private marketplace\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace, private, security, admin\ntitle: Configure a private marketplace for extensions\ntags: [admin]\nlinkTitle: Configure a private marketplace\nweight: 30\n---\n\n{{< summary-bar feature_name=\"Private marketplace\" >}}\n\nLearn how to configure and set up a private marketplace with a curated list of extensions for your Docker Desktop users.\n\nDocker Extensions' private marketplace is designed specifically for organizations who don’t give developers root access to their machines. It makes use of [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) so administrators have complete control over the private marketplace.\n\n## Prerequisites\n\n- [Download and install Docker Desktop](https://docs.docker.com/desktop/release-notes/).\n- You must be an administrator for your organization.\n- You have the ability to push the `extension-marketplace` folder and `admin-settings.json` file to the locations specified below through device management software such as [Jamf](https://www.jamf.com/).\n\n## Step one: Initialize the private marketplace\n\n1. Create a folder locally for the content that will be deployed to your developers’ machines:\n\n   ```console\n   $ mkdir my-marketplace\n   $ cd my-marketplace\n   ```\n\n2. Initialize the configuration files for your marketplace:\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ /Applications/Docker.app/Contents/Resources/bin/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin init\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ /opt/docker-desktop/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\nThis creates 2 files:\n\n- `admin-settings.json`, which activates the private marketplace feature once it’s applied to Docker Desktop on your developers’ machines.\n- `extensions.txt`, which determines which extensions to list in your private marketplace.\n\n> [!IMPORTANT]\n>\n> If your org is using [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will not need the `admin-settings.json` file. Delete the generated file and keep only the `extensions.txt` file.\n\n## Step two: Set the behaviour\n\nThe generated `admin-settings.json` file includes various settings you can modify.\n\n> [!IMPORTANT]\n>\n> If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will define the same settings in Docker Home instead of the `admin-settings.json` file.\n\nEach setting has a `value` that you can set, including a `locked` field that lets you lock the setting and make it unchangeable by your developers.\n\n- `extensionsEnabled` enables Docker Extensions.\n- `extensionsPrivateMarketplace` activates the private marketplace and ensures Docker Desktop connects to content defined and controlled by the administrator instead of the public Docker marketplace.\n- `onlyMarketplaceExtensions` allows or blocks developers from installing other extensions by using the command line. Teams developing new extensions must have this setting unlocked (`\"locked\": false`) to install and test extensions being developed.\n- `extensionsPrivateMarketplaceAdminContactURL` defines a contact link for developers to request new extensions in the private marketplace. If `value` is empty then no link is shown to your developers on Docker Desktop, otherwise this can be either an HTTP link or a “mailto:” link. For example,\n\n  ```json\n  \"extensionsPrivateMarketplaceAdminContactURL\": {\n    \"locked\": true,\n    \"value\": \"mailto:admin@acme.com\"\n  }\n  ```\n\nTo find out more information about the `admin-settings.json` file, see [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md).\n\n## Step three: List allowed extensions\n\nThe generated `extensions.txt` file defines the list of extensions that are available in your private marketplace.\n\nEach line in the file is an allowed extension and follows the format of `org/repo:tag`.\n\nFor example, if you want to permit the Disk Usage extension you would enter the following into your `extensions.txt` file:\n\n```console\ndocker/disk-usage-extension:0.2.8\n```\n\nIf no tag is provided, the latest tag available for the image is used. You can also comment out lines with `#` so the extension is ignored.\n\nThis list can include different types of extension images:\n\n- Extensions from the public marketplace or any public image stored in Docker Hub.\n- Extension images stored in Docker Hub as private images. Developers need to be signed in and have pull access to these images.\n- Extension images stored in a private registry. Developers need to be signed in and have pull access to these images.\n\n> [!IMPORTANT]\n>\n> Your developers can only install the version of the extension that you’ve listed.\n\n## Step four: Generate the private marketplace\n\nOnce the list in `extensions.txt` is ready, you can generate the marketplace:\n\n{{< tabs group=\"os_version\" >}}\n{{< tab name=\"Mac\" >}}\n\n```console\n$ /Applications/Docker.app/Contents/Resources/bin/extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\n```console\n# For all-user installations\n$ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin generate\n\n# For per-user installations\n$ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Linux\" >}}\n\n```console\n$ /opt/docker-desktop/extension-admin generate\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThis creates an `extension-marketplace` directory and downloads the marketplace metadata for all the allowed extensions.\n\nThe marketplace content is generated from extension image information as image labels, which is the [same format as public extensions](extensions-sdk/extensions/labels.md). It includes the extension title, description, screenshots, links, etc.\n\n## Step five: Test the private marketplace setup\n\nIt's recommended that you try the private marketplace on your Docker Desktop installation.\n\n1. Run the following command in your terminal. This command automatically copies the generated files to the location where Docker Desktop reads the configuration files. Depending on your operating system, the location is:\n\n    - Mac: `/Library/Application\\ Support/com.docker.docker`\n    - Windows: `C:\\ProgramData\\DockerDesktop`\n    - Linux: `/usr/share/docker-desktop`\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ sudo /Applications/Docker.app/Contents/Resources/bin/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows (run as admin)\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin apply\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ sudo /opt/docker-desktop/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\n2. Quit and re-open Docker Desktop. \n3. Sign in with a Docker account.\n\n> [!IMPORTANT]\n>\n> > If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), in Docker Desktop 4.59 and earlier, you must manually delete the `admin-settings.json` file created in the target folder by the `apply` command before step 2. In Docker Desktop 4.60 and later, this step is no longer necessary. \n\nWhen you select the **Extensions** tab, you should see the private marketplace listing only the extensions you have allowed in `extensions.txt`.\n\n![Extensions Private Marketplace](/assets/images/extensions-private-marketplace.webp)\n\n## Step six: Distribute the private marketplace\n\nOnce you’ve confirmed that the private marketplace configuration works, the final step is to distribute the files to the developers’ machines with the MDM software your organization uses. For example, [Jamf](https://www.jamf.com/).\n\nThe files to distribute are:\n* `admin-settings.json` (except if your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md))\n* the entire `extension-marketplace` folder and its subfolders\n\nThese files must be placed on developer's machines. Depending on your operating system, the target location is (as mentioned above):\n\n- Mac: `/Library/Application\\ Support/com.docker.docker`\n- Windows: `C:\\ProgramData\\DockerDesktop`\n- Linux: `/usr/share/docker-desktop`\n\nMake sure your developers are signed in to Docker Desktop in order for the private marketplace configuration to take effect. As an administrator, you should [enforce sign-in](/manuals/enterprise/security/enforce-sign-in/_index.md).\n\n## Feedback\n\nGive feedback or report any bugs you may find by emailing `extensions@docker.com`.\n","frontmatter":{"description":"How to configure and use Docker Extensions' private marketplace","keywords":"Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace, private, security, admin","title":"Configure a private marketplace for extensions","tags":["admin"],"linkTitle":"Configure a private marketplace","weight":30},"isInternal":false,"tokens":2059,"sizeBytes":9409},{"name":"settings-feedback.md","path":"content/manuals/extensions/settings-feedback.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/extensions/settings-feedback.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, feedback\ntitle: Settings and feedback for Docker Extensions\nlinkTitle: Settings and feedback\nweight: 40\n---\n\n## Settings\n\n### Turn on or turn off extensions\n\nDocker Extensions is switched off by default. To change your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Enable Docker Extensions**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> If you are an [organization owner](/manuals/admin/organization/manage/manage-a-team.md#what-is-an-organization-owner), you can turn off extensions for your users. Open the `settings-store.json` file, and set `\"extensionsEnabled\"` to `false`.\n> The `settings-store.json` file is located at:\n>   - `~/Library/Group Containers/group.com.docker/settings-store.json` on Mac\n>   - `C:\\Users\\[USERNAME]\\AppData\\Roaming\\Docker\\settings-store.json` on Windows\n>\n> This can also be done with [Hardened Docker Desktop](/manuals/enterprise/security/hardened-desktop/_index.md)\n\n### Turn on or turn off extensions not available in the Marketplace\n\nYou can install extensions through the Marketplace or through the Extensions SDK tools. You can choose to only allow published extensions. These are extensions that have been reviewed and published in the Extensions Marketplace.\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Allow only extensions distributed through the Docker Marketplace**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n### See containers created by extensions\n\nBy default, containers created by extensions are hidden from the list of containers in the Docker Desktop Dashboard and the Docker CLI. To make them visible\nupdate your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Show Docker Extensions system containers**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> Enabling extensions doesn't use computer resources (CPU / Memory) by itself.\n>\n> Specific extensions might use computer resources, depending on the features and implementation of each extension, but there is no reserved resources or usage cost associated with enabling extensions.\n\n## Submit feedback\n\nFeedback can be given to an extension author through a dedicated Slack channel or GitHub. To submit feedback about a particular extension:\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the extension you want to provide feedback on. \n3. Scroll down to the bottom of the extension's description and, depending on the \nextension, select:\n    - Support\n    - Slack\n    - Issues. You'll be sent to a page outside of Docker Desktop to submit your feedback.\n\nIf an extension doesn't provide a way for you to give feedback, contact us and we'll pass on the feedback for you. To provide feedback, select the **Give feedback** to the right of **Extensions Marketplace**.\n","frontmatter":{"description":"Extensions","keywords":"Docker Extensions, Docker Desktop, Linux, Mac, Windows, feedback","title":"Settings and feedback for Docker Extensions","linkTitle":"Settings and feedback","weight":40},"isInternal":false,"tokens":680,"sizeBytes":3184},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"---\ntitle: \"Agents\"\ndescription: \"Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents.\"\nkeywords: docker agent, ai agents, concepts, agents\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/agents/\n---\n\n_Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents._\n\n## What is an Agent?\n\nAn agent in Docker Agent is defined by:\n\n- **Model** — The AI model powering it (e.g., Claude, GPT-5, Gemini). See [Models](../models/index.md).\n- **Description** — A brief summary of what the agent does (used by other agents for delegation)\n- **Instruction** — The system prompt that defines the agent's behavior and personality\n- **Tools** — Capabilities like filesystem access, shell commands, or external APIs\n- **Sub-agents** — Other agents it can delegate tasks to\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: |\n      You are an expert developer. Write clean, efficient code\n      and explain your reasoning step by step.\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n```\n\n## The Root Agent\n\nEvery Docker Agent configuration has a **root agent** — the entry point that receives user messages. In a single-agent setup, this is the only agent. In a multi-agent setup, the root agent acts as a coordinator, delegating tasks to specialized sub-agents.\n\n> [!NOTE]\n> **Naming**\n>\n> The first agent defined in your YAML (or the one named `root`) is the root agent by default. You can also specify which agent to start with using `docker agent run config.yaml -a agent_name`.\n\n## Agent Properties\n\n| Property               | Type    | Required | Description                                                    |\n| ---------------------- | ------- | -------- | -------------------------------------------------------------- |\n| `model`                | string  | ✓        | Model reference (inline like `openai/gpt-5` or a named model) |\n| `description`          | string  | ✓        | What the agent does — used by other agents for delegation      |\n| `instruction`          | string  | ✓        | System prompt defining behavior                                |\n| `toolsets`             | array   | ✗        | List of tool configurations                                    |\n| `sub_agents`           | array   | ✗        | Names of agents this agent can delegate to                     |\n| `fallback`             | object  | ✗        | Fallback model configuration for resilience                    |\n| `add_date`             | boolean | ✗        | Include current date in context                                |\n| `add_environment_info` | boolean | ✗        | Include OS, working directory, git info in context             |\n| `max_iterations`       | int     | ✗        | Max tool-calling loops (default: unlimited)                    |\n| `commands`             | object  | ✗        | Named prompts callable via `/command`                          |\n| `skills`               | boolean \\| list | ✗    | Enable skill discovery and loading. `true` = `[\"local\"]`; list values may combine `\"local\"` with remote skill-server URLs. |\n\n## Model Fallbacks\n\nAgents can automatically fail over to alternative models when the primary model is unavailable:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2 # retries per model for 5xx errors\n      cooldown: 1m # stick with fallback after 429\n```\n\n## Named Commands\n\nDefine reusable prompts that can be invoked as commands:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a helpful assistant.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\n```bash\n# Run a named command\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n```\n\nCommands support environment variable interpolation using JavaScript template literal syntax. Undefined variables expand to empty strings.\n\n## Default Agent\n\nRunning `docker agent run` without a config argument uses `docker-agent.yaml`, `docker-agent.yml`, or `docker-agent.hcl` from the current directory when present. Otherwise, it uses a capable built-in default agent for quick tasks without needing any configuration.\n\n```bash\n# Use the project config or built-in default agent\n$ docker agent run\n\n# Override the default with an alias\n$ docker agent alias add default /path/to/my-agent.yaml\n$ docker agent run  # now runs your custom agent\n```\n\n> [!TIP]\n> **See also**\n>\n> For reusable task-specific instructions, see [Skills](../../features/skills/index.md). For multi-agent patterns, see [Multi-Agent](../multi-agent/index.md). For full config reference, see [Agent Config](../../configuration/agents/index.md).\n","frontmatter":{"title":"Agents","description":"Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents.","keywords":"docker agent, ai agents, concepts, agents","weight":10,"canonical":"https://docs.docker.com/ai/docker-agent/concepts/agents/"},"isInternal":false,"tokens":1118,"sizeBytes":5053},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"---\ntitle: \"Agent Configuration\"\ndescription: \"Complete reference for defining agents in your YAML configuration.\"\nkeywords: docker agent, ai agents, configuration, yaml, agent configuration\nlinkTitle: \"Agent Config\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/agents/\n---\n\n_Complete reference for defining agents in your YAML configuration._\n\nA configuration must define at least one agent under `agents`.\n\n## Full Schema\n\n<!-- yaml-lint:skip -->\n```yaml\nagents:\n  agent_name:\n    model: string # Required: model reference\n    description: string # Required: what this agent does\n    instruction: string # Required (unless instruction_file): system prompt\n    instruction_file: string | [list] # Optional: load the system prompt from one or more files relative to this config (mutually exclusive with instruction)\n    sub_agents: [list] # Optional: local or external sub-agent references\n    toolsets: [list] # Optional: tool configurations (use `type: rag` for RAG sources)\n    fallback: # Optional: fallback config\n      models: [list]\n      retries: 2\n      cooldown: 1m\n    add_date: boolean # Optional: add date to context\n    add_environment_info: boolean # Optional: add env info to context\n    add_prompt_files: [list] # Optional: include additional prompt files\n    add_description_parameter: bool # Optional: add description to tool schema\n    redact_secrets: boolean # Optional: scrub detected secrets out of tool args, outgoing chat messages, and tool output\n    code_mode_tools: boolean # Optional: let the agent write JavaScript to orchestrate tool calls (see Code Mode)\n    max_iterations: int # Optional: max tool-calling loops\n    max_consecutive_tool_calls: int # Optional: max identical consecutive tool calls\n    max_old_tool_call_tokens: int # Optional: token budget for old tool call content (disabled unless positive)\n    max_tool_result_tokens: int # Optional: per-tool-result token cap with middle-out truncation (disabled unless positive)\n    num_history_items: int # Optional: limit conversation history\n    session_compaction: boolean # Optional: disable automatic session compaction (default: true)\n    compaction_threshold: float # Optional: context-window fraction that triggers auto-compaction (0–1, default: 0.9)\n    compaction_model: string # Optional: model used for session-compaction (summary generation)\n    use_toolsets: [list] # Optional: names of top-level toolsets to merge into this agent\n    readonly: boolean # Optional: restrict all toolsets to read-only tools only\n    skills: boolean | [list] # Optional: enable skill discovery (true/false or list of names and/or sources)\n    use_commands: [list] # Optional: names of top-level commands groups to merge into this agent\n    use_skills: [list] # Optional: names of top-level skills groups to merge into this agent\n    commands: # Optional: named prompts\n      name: \"prompt text\" # or {instruction: \"prompt\", agent: \"sub_agent_name\"} or {url: \"https://...\"} (TUI only)\n    welcome_message: string # Optional: message shown at session start\n    handoffs: [list] # Optional: agent names this agent can hand off to\n    force_handoff: string # Optional: agent that always receives the conversation when this agent stops\n    hooks: # Optional: lifecycle hooks\n      pre_tool_use: [list]\n      tool_response_transform: [list]\n      post_tool_use: [list]\n      session_start: [list]\n      session_end: [list]\n      on_user_input: [list]\n      stop: [list]\n      notification: [list]\n    structured_output: # Optional: constrain output format\n      name: string\n      schema: object\n    cache: # Optional: response cache (skip the model on repeat questions)\n      enabled: boolean\n      case_sensitive: boolean\n      trim_spaces: boolean\n      path: string\n    harness: # Optional: delegate to an external coding CLI (Claude Code, Codex, opencode, pi)\n      type: string # Required: claude-code | codex | opencode | pi\n      model: string # Optional: model override forwarded to the CLI (omit for the CLI's own default)\n      effort: string # claude-code only: low | medium | high | xhigh | max (omit for the Claude Code default)\n      agent: string # opencode only: agent profile name\n      thinking: boolean # opencode only: enable extended thinking\n```\n\n> [!TIP]\n> **See also**\n>\n> For model parameters, see [Model Config](../models/index.md). For tool details, see [Tool Config](../tools/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n\n## Properties Reference\n\n| Property                    | Type    | Required | Description                                                                                                                                                                   |\n| --------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `model`                     | string  | ✓        | Model reference. Either inline (`openai/gpt-5`) or a named model from the `models` section.                                                                              |\n| `description`               | string  | ✓        | Brief description of the agent's purpose. Used by coordinators to decide delegation.                                                                                          |\n| `instruction`               | string  | ✓        | System prompt that defines the agent's behavior, personality, and constraints. Required unless `instruction_file` is set.                                                      |\n| `instruction_file`          | string \\| array  | ✗        | Path(s) to a file or files (relative to the config file's directory) whose contents become the agent's instruction, loaded at startup. Accepts a single path or a list; multiple files are concatenated in order, separated by a blank line. Mutually exclusive with `instruction`. Each path must be a local relative path inside the config directory (absolute paths and `..` traversal are rejected). Only supported for local file-based configs, not OCI/URL sources. See [External Instruction Files](#external-instruction-files) below. |\n| `sub_agents`                | array   | ✗        | List of agent names or external OCI references this agent can delegate to. Supports local agents, registry references (e.g., `myorg/agent:tag`), and named references (`name:reference`). Automatically enables the `transfer_task` tool. Pin external OCI references to a digest (`name@sha256:…`) to skip the per-run registry lookup that tag references incur. See [External Sub-Agents](../../concepts/multi-agent/index.md#external-sub-agents-from-registries). |\n| `toolsets`                  | array   | ✗        | List of tool configurations. See [Tool Config](../tools/index.md).                                                                                                        |\n| `fallback`                  | object  | ✗        | Automatic model failover configuration.                                                                                                                                       |\n| `add_date`                  | boolean | ✗        | When `true`, injects the current date into the agent's context.                                                                                                               |\n| `add_environment_info`      | boolean | ✗        | When `true`, injects working directory, OS, CPU architecture, and git info into context.                                                                                      |\n| `add_prompt_files`          | array   | ✗        | List of file paths whose contents are appended to the system prompt. Useful for including coding standards, guidelines, or additional context.                                |\n| `add_description_parameter` | boolean | ✗        | When `true`, adds agent descriptions as a parameter in tool schemas. Helps with tool selection in multi-agent scenarios.                                                      |\n| `redact_secrets`            | boolean | ✗        | When `true`, scrubs detected secrets (API keys, tokens, private keys, etc.) out of tool-call arguments, outgoing chat messages, and tool output before they reach a tool, the model, or downstream consumers. See [Redacting Secrets](#redacting-secrets) below.   |\n| `code_mode_tools`           | boolean | ✗        | When `true`, replaces the agent's individual tools with a single tool that runs a JavaScript script calling as many of them as needed in one turn. See [Code Mode](../../features/code-mode/index.md). |\n| `max_iterations`            | int     | ✗        | Maximum number of tool-calling loops. Default: unlimited (0). Set this to prevent infinite loops.                                                                             |\n| `max_consecutive_tool_calls` | int     | ✗        | Maximum consecutive identical tool calls before the agent is terminated, preventing degenerate loops. Default: `5`.                                                          |\n| `max_old_tool_call_tokens`  | int     | ✗        | Maximum number of tokens to keep from old tool call arguments and results. Older tool calls beyond this budget have their content replaced with a placeholder, saving context space. Tokens are approximated as `len/4`. Truncation is disabled by default; set a positive value to enable it. Set to `-1` to disable truncation (unlimited). |\n| `max_tool_result_tokens`    | int     | ✗        | Maximum number of tokens to keep from each tool result when it is added to the session. Oversized results are truncated middle-out: the head and tail are kept and the removed middle is replaced with a truncation marker. Textual documents attached to the result share the same budget. Tokens are approximated as `len/4`. The cap is disabled by default; set a positive value to enable it. `0` and `-1` both leave tool results unbounded. |\n| `num_history_items`         | int     | ✗        | Limit the number of conversation history messages sent to the model. Useful for managing context window size with long conversations. Default: unlimited (all messages sent). |\n| `session_compaction`        | boolean | ✗        | When `false`, disables automatic session compaction for this agent: neither the proactive threshold trigger nor the post-overflow auto-recovery runs. The manual `/compact` command remains available. Default: `true`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_threshold`      | float   | ✗        | Fraction of the model's context window at which proactive auto-compaction triggers. Must be greater than `0` and at most `1`. A `compaction_threshold` set on the agent's model takes precedence. Default: `0.9`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_model`          | string  | ✗        | Model used for session compaction (summary generation). Can be a named model or an inline `provider/model` string. This agent-level value takes precedence over a `compaction_model` set on the agent's model or provider; when none is set, the agent's own model compacts. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `skills`                    | bool/array | ✗     | Enable automatic skill discovery. `true` loads all discovered local skills, `false` disables them. A list can mix skill sources (`local` or `https://…` URLs) and skill names to include — see [Skills](../../features/skills/index.md).                                                     |\n| `commands`                  | object  | ✗        | Named prompts that can be run with `docker agent run config.yaml /command_name`. Can be simple strings or objects with `instruction` and/or `agent` fields for agent switching, or a `url` field to open a link in the browser (TUI only). See [Named Commands](#named-commands) below. |\n| `use_commands`              | list of string | ✗   | Names of top-level `commands` groups to merge into this agent. Inline `commands` entries take precedence on name conflicts. Default: `[]`. |\n| `use_skills`                | list of string | ✗   | Names of top-level `skills` groups to merge into this agent. Inline skills are deduplicated by name against merged entries. Default: `[]`. |\n| `use_toolsets`              | list of string | ✗   | Names of top-level `toolsets` groups to merge into this agent. See [Reusable Toolsets](../overview/index.md#reusable-toolsets-toolsets). Default: `[]`. |\n| `readonly`                  | boolean | ✗   | When `true`, every toolset on this agent is filtered to expose only read-only tools (those annotated with a read-only hint). Mutating tools are removed at load time and cannot be called even if the model tries. See [Read-Only Agents](#read-only-agents) below. |\n| `welcome_message`           | string  | ✗        | Message displayed to the user when a session starts. Rendered as Markdown in the TUI. **Not sent to the model** — it exists purely for the user's benefit. Useful for telling users what the agent can do and what commands are available. |\n| `handoffs`                  | array   | ✗        | List of agent names this agent can hand off the conversation to. Enables the `handoff` tool. See [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).                  |\n| `force_handoff`             | string  | ✗        | Name of an agent that unconditionally receives the conversation whenever this agent produces a final response. The runtime performs the switch itself, bypassing the LLM's tool-calling, guaranteeing deterministic pipelines. Must not reference the agent itself, and chains must not form a cycle. See [Forced Handoffs](../../concepts/multi-agent/index.md#forced-handoffs). |\n| `hooks`                     | object  | ✗        | Lifecycle hooks for running commands at various points. See [Hooks](../hooks/index.md).                                                                                   |\n| `structured_output`         | object  | ✗        | Constrain agent output to match a JSON schema. See [Structured Output](../structured-output/index.md).                                                                    |\n| `cache`                     | object  | ✗        | Response cache. When the same user question is asked again, the previous answer is replayed verbatim and the model is not called. See [Response Cache](#response-cache) below.                  |\n| `harness`                   | object  | ✗        | Run this agent through an external coding CLI instead of a model. **Note:** Any `toolsets:` defined on the same agent are silently ignored when `harness:` is set — the external CLI brings its own tools. See [Coding Harnesses](../../features/harnesses/index.md). |\n\n> [!WARNING]\n> **max_iterations**\n>\n> Default is `0` (unlimited). Always set `max_iterations` for agents with powerful tools like `shell` to prevent infinite loops. A value of 20–50 is typical for development agents.\n\n> [!TIP]\n> **Managing long sessions**\n>\n> `max_old_tool_call_tokens`, `max_tool_result_tokens`, `num_history_items`, `session_compaction`, and `compaction_threshold` all help keep long-running sessions inside the model's context window. See the [Context & Compaction guide](../../guides/compaction/index.md) for how to combine them.\n\n## External Instruction Files\n\nLong system prompts can be kept in their own files instead of being inlined in\nthe YAML, using `instruction_file`. This separates infrastructure configuration\n(models, providers, tools) from behavioral content (the prompt), which keeps\nversion-control diffs focused, reduces merge conflicts on shared configs, and\nlets instruction content be edited without risking YAML syntax errors.\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-5-mini\n    description: Routes work between specialist agents\n    instruction_file: instructions/coordinator.md\n    sub_agents:\n      - writer\n  writer:\n    model: openai/gpt-5-mini\n    description: Drafts and edits written content\n    instruction_file: instructions/writer.md\n```\n\nThe path is resolved relative to the config file's directory and the file's\ncontents are loaded as the agent's instruction when the config is loaded. Notes:\n\n- **Mutually exclusive** with `instruction`. Setting both is an error.\n- Each path must be a **local relative path inside the config directory**.\n  Absolute paths and `..` traversal are rejected.\n- A **list** of files is also accepted; their contents are concatenated in\n  order, separated by a blank line. This lets a shared preamble be reused\n  across agents while each agent appends its own specifics:\n\n  ```yaml\n  agents:\n    writer:\n      model: openai/gpt-5-mini\n      description: Drafts and edits written content\n      instruction_file:\n        - instructions/shared-preamble.md\n        - instructions/writer.md\n  ```\n\n- Only supported for **local file-based configs**, not agents loaded from OCI\n  registries or URLs. When an agent is pushed with `docker agent share push`,\n  the file contents are inlined into the pushed artifact, so the published\n  agent stays self-contained.\n\nA runnable example lives in [`examples/instruction_file.yaml`](https://github.com/docker/docker-agent/blob/main/examples/instruction_file.yaml).\n\n## Prompt Files\n\n`add_prompt_files` injects the contents of one or more files into the agent's\ncontext at the start of every turn — handy for repo-wide conventions like\n`AGENTS.md` or `CLAUDE.md` that should stay available without being pasted\ninto `instruction`:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A helpful coding assistant\n    instruction: You are an expert software developer.\n    add_prompt_files:\n      - AGENTS.md\n```\n\nFor each name, the agent loads the closest match found by walking up from the\ncurrent working directory, plus (if it's a different file) a copy at that\nname directly under the user's home directory — so a personal `~/AGENTS.md`\ncan layer on top of a repo-local one. Missing files are skipped rather than\nerroring. Because resolution and the read happen on every turn, edits to the\nfile are picked up without restarting the agent.\n\nUse `--prompt-file` to add files for a single run without editing the\nconfig. It's merged with any `add_prompt_files` already set on the agent,\nwith duplicates dropped:\n\n```bash\n$ docker agent run agent.yaml --prompt-file CONTRIBUTING.md\n```\n\nResolved prompt files show up as their own entries in the `/context` dialog — see [File Attachments](../../features/tui/index.md#file-attachments) in the Terminal UI guide.\n\nSee [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how prompt files compare to `@`/`/attach` attachments, the `rag` toolset, and sending content over the API/chat server.\n\n## Response Cache\n\nThe response cache short-circuits the model when the same user question is asked again. The first time a question is asked, the agent calls the model normally and stores the assistant's reply. Subsequent identical questions skip the model entirely and replay the stored reply verbatim.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Cached assistant\n    instruction: You are a helpful assistant.\n    cache:\n      enabled: true          # required to turn the cache on\n      case_sensitive: false  # default: false (\"Hello\" == \"hello\")\n      trim_spaces: true      # default: false (\"  hello  \" == \"hello\")\n      path: ./cache.json     # optional: persist to disk; omit for in-memory\n```\n\n| Property         | Type    | Default | Description                                                                                                                                                                                                                       |\n| ---------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `enabled`        | boolean | `false` | Master switch. When `false` (or when the `cache` section is omitted), no caching is performed.                                                                                                                                     |\n| `case_sensitive` | boolean | `false` | When `true`, questions must match exactly (including case) to hit the cache.                                                                                                                                                       |\n| `trim_spaces`    | boolean | `false` | When `true`, leading and trailing whitespace is stripped from the question before it is compared.                                                                                                                                  |\n| `path`           | string  | _empty_ | When set, cache entries are persisted to a JSON file at the given path and reloaded on startup so the cache survives restarts. Relative paths resolve against the agent config directory. When empty, the cache lives in memory only. |\n\n**How it works**\n\n- The cache key is the latest user message in the session, normalized according to `case_sensitive` and `trim_spaces`.\n- On a hit, the cached reply is added to the session as the assistant message and stop hooks fire normally — the rest of the agent (tools, sub-agents, the model) is bypassed.\n- On a miss, the agent runs normally; the final assistant message produced by the first stop of the run is then stored under the question's key.\n- Only the response to the original user question of a run is cached; follow-up turns inside the same `RunStream` are not.\n\n**File-backed storage**\n\nWhen `path` is set, every `Store` rewrites the entire cache file. Writes are **atomic**: the new content is written to a sibling temp file, `fsync`'d, and renamed over the destination, so a concurrent reader (or a process that crashes mid-write) will always see either the previous content or the new content in full — never a partially written file. The parent directory is also `fsync`'d after the rename so the rename itself is durable.\n\n**Cross-process sharing**\n\nMultiple processes can share the same `path:` cache file safely. Every `Store` takes an exclusive advisory lock on a sibling `<path>.lock` file (POSIX `flock(2)` on Unix, `LockFileEx` on Windows), reloads the current on-disk state under the lock, merges the new entry, and writes back atomically. Two processes that store *different* keys at the same time both see their writes preserved on disk; the lock window is short (one read + one fsync'd write).\n\n`Lookup` watches the file's modification time and reloads the in-memory map when the file has advanced since its last load, so writes from a sibling process become visible without a restart. The `<path>.lock` sentinel file is created on first write and never deleted: removing it would let two processes lock different inodes and lose mutual exclusion.\n\n## Redacting Secrets\n\nThe `redact_secrets` flag is a single agent-level switch that scrubs accidentally leaked credentials, tokens, and private keys out of an agent's I/O. It wires up three complementary defenses:\n\n1. A `pre_tool_use` built-in hook that scrubs detected secrets from the **arguments of every tool call**, before the tool sees them.\n2. A `before_llm_call` built-in hook that scrubs the same patterns from **outgoing chat messages** — message content, multi-part text content, prior reasoning content, and the JSON-encoded arguments of any tool call still in the conversation — before they reach the model provider.\n3. A `tool_response_transform` built-in hook that scrubs **tool output at the source**, so the secret never reaches event consumers, the persisted session file, the `post_tool_use` hook input, or the next LLM call.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: A helpful assistant that scrubs secrets before they leak\n    instruction: |\n      You are a helpful assistant. If the user accidentally pastes a token,\n      do your best work without echoing the secret back.\n    redact_secrets: true\n    toolsets:\n      - type: shell\n```\n\nDetection uses the [portcullis](https://github.com/docker/portcullis) ruleset, which recognises common secret patterns including:\n\n- GitHub Personal Access Tokens (`ghp_*`, `gho_*`, `ghu_*`, `ghs_*`, `ghr_*`, fine-grained `github_pat_*`)\n- AWS access keys (`AKIA*`, `ASIA*`, …) and secret access keys\n- GitLab PATs (`glpat-*`), Hugging Face tokens (`hf_*`)\n- Stripe (`sk_live_*`, `pk_test_*`, …), Slack (`xoxb-*`, …), Shopify, Twilio, Discord, Atlassian, Mailchimp, SendGrid, and many more\n- JWTs, GCP service-account JSON, Heroku keys, Docker Hub PATs (`dckr_pat_*`)\n- PEM-encoded private keys (`-----BEGIN … PRIVATE KEY-----` blocks)\n\nEach detected span is replaced with the literal string `[REDACTED]`; the surrounding text is preserved so a redacted argument still looks like a legitimate flag (e.g. `--token=[REDACTED]`). Redaction is idempotent — applying it twice yields the same result.\n\n> [!NOTE]\n> **False positives vs. false negatives**\n>\n> False positives are extremely rare: every rule pairs a regex with a discriminating keyword, so plain English never trips detection. **False negatives are possible** — only patterns the ruleset recognises are scrubbed, so this is a defense-in-depth feature, not a substitute for keeping secrets out of the conversation in the first place. Pair it with a proper [secret manager](../../guides/secrets/index.md) for the credentials your agent actually needs.\n\n> [!NOTE]\n> **Equivalent hook entry**\n>\n> Setting `redact_secrets: true` on the agent is shorthand for auto-registering all three legs of the feature as hook entries. They share the _same_ built-in name (`type: builtin`, `command: redact_secrets`) on `pre_tool_use`, `before_llm_call`, and `tool_response_transform` respectively — the implementation dispatches on the hook event. You can spell them out by hand to scope a leg to a subset of tools (set `matcher:` to a regex), stack them with other rewriters in a specific order, or enable just one or two legs. See [`examples/redact_secrets_hooks.yaml`](https://github.com/docker/docker-agent/blob/main/examples/redact_secrets_hooks.yaml) for a complete manual wiring and the [Hooks reference](../hooks/index.md#available-built-ins) for the builtin's event coverage.\n\n## Welcome Message\n\nDisplay a message when users start a session:\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-5\n    description: Development assistant\n    instruction: You are a helpful coding assistant.\n    welcome_message: |\n      👋 Welcome! I'm your development assistant.\n\n      I can help you with:\n      - Writing and reviewing code\n      - Running tests and debugging\n      - Explaining concepts\n\n      What would you like to work on?\n```\n\n## Deferred Tool Loading\n\nToolsets support `defer` to load tools on-demand and speed up agent startup. See [Deferred Tool Loading](../tools/index.md#deferred-tool-loading) for details.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-purpose assistant\n    instruction: You have access to many tools.\n    toolsets:\n      - type: mcp\n        ref: docker:github-official\n        defer: true\n      - type: filesystem\n```\n\n## Fallback Configuration\n\nAutomatically switch to backup models when the primary fails:\n\n| Property   | Type   | Default | Description                                                |\n| ---------- | ------ | ------- | ---------------------------------------------------------- |\n| `models`   | array  | `[]`    | Fallback models to try in order                            |\n| `retries`  | int    | `2`     | Retries per model for 5xx errors. `-1` to disable.         |\n| `cooldown` | string | `1m`    | How long to stick with a fallback after a rate limit (429) |\n\n**Error handling:**\n\n- **Retryable** (same model with backoff): HTTP 5xx, 408, network timeouts\n- **Non-retryable** (skip to next model): HTTP 429, 4xx client errors\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2\n      cooldown: 1m\n```\n\n## Named Commands\n\n> [!TIP]\n> **Full reference**\n>\n> This section covers the basics. For URL commands, agent-switching commands, reusable top-level `commands:` groups, and hiding commands with `--disable-commands`, see [Custom Commands](../commands/index.md).\n\nDefine reusable prompt shortcuts that can send prompts to the current agent, switch to a different sub-agent, or open a URL in the browser:\n\n> **Note:** Named slash commands execute immediately, even while the agent is processing another message. Unlike regular chat messages (which are queued), slash commands interrupt or direct the agent even while it is mid-response.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n      deploy: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n      \n      # Advanced format with agent switching\n      plan:\n        agent: planner  # Switch to the 'planner' agent\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"  # Optional: send this prompt after switching\n      \n      # Agent switching without instruction - forwards remaining text as prompt\n      review:\n        agent: reviewer  # Any text after /review is sent to the reviewer agent\n\n      # URL command - opens a link in the browser instead of messaging the agent\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n```\n\n### Command Formats\n\nCommands support three formats:\n\n1. **Simple string format**: The string becomes the instruction sent to the current agent\n\n   ```yaml\n   df: \"Check disk space\"\n   ```\n\n2. **Advanced object format**: Supports agent switching and optional instructions\n\n   ```yaml\n   plan:\n     agent: planner  # Required: name of any agent defined in the team\n     instruction: \"Plan: ${args.join(\\\" \\\")}\"  # Optional: prompt to send after switching\n     description: \"Switch to planning mode\"  # Optional: shown in help text\n   ```\n\n3. **URL format**: Opens a link in the browser instead of messaging the agent\n\n   ```yaml\n   docs:\n     url: https://docs.docker.com/          # Required: URL to open\n     description: \"Open the documentation\"  # Optional: shown in help text\n   ```\n\nWhen `agent` is set without `instruction`, any text typed after the slash command (e.g., `/plan build a web app`) is forwarded as a prompt to the target agent. The target agent can be **any agent defined in the team configuration** — it does not need to be listed in the current agent's `sub_agents` array.\n\n**Argument and expansion syntax**\n\nAn `instruction` string can reference the command's arguments and expand tool calls:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments, in the order the user typed them after the command\n- `${args.join(\" \")}` — all arguments joined into a single string\n- `${tool_name({...})}` — calls a tool and inlines its return value (any tool available to the agent)\n- `!tool_name(key=value)` — legacy tool-call form: calls a tool with plain `key=value` arguments and inlines its output\n\n### Agent-Switching Commands\n\nCommands with an `agent` field switch the active agent for that command's scope. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the appropriate specialist.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n      # Simple prompt command (no switching)\n      status: \"Summarize what we have accomplished so far\"\n\n  planner:\n    model: openai/gpt-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\n**Agent-switching vs. `handoff`**\n\n| | Agent-switching command | `handoff` tool |\n| --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` |\n| **Session** | Stays in the same session | Stays in the same session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation |\n| **Return** | User must explicitly switch back | Target agent can chain to another agent |\n\n**Agent-switching vs. `transfer_task`**\n\n`transfer_task` launches a **sub-session**: the root agent sends a task, the child runs in isolation, and the result is returned to the root. The root agent stays in control and the child's work is never in the main conversation. Use `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n```bash\n# Run commands from the CLI\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\nCommands use JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation. Undefined variables expand to empty strings.\n\nThe same syntax is also expanded in agent and toolset instructions: `agents.<name>.instruction` and `toolsets[*].instruction` support `${env.X}` placeholders (with optional `||` defaults and ternary expressions). `agents.<name>.description` and `agents.<name>.welcome_message` also support it.\n\nNote that path-like fields (`working_dir`, `path`) primarily use a shell-style syntax (`$VAR`, `${VAR}`, `~`), and also accept `${env.X}` as an alias (though not richer JS expressions). See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full table.\n\n### URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — both standard web URLs and custom schemes such as `docker-desktop://` for deep links. URL commands are TUI-only — they have no effect when run from the CLI.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Read-Only Agents\n\nSet `readonly: true` on an agent to restrict all of its toolsets to tools that are annotated as read-only. Mutating tools are filtered out at load time — the agent cannot list or call them, even if the model hallucinates a call.\n\nYou can also set `readonly: true` on an individual toolset to restrict only that toolset while leaving others unrestricted.\n\n```yaml\nagents:\n  # Agent-level readonly: every toolset is restricted to read-only tools.\n  inspector:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only inspector that can explore but never modify.\n    instruction: Explore the project. Do not make changes.\n    readonly: true\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  # Toolset-level readonly: only the filesystem toolset is restricted;\n  # the shell toolset keeps all of its tools.\n  mixed:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only file access, full shell access.\n    instruction: You can read files and run any shell command.\n    toolsets:\n      - type: filesystem\n        readonly: true\n      - type: shell\n```\n\nSee [`examples/readonly.yaml`](https://github.com/docker/docker-agent/blob/main/examples/readonly.yaml) for a complete example.\n\n> [!NOTE]\n> **Which tools are read-only?**\n>\n> Whether a tool is read-only is determined by its `ReadOnlyHint` annotation. For built-in tools, read-only operations (list/read/search) carry the hint; mutating operations (write/delete/execute) do not. Custom and MCP tools expose the hint via their own annotations.\n\n## Complete Example\n\n```yaml\nmodels:\n  claude:\n    provider: anthropic\n    model: claude-sonnet-4-5\n    max_tokens: 64000\n\nagents:\n  root:\n    model: claude\n    description: Technical lead coordinating development\n    instruction: |\n      You are a technical lead. Analyze requests and delegate\n      to the right specialist. Always review work before responding.\n    welcome_message: \"👋 I'm your tech lead. How can I help today?\"\n    sub_agents: [developer, researcher]\n    add_date: true\n    add_environment_info: true\n    fallback:\n      models: [openai/gpt-5]\n    toolsets:\n      - type: think\n    commands:\n      review: \"Review all recent code changes for issues\"\n    hooks:\n      session_start:\n        - type: command\n          command: \"./scripts/setup.sh\"\n\n  developer:\n    model: claude\n    description: Expert software developer\n    instruction: Write clean, tested, production-ready code.\n    max_iterations: 30\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n\n  researcher:\n    model: openai/gpt-5\n    description: Web researcher with memory\n    instruction: Search for information and remember findings.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n      - type: memory\n        path: ./research.db\n```\n","frontmatter":{"title":"Agent Configuration","description":"Complete reference for defining agents in your YAML configuration.","keywords":"docker agent, ai agents, configuration, yaml, agent configuration","linkTitle":"Agent Config","weight":30,"canonical":"https://docs.docker.com/ai/docker-agent/configuration/agents/"},"isInternal":false,"tokens":8506,"sizeBytes":39250},{"name":"_index.md","path":"content/manuals/ai/sandboxes/agents/_index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/_index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"---\ntitle: Supported agents\nlinkTitle: Agents\nweight: 40\ndescription: AI coding agents supported by Docker Sandboxes.\nkeywords: docker sandboxes, ai agents, claude code, codex, cursor, gemini\n---\n\nDocker Sandboxes runs the following agents out of the box:\n\n- [Claude Code](claude-code/)\n- [Codex](codex/)\n- [Copilot](copilot/)\n- [Cursor](cursor/)\n- [Docker Agent](docker-agent/)\n- [Droid](droid/)\n- [Gemini](gemini/)\n- [Kiro](kiro/)\n- [OpenCode](opencode/)\n- [Shell](shell/) — agent-less sandbox for manual setup or testing\n\nWant to pre-install tools or customize an agent's environment?\nSee [Customize](../customize/).\n","frontmatter":{"title":"Supported agents","linkTitle":"Agents","weight":40,"description":"AI coding agents supported by Docker Sandboxes.","keywords":"docker sandboxes, ai agents, claude code, codex, cursor, gemini"},"isInternal":false,"tokens":172,"sizeBytes":622},{"name":"claude-code.md","path":"content/manuals/ai/sandboxes/agents/claude-code.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/claude-code.md","title":"Subagent: claude-code","category":"subagent-persona","format":"markdown","content":"---\ntitle: Claude Code\nweight: 10\ndescription: |\n  Use Claude Code in Docker Sandboxes with authentication, local models,\n  configuration, and YOLO mode for AI-assisted development.\nkeywords: docker sandboxes, claude code, anthropic, ai agent, sbx, local models, llmman, ollama\n---\n\nOfficial documentation: [Claude Code](https://code.claude.com/docs)\n\n## Quick start\n\nLaunch Claude Code in a sandbox by pointing it at a project directory:\n\n```console\n$ sbx run claude ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so `sbx run claude`\nfrom inside your project works too. To start Claude with a specific prompt:\n\n```console\n$ sbx run claude --name my-sandbox -- \"Add error handling to the login function\"\n```\n\nEverything after `--` is passed directly to Claude Code. You can also pipe in a\nprompt from a file with `-- \"$(cat prompt.txt)\"`.\n\n## Authentication\n\nClaude Code requires either an Anthropic API key or a Claude subscription.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set anthropic\n```\n\n**Claude subscription**: If no API key is set, use the `/login` command inside\nClaude Code to authenticate via OAuth.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.claude`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Remote control\n\nTo use Claude Code's `/remote-control` command inside a sandbox, turn on remote\ncontrol:\n\n```console\n$ sbx settings set claude.remoteControl true\n```\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nclaude --dangerously-skip-permissions\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--dangerously-skip-permissions` is\npreserved:\n\n```console\n$ sbx run claude -- -c   # runs claude --dangerously-skip-permissions -c\n```\n\nWhen the first argument is a bare word, such as the `agents` subcommand, it\nreplaces the defaults instead.\n\nSee the [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference)\nfor available options.\n\n## Agents view\n\nClaude Code's [agents view](https://code.claude.com/docs/en/agent-view)\nstarts background sessions that run tasks in parallel. Pair it with\n[clone mode](../workflows/git.md#clone-mode) to keep their changes inside the\nsandbox:\n\n```console\n$ sbx run --clone claude -- agents\n```\n\nThis invocation replaces the\n[default startup command](#default-startup-command), so it doesn't\ninclude `--dangerously-skip-permissions` and you can't switch to\nbypass-permissions mode inside the sandbox. To work around this, either\nuse Claude Code's auto mode or pass the flag explicitly:\n\n```console\n$ sbx run --clone claude -- --dangerously-skip-permissions agents\n```\n\nClaude Code may use branches or worktrees to keep changes from its background\nsessions separate. This depends on the task, Claude Code configuration, and\nproject instructions. The `--clone` flag doesn't control this behavior. Claude\nCode creates any branches and worktrees inside the sandbox, not in your host\ncheckout.\n\nTo review a branch created by a session, fetch the\n`sandbox-<sandbox-name>` remote from the host:\n\n```console\n$ git fetch sandbox-<sandbox-name>\n$ git diff main..sandbox-<sandbox-name>/<branch>\n```\n\nSee [Git workflows](../workflows/git.md) for clone-mode details.\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:claude-code`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n\n## Use a local model\n\nThe `--model` flag routes Claude Code's Anthropic API requests to a model\nserved on your host. This feature is experimental and isn't supported on\nWindows.\n\nEnable the feature:\n\n```console\n$ sbx settings set platform.allowExperimentalFeatures true\n$ sbx settings set feature.model true\n```\n\nTo use the bundled `llmman` model server, pass a GGUF model reference or short\nname:\n\n```console\n$ sbx run --model gemma4 claude\n```\n\nOn first use, `sbx` starts `llmman`, pulls the model, and leaves the server\nrunning on your host. Later sandboxes reuse the server and its model store.\n\nTo use an existing Ollama installation instead, set the provider to `ollama`:\n\n```console\n$ sbx run --model gemma4 --provider ollama claude\n```\n\nOllama must already be installed and running. `sbx` connects to it but doesn't\nstart or manage the Ollama process.\n\nYou can also change the model for an existing sandbox:\n\n```console\n$ sbx run --name <sandbox-name> --model <model-name>\n```\n\nChanging the model recreates the sandbox container. The workspace and\nkit-owned volumes persist.\n\nTo use Docker Model Runner instead, see\n[Run Claude Code in a Docker Sandbox with Docker Model Runner](/guides/claude-code-sandbox-model-runner/).\n","frontmatter":{"title":"Claude Code","weight":10,"description":"Use Claude Code in Docker Sandboxes with authentication, local models,\nconfiguration, and YOLO mode for AI-assisted development.\n","keywords":"docker sandboxes, claude code, anthropic, ai agent, sbx, local models, llmman, ollama"},"isInternal":false,"tokens":1210,"sizeBytes":4983},{"name":"codex.md","path":"content/manuals/ai/sandboxes/agents/codex.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/codex.md","title":"Subagent: codex","category":"subagent-persona","format":"markdown","content":"---\ntitle: Codex\nweight: 20\ndescription: |\n  Use OpenAI Codex in Docker Sandboxes with API key authentication and YOLO\n  mode configuration.\nkeywords: docker sandboxes, codex, openai, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Codex in a\nsandboxed environment.\n\nOfficial documentation: [Codex CLI](https://developers.openai.com/codex/cli)\n\n## Quick start\n\nCreate a sandbox and run Codex for a project directory:\n\n```console\n$ sbx run codex ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run codex\n```\n\n## Authentication\n\nIf you haven't stored an OpenAI credential, `sbx run codex` prompts you to\nauthenticate on your host before launching the sandbox. The flow runs on the\nhost, so credentials are never exposed inside the sandbox.\n\nTo set up authentication ahead of time, choose one of the following methods.\n\n**OAuth**: Start the OAuth flow on your host with:\n\n```console\n$ sbx secret set openai --oauth\n```\n\nThis opens a browser window for authentication and stores the resulting tokens\nin your OS keychain. The OAuth flow runs on the host, not inside the sandbox,\nso browser-based authentication works without any extra setup.\n\n**API key**: Store your OpenAI API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n```\n\nSee [Credentials](../configuration/credentials.md) for more details.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.codex`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncodex --dangerously-bypass-approvals-and-sandbox\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). A bare word — such as a prompt — replaces the\ndefaults instead, so lead with the flag to keep bypass mode:\n\n```console\n$ sbx run codex -- --dangerously-bypass-approvals-and-sandbox \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:codex`\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Codex","weight":20,"description":"Use OpenAI Codex in Docker Sandboxes with API key authentication and YOLO\nmode configuration.\n","keywords":"docker sandboxes, codex, openai, ai agent, sbx"},"isInternal":false,"tokens":568,"sizeBytes":2415},{"name":"copilot.md","path":"content/manuals/ai/sandboxes/agents/copilot.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/copilot.md","title":"Subagent: copilot","category":"subagent-persona","format":"markdown","content":"---\ntitle: Copilot\nweight: 30\ndescription: |\n  Use GitHub Copilot in Docker Sandboxes with GitHub token authentication and\n  trusted folder configuration.\nkeywords: docker sandboxes, github copilot, ai agent, github token, sbx\n---\n\nThis guide covers authentication, configuration, and usage of GitHub Copilot\nin a sandboxed environment.\n\nOfficial documentation: [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli)\n\n## Quick start\n\nCreate a sandbox and run Copilot for a project directory:\n\n```console\n$ sbx run copilot ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run copilot\n```\n\n## Authentication\n\nCopilot requires a GitHub token with Copilot access. Store your token using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set github --command 'gh auth token'\n```\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCopilot is configured to trust the workspace directory by default, so it\noperates without repeated confirmations for workspace files.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncopilot --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run copilot -- -p \"review this PR\"   # runs copilot --yolo -p \"review this PR\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:copilot`\n\nPreconfigured to trust the workspace directory.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Copilot","weight":30,"description":"Use GitHub Copilot in Docker Sandboxes with GitHub token authentication and\ntrusted folder configuration.\n","keywords":"docker sandboxes, github copilot, ai agent, github token, sbx"},"isInternal":false,"tokens":466,"sizeBytes":2018},{"name":"cursor.md","path":"content/manuals/ai/sandboxes/agents/cursor.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/cursor.md","title":"Subagent: cursor","category":"subagent-persona","format":"markdown","content":"---\ntitle: Cursor\nweight: 40\ndescription: |\n  Use Cursor in Docker Sandboxes with API key or proxy-managed OAuth\n  authentication.\nkeywords: docker sandboxes, cursor, cursor agent, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Cursor in a\nsandboxed environment.\n\nOfficial documentation: [Cursor CLI](https://cursor.com/cli)\n\n## Quick start\n\nCreate a sandbox and run Cursor for a project directory:\n\n```console\n$ sbx run cursor ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run cursor\n```\n\n## Authentication\n\nCursor supports two authentication methods: an API key or OAuth.\n\n**API key**: Store your Cursor API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set cursor\n```\n\n**OAuth**: If no API key is set, Cursor prompts you to sign in interactively\non first run. The proxy intercepts the token exchange with\n`api2.cursor.sh/auth/poll`, so credentials are managed by the host and aren't\nstored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.cursor`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCursor reads `AGENTS.md` from the workspace for agent-specific instructions.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncursor-agent --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run cursor -- -p \"refactor this\"   # runs cursor-agent --yolo -p \"refactor this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:cursor-agent-docker`\n\nPreconfigured with HTTP/1.1 and server-sent events for agent traffic so\nrequests flow through the host proxy. Authentication state is persisted across\nsandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Cursor","weight":40,"description":"Use Cursor in Docker Sandboxes with API key or proxy-managed OAuth\nauthentication.\n","keywords":"docker sandboxes, cursor, cursor agent, ai agent, sbx"},"isInternal":false,"tokens":529,"sizeBytes":2290},{"name":"docker-agent.md","path":"content/manuals/ai/sandboxes/agents/docker-agent.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/docker-agent.md","title":"Subagent: docker-agent","category":"subagent-persona","format":"markdown","content":"---\ntitle: Docker Agent\nweight: 50\ndescription: |\n  Use Docker Agent in Docker Sandboxes with multi-provider authentication\n  supporting OpenAI, Anthropic, and more.\nkeywords: docker sandboxes, docker agent, openai, anthropic, sbx\n---\n\nOfficial documentation: [Docker Agent](/manuals/ai/docker-agent/_index.md)\n\n## Quick start\n\nCreate a sandbox and run Docker Agent for a project directory:\n\n```console\n$ sbx run docker-agent ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so\n`sbx run docker-agent` from inside your project works too.\n\n## Authentication\n\nDocker Agent supports multiple providers. Store keys for the providers you want\nto use with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set nebius\n$ sbx secret set mistral\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. Docker Agent detects\navailable credentials and routes requests to the appropriate provider.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ndocker-agent run --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). When the first argument is a bare word — such\nas the `run` subcommand or a config file — it replaces the defaults, so include\n`run --yolo` yourself:\n\n```console\n$ sbx run docker-agent -- run --yolo agent.yml\n```\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:docker-agent`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n","frontmatter":{"title":"Docker Agent","weight":50,"description":"Use Docker Agent in Docker Sandboxes with multi-provider authentication\nsupporting OpenAI, Anthropic, and more.\n","keywords":"docker sandboxes, docker agent, openai, anthropic, sbx"},"isInternal":false,"tokens":475,"sizeBytes":2010},{"name":"droid.md","path":"content/manuals/ai/sandboxes/agents/droid.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/droid.md","title":"Subagent: droid","category":"subagent-persona","format":"markdown","content":"---\ntitle: Droid\nweight: 60\ndescription: |\n  Use Droid in Docker Sandboxes with API key or OAuth authentication.\nkeywords: docker sandboxes, droid, factory, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Droid, an AI\ncoding agent by Factory, in a sandboxed environment.\n\nOfficial documentation: [Droid](https://docs.factory.ai/)\n\n## Quick start\n\nCreate a sandbox and run Droid for a project directory:\n\n```console\n$ sbx run droid ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run droid\n```\n\n## Authentication\n\nDroid requires a [Factory account](https://factory.ai). Both authentication\nmethods authenticate you to Factory's service directly — unlike other agents\nwhere you supply a model provider key, Factory manages model access through\nyour Factory account.\n\n**API key**: Store your Factory API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set droid\n```\n\n**OAuth**: If no API key is set, Droid prompts you to authenticate\ninteractively on first run. The proxy handles the OAuth flow, so credentials\naren't stored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nThe sandbox runs `droid` with no implicit flags. Args after `--` are passed\nstraight through:\n\n```console\n$ sbx run droid -- exec \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:droid-docker`\n\nPreconfigured to run without approval prompts. Authentication state is\npersisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Droid","weight":60,"description":"Use Droid in Docker Sandboxes with API key or OAuth authentication.\n","keywords":"docker sandboxes, droid, factory, ai agent, sbx"},"isInternal":false,"tokens":449,"sizeBytes":1981},{"name":"gemini.md","path":"content/manuals/ai/sandboxes/agents/gemini.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/gemini.md","title":"Subagent: gemini","category":"subagent-persona","format":"markdown","content":"---\ntitle: Gemini\nweight: 70\ndescription: |\n  Use Google Gemini in Docker Sandboxes with proxy-managed authentication and\n  API key configuration.\nkeywords: docker sandboxes, gemini, google, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Google Gemini in\na sandboxed environment.\n\nOfficial documentation: [Gemini CLI](https://geminicli.com/docs/)\n\n## Quick start\n\nCreate a sandbox and run Gemini for a project directory:\n\n```console\n$ sbx run gemini ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run gemini\n```\n\n## Authentication\n\nGemini requires either a Google API key or a Google account with Gemini access.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set google\n```\n\n**Google account**: If no API key is set, Gemini prompts you to sign in\ninteractively when it starts. Interactive authentication is scoped to the\nsandbox and doesn't persist if you remove and recreate it.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.gemini`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nThe sandbox disables Gemini's built-in sandbox tool (since the sandbox itself\nprovides isolation).\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ngemini --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run gemini -- -p \"explain this\"   # runs gemini --yolo -p \"explain this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:gemini`\n\nGemini is configured to disable its built-in OAuth flow. Authentication is\nmanaged through the proxy with API keys.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Gemini","weight":70,"description":"Use Google Gemini in Docker Sandboxes with proxy-managed authentication and\nAPI key configuration.\n","keywords":"docker sandboxes, gemini, google, ai agent, sbx"},"isInternal":false,"tokens":516,"sizeBytes":2222},{"name":"kiro.md","path":"content/manuals/ai/sandboxes/agents/kiro.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/kiro.md","title":"Subagent: kiro","category":"subagent-persona","format":"markdown","content":"---\ntitle: Kiro\nweight: 80\ndescription: |\n  Use Kiro in Docker Sandboxes with device flow authentication for interactive\n  AI-assisted development.\nkeywords: docker sandboxes, kiro, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Kiro in a\nsandboxed environment.\n\nOfficial documentation: [Kiro CLI](https://kiro.dev/docs/cli/)\n\n## Quick start\n\nCreate a sandbox and run Kiro for a project directory:\n\n```console\n$ sbx run kiro ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run kiro\n```\n\nOn first run, Kiro prompts you to authenticate using device flow.\n\n## Authentication\n\nKiro uses device flow authentication, which requires interactive login through\na web browser. This method provides secure authentication without storing API\nkeys directly.\n\n### Device flow login\n\nWhen you first run Kiro, it prompts you to authenticate:\n\n1. Kiro displays a URL and a verification code\n2. Open the URL in your web browser\n3. Enter the verification code\n4. Complete the authentication flow in your browser\n5. Return to the terminal - Kiro proceeds automatically\n\nThe authentication session is persisted in the sandbox and doesn't require\nrepeated login unless you destroy and recreate the sandbox.\n\n### Manual login\n\nYou can trigger the login flow manually:\n\n```console\n$ sbx run kiro --name <sandbox-name> -- login --use-device-flow\n```\n\nThis command initiates device flow authentication without starting a coding\nsession.\n\n### Authentication persistence\n\nKiro stores authentication state in `~/.local/share/kiro-cli/data.sqlite3`\ninside the sandbox. This database persists as long as the sandbox exists. If\nyou destroy the sandbox, you'll need to authenticate again when you recreate\nit.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nKiro requires minimal configuration. The agent runs with trust-all-tools mode\nby default, which lets it execute commands without repeated approval prompts.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nkiro chat --trust-all-tools\n```\n\nWhen the first argument after `--` is a flag (begins with `-`), it's added\nafter the defaults — for example, `sbx run kiro -- --resume` runs\n`kiro chat --trust-all-tools --resume`. When the first argument is a bare word,\nit replaces the defaults, which is why `sbx run kiro -- login --use-device-flow`\nruns the login subcommand on its own. To run `chat` with extra arguments of\nyour own, include the subcommand:\n\n```console\n$ sbx run kiro -- chat --trust-all-tools --resume\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:kiro`\n\nAuthentication state is persisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"Kiro","weight":80,"description":"Use Kiro in Docker Sandboxes with device flow authentication for interactive\nAI-assisted development.\n","keywords":"docker sandboxes, kiro, ai agent, authentication, sbx"},"isInternal":false,"tokens":693,"sizeBytes":3090},{"name":"opencode.md","path":"content/manuals/ai/sandboxes/agents/opencode.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/opencode.md","title":"Subagent: opencode","category":"subagent-persona","format":"markdown","content":"---\ntitle: OpenCode\nweight: 90\ndescription: |\n  Use OpenCode in Docker Sandboxes with multi-provider authentication and TUI\n  interface for AI development.\nkeywords: docker sandboxes, opencode, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of OpenCode in a\nsandboxed environment.\n\nOfficial documentation: [OpenCode](https://opencode.ai/docs)\n\n## Quick start\n\nCreate a sandbox and run OpenCode for a project directory:\n\n```console\n$ sbx run opencode ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run opencode\n```\n\nOpenCode launches a TUI (text user interface) where you can select your\npreferred LLM provider and interact with the agent.\n\n## Authentication\n\nOpenCode supports multiple providers. Store keys for the providers you want to\nuse with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set groq\n$ sbx secret set aws\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. OpenCode detects\navailable credentials and offers those providers in the TUI.\n\n### OpenCode Zen API keys\n\nOpenCode Zen API keys aren't part of the built-in OpenCode credentials that\n`sbx secret set` supports. To use an OpenCode Zen API key, store it as a\n[custom secret](../configuration/credentials.md#custom-secrets):\n\nSet the `OPENCODE_API_KEY` environment variable on the host, then store it:\n\n```console\n$ sbx secret set-custom \\\n    --host opencode.ai \\\n    --env OPENCODE_API_KEY \\\n    --value \"$OPENCODE_API_KEY\"\n```\n\nCustom secrets keep the real key in the host secret store. The sandbox receives\n`OPENCODE_API_KEY` as a placeholder, and the host-side proxy replaces that\nplaceholder with the real key on requests to `opencode.ai`.\n\nOpenCode Zen also requires network access to `opencode.ai`:\n\n```console\n$ sbx policy allow network opencode.ai:443\n```\n\nIf you add a global custom secret, recreate existing OpenCode sandboxes so the\nnew environment variable is available inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nOpenCode uses a TUI interface and doesn't require extensive configuration\nfiles. The agent prompts you to select a provider when it starts, and you can\nswitch providers during a session.\n\n### Default startup command\n\nThe sandbox runs `opencode` with no implicit flags. Args after `--` are passed\nstraight through. For example, to resume an existing session:\n\n```console\n$ sbx run opencode -- -s <session-id>\n```\n\n### TUI mode\n\nOpenCode launches in TUI mode by default. The interface shows:\n\n- Available LLM providers (based on configured credentials)\n- Current conversation history\n- File operations and tool usage\n- Real-time agent responses\n\nUse keyboard shortcuts to navigate the interface and interact with the agent.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:opencode`\n\nOpenCode supports multiple LLM providers with automatic credential injection\nthrough the sandbox proxy.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n","frontmatter":{"title":"OpenCode","weight":90,"description":"Use OpenCode in Docker Sandboxes with multi-provider authentication and TUI\ninterface for AI development.\n","keywords":"docker sandboxes, opencode, ai agent, authentication, sbx"},"isInternal":false,"tokens":790,"sizeBytes":3490},{"name":"shell.md","path":"content/manuals/ai/sandboxes/agents/shell.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/content/manuals/ai/sandboxes/agents/shell.md","title":"Subagent: shell","category":"subagent-persona","format":"markdown","content":"---\ntitle: Shell\nweight: 100\ndescription: Run an agent-less sandbox with a Bash login shell for manual setup, testing custom agent implementations, or inspecting a running environment.\nkeywords: sandboxes, sbx, shell, agent, manual setup, testing\n---\n\n`sbx run shell` drops you into a Bash login shell inside a sandbox with no\npre-installed agent binary. It's useful for installing and configuring\nagents manually, testing custom implementations, or inspecting a running\nenvironment.\n\n```console\n$ sbx run shell ~/my-project\n```\n\nThe workspace path defaults to the current directory. To run a one-off\ncommand instead of an interactive shell, pass it after `--`:\n\n```console\n$ sbx run shell -- -c \"echo 'Hello from sandbox'\"\n```\n\n## Default startup command\n\nWithout extra args, the sandbox runs `bash -l`. When the first argument after\n`--` is a flag (begins with `-`), it's added after `-l`, so login-shell\nbehavior is preserved:\n\n```console\n$ sbx run shell -- -c \"echo hi\"   # runs bash -l -c \"echo hi\"\n```\n\nWhen the first argument is a bare word, it replaces `-l` instead.\n\nStore credentials using [stored secrets](../configuration/credentials.md#stored-secrets)\nbefore running the sandbox. The proxy injects them into outbound API requests;\ncredentials are never stored inside the VM:\n\n```console\n$ sbx secret set anthropic\n$ sbx secret set openai\n```\n\nOnce inside the shell, you can install agents using their standard methods,\nfor example `npm install -g @continuedev/cli`. For complex setups, build a\n[custom template](../customize/templates.md) instead of installing\ninteractively each time.\n\n## Base image\n\nThe shell sandbox uses the `shell` base image — the common base environment\nwithout a pre-installed agent.\n","frontmatter":{"title":"Shell","weight":100,"description":"Run an agent-less sandbox with a Bash login shell for manual setup, testing custom agent implementations, or inspecting a running environment.","keywords":"sandboxes, sbx, shell, agent, manual setup, testing"},"isInternal":false,"tokens":402,"sizeBytes":1724},{"name":"index.md","path":"_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md","rawUrl":"https://raw.githubusercontent.com/docker/docs/HEAD/_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md","title":"Command: /index","category":"command-prompt","format":"markdown","content":"---\ntitle: \"Custom Commands\"\ndescription: \"Define slash commands that send prompts, open URLs, or switch agents, and reuse them across agents with top-level command groups.\"\nkeywords: docker agent, ai agents, configuration, yaml, custom commands, slash commands\nlinkTitle: \"Custom Commands\"\nweight: 55\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/commands/\n---\n\n_Define slash commands that send prompts, open URLs, or switch agents._\n\n## What Slash Commands Are\n\nA slash command is a named shortcut a user types in the TUI (`/df`, `/deploy`, `/plan`) or on the CLI (`docker agent run agent.yaml /df`) instead of typing out a full prompt. Every agent can declare its own commands under `commands:`, and top-level `commands:` groups let multiple agents share the same set without duplicating them.\n\nUnlike regular chat messages — which are queued while the agent is busy — slash commands (both built-in and named) execute immediately, even mid-response.\n\nCommands come in three shapes:\n\n| Shape | What it does |\n| --- | --- |\n| [Prompt command](#prompt-commands) | Sends a prompt to the current agent |\n| [URL command](#url-commands) | Opens a link in the user's browser (full TUI only) |\n| [Agent-switching command](#agent-switching-commands) | Switches the active agent, optionally with a prompt (full TUI and CLI) |\n\n> [!IMPORTANT]\n> **Behavior differs by frontend**\n>\n> `url` and `agent` are only fully honored in the **full TUI**, which checks `url` before `agent` (a URL command opens the browser and stops there; an agent-switching command switches before sending any instruction). The **lean TUI** doesn't special-case either field — it only resolves a command's expanded text and sends it as a chat message, so a URL-only command silently sends whatever trailing text followed the slash (often nothing, opening no browser) and an agent-switching command sends its instruction to the *current* agent instead of the target. The **CLI** (`docker agent run agent.yaml /command`) switches agents like the full TUI, but has no browser to open, so `url` has no effect there. The **HTTP API** (`POST /api/sessions/:id/agent/:agent`) resolves agent-switching commands server-side: if the message content starts with a slash command whose `agent` field is set, the active agent is switched and the message is rewritten before the turn runs. Prompt-only and URL commands are not resolved server-side and pass through to the model unchanged.\n\n## Prompt Commands\n\nThe simplest form: a string value that becomes the instruction sent to the current agent.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A system administrator assistant.\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\nFor more control, use the object form with an `instruction:` field, plus an optional `description:` shown in completion dialogs and help text:\n\n```yaml\ncommands:\n  deploy:\n    description: \"Deploy the application to staging\"\n    instruction: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n```\n\nCommands support JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation, with optional `||` defaults and ternary expressions — the same syntax as agent `instruction` and `description`. Undefined variables expand to the empty string. See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full picture.\n\nPrompt commands can also reference the text typed after the slash and call tools, using the same `${...}` expansion engine as `${env.VAR}`:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments (whitespace-tokenized; quoted substrings keep their spaces together).\n- `${args}` or `${args.join(\" \")}` — the full argument list.\n- `${tool_name({key: value, ...})}` — calls an agent tool and inlines its output. JS expressions are evaluated before tool commands, so tool output is never itself re-evaluated as JS.\n- `` !tool_name(key=value) `` — legacy bang syntax for the same tool-call inlining; still supported alongside `${tool_name({...})}`.\n\nIf `instruction` uses none of the `${args...}` placeholders, any text typed after the slash is appended to the resolved instruction automatically.\n\n```yaml\ncommands:\n  fix:\n    description: \"Fix a file, with optional extra options\"\n    instruction: \"Fix the file ${args[0]} with options ${args[1]}\"\n  run:\n    description: \"Run a command with all the typed arguments\"\n    instruction: 'Run command with args: ${args.join(\" \")}'\n  lint:\n    description: \"Show the current lint output\"\n    instruction: 'Lint: ${shell({cmd: \"task lint\"})}'\n```\n\n```bash\n# Run commands from the CLI too\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ docker agent run agent.yaml /fix main.go --verbose\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\n## URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — standard web URLs and custom schemes such as `docker-desktop://` for deep links.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\n> [!NOTE]\n> **Full TUI only**\n>\n> URL commands only open a browser in the full TUI. The CLI and lean TUI don't check the `url` field at all, so `docker agent run agent.yaml /docs` never opens a browser there — but the command is still dispatched: its resolved text (usually empty, for a URL-only command) is sent as a prompt and can trigger a model turn.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Agent-Switching Commands\n\nA command with an `agent` field switches the active agent for the rest of the conversation. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the right specialist.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(' ')}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\nWhen `agent` is set **without** `instruction`, any text typed after the slash command (e.g. `/review fix the auth bug`) is forwarded as a prompt to the target agent. When both are set, the agent is switched first, then the instruction is sent to the new agent. Either way, the target can be **any agent defined in the team**, not just one of the current agent's own `sub_agents` — `sub_agents` above is shown because `planner` and `reviewer` also happen to be delegation targets, not because `agent:` requires it.\n\nAgent switching stays in the same session — the target agent sees the full conversation history, and the user must explicitly switch back (there's no automatic return). This is different from the two other ways agents hand off work:\n\n| | Agent-switching command | `handoff` tool | `transfer_task` |\n| --- | --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` | Model calls `transfer_task()` |\n| **Session** | Stays in the same session | Stays in the same session | Launches an isolated sub-session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation | Child runs in isolation; only the result returns |\n| **Control** | User must explicitly switch back | Target agent can chain to another agent | Root agent stays in control |\n\nUse `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n## Reusable Command Groups\n\nRepeated command sets across agents can be hoisted into the top-level `commands:` section and pulled in by name with `use_commands:` — the same reuse pattern as `mcps:` for MCP servers and `toolsets:` for shared toolsets.\n\n```yaml\ncommands:\n  ci:\n    deploy: \"Deploy the application\"\n    test: \"Run the test suite\"\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Lead developer\n    instruction: You are the lead developer. Coordinate the team.\n    use_commands: [ci]      # reuse the \"ci\" command group\n    commands:\n      lint: \"Run the linter\"  # inline command, merged in (wins on conflict)\n\n  docs-writer:\n    model: anthropic/claude-sonnet-4-5\n    description: Documentation writer\n    instruction: You write and maintain the project documentation.\n    use_commands: [ci]      # same group, reused without duplication\n```\n\nAn agent's own inline `commands:` entries take precedence over merged `use_commands:` entries on name conflicts. See [`examples/shared-commands-skills.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared-commands-skills.yaml) for a complete example that also covers the equivalent `skills:` / `use_skills:` pattern.\n\n## Hiding Commands\n\nUse `--disable-commands` to hide and disable specific slash commands in the TUI — built-in ones (`/cost`, `/eval`, `/model`, …) or your own named ones. Accepts a comma-separated list; the leading slash is optional and matching is case-insensitive.\n\n```bash\n$ docker agent run agent.yaml --disable-commands=\"/cost,/eval,/model\"\n```\n\nThis is useful for shipping a distributed agent with a narrower command surface — for example, hiding `/model` so a published agent always runs its intended model.\n\n## Built-in Commands\n\nThe TUI ships its own slash commands (`/new`, `/compact`, `/sessions`, `/settings`, …) alongside whatever an agent defines. See [Slash Commands](../../features/tui/index.md#slash-commands) in the TUI reference for the full list.\n\n## Command Configuration Reference\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `description` | string | Shown in completion dialogs and help text. |\n| `instruction` | string | The prompt sent to the agent. Supports argument expansion (`${args[0]}`, `${args.join(\" \")}`, …), tool calls (`${tool_name({...})}`), and the legacy bang syntax `!tool_name(...)`. |\n| `agent` | string | Name of an agent in the team to switch to when this command is invoked — any agent in the team's `agents:` map, not just one of the current agent's `sub_agents`. When set without `instruction`, any text typed after the slash command is forwarded as a prompt to the target agent. |\n| `url` | string | URL to open in the user's default browser when this command is invoked, instead of sending a prompt to the agent (full TUI only — see [URL Commands](#url-commands)). The token `{{session_id}}` is replaced at invocation time with the current session ID (URL-query-escaped). |\n\n`instruction` and `agent` can be combined (the agent is switched first, then the instruction is sent to the new agent). In the full TUI, if `url` is set, it takes precedence over `agent` and `instruction` — the command only opens the browser; the lean TUI and CLI don't check `url` at all, so a URL-only command instead sends its (usually empty) resolved text as a prompt. See [Behavior differs by frontend](#what-slash-commands-are) above. The simple string form is shorthand for `{ instruction: \"...\" }`.\n","frontmatter":{"title":"Custom Commands","description":"Define slash commands that send prompts, open URLs, or switch agents, and reuse them across agents with top-level command groups.","keywords":"docker agent, ai agents, configuration, yaml, custom commands, slash commands","linkTitle":"Custom Commands","weight":55,"canonical":"https://docs.docker.com/ai/docker-agent/configuration/commands/"},"isInternal":false,"tokens":2993,"sizeBytes":13103}],"systemPromptSnippet":"<agent_rules repository=\"docker/docs\">\n\n<!-- Skill/Rule: AI Agent Protocol & Instructions (AGENTS.md) -->\n# AGENTS.md\n\nInstructions for AI agents working on Docker documentation.\nThis site builds https://docs.docker.com/ using Hugo.\n\n## Project structure\n\n```text\ncontent/          # Documentation source (Markdown + Hugo front matter)\n├── manuals/      # Product docs (Engine, Desktop, Hub, etc.)\n├── guides/       # Task-oriented guides\n├── reference/    # API and CLI reference\n└── includes/     # Reusable snippets\nlayouts/          # Hugo templates and shortcodes\ndata/             # YAML data files (CLI reference, etc.)\nassets/           # CSS (Tailwind v4) and JS (Alpine.js)\nstatic/           # Images, fonts\n_vendor/          # Vendored Hugo modules (read-only)\n```\n\n## URL prefix stripping\n\nThe `/manuals` prefix is stripped from published URLs:\n`content/manuals/desktop/install.md` becomes `/desktop/install/` on the live\nsite.\n\nWhen writing internal cross-references in source files, keep the `/manuals/`\nprefix in the path — Hugo requires the full source path. The stripping only\naffects the published URL, not the internal link target. Anchor links must\nexactly match the generated heading ID (Hugo lowercases and slugifies\nheadings).\n\n## Vendored content (do not edit)\n\nContent in `_vendor/` and CLI reference data in `data/cli/` are vendored\nfrom upstream repos. Content pages under `content/reference/cli/` are\ngenerated from `data/cli/` YAML. Do not edit any of these files — changes\nmust go to the source repository:\n\n| Content | Source repo |\n|---------|-------------|\n| CLI reference (`docker`, `docker build`, etc.) | docker/cli |\n| Buildx reference | docker/buildx |\n| Compose reference | docker/compose |\n| Model Runner reference | docker/model-runner |\n| Dockerfile reference | moby/buildkit |\n| Engine API reference | moby/moby |\n| AI Governance API (`content/reference/api/ai-governance/api.yaml`) | docker/governor-services (private) |\n\nIf a validation failure or broken link traces back to vendored content, note\nthe upstream repo that needs fixing. Do not attempt to fix it locally.\n\n`content/reference/api/ai-governance/api.yaml` is a verbatim copy of the\nupstream `openapi.yaml` — do not edit it by hand. Re-vendor it with\n`hack/sync-governance-api.sh`, which fetches the latest spec from the private\n`docker/governor-services` repo (using your own `gh` auth).\n\n## Writing guidelines\n\nRead and follow [STYLE.md](STYLE.md) and [COMPONENTS.md](COMPONENTS.md).\nThese contain all style rules, shortcode syntax, and front matter requirements.\n\n### Style violations to avoid\n\nEvery piece of writing must avoid these words and patterns (enforced by Vale):\n\n- Hedge words: \"simply\", \"easily\", \"just\", \"seamlessly\"\n- Meta-commentary: \"it's worth noting\", \"it's important to understand\"\n- \"allows you to\" or \"enables you to\" — use \"lets you\" or rephrase\n- \"we\" — use \"you\" or \"Docker\"\n- \"click\" — use \"select\"\n- Bold for emphasis or product names — only bold UI elements\n- Time-relative language: \"currently\", \"new\", \"recently\", \"now\"\n\n### Version-introduction notes\n\nExplicit version anchors (\"Starting with Docker Desktop version X...\") are\ndifferent from time-relative language — they mark when a feature was\nintroduced, which is permanently true.\n\n- Recent releases (~6 months): leave version callouts in place\n- Old releases: consider removing if the callout adds little value\n- When in doubt, keep the callout and flag for maintainer review\n\n### Vale gotchas\n\n- Use lowercase \"config\" in prose — `vale.Terms` flags a capital-C \"Config\"\n\n### Updating the vocabulary\n\nIf Vale flags a legitimate tech term, product name, or compound identifier\nas a misspelling, add it to `_vale/config/vocabularies/Docker/accept.txt`.\nThis is optional — only update when a real new term is missing, not to\nsilence individual violations.\n\n- Use the canonical form for case-sensitive product names (`PyTorch`,\n  `GitHub`, `Kubernetes`, `BuildKit`). `Vale.Terms` enforces that exact\n  case across the docs.\n- Use `[Aa]bcd` character-class regex for words that legitimately appear\n  in multiple cases (e.g., sentence-starting capitalization, or a name\n  that's also a generic noun). This covers spelling without enforcing\n  a single canonical form.\n- Avoid broad regex patterns — entries that match many words at once\n  (especially with `(?i)`) suppress other rule checks on every match.\n- Don't add a wrong-cased entry to silence one false positive — it\n  cascades into `Vale.Terms` violations on every correct usage.\n\n## Alpine.js patterns\n\nDo not combine Alpine's `x-show` with the HTML `hidden` attribute on the\nsame element. `x-show` toggles inline `display` styles, but `hidden` applies\n`display: none` via the user-agent stylesheet — the element stays hidden\nregardless of `x-show` state. Use `x-cloak` for pre-Alpine hiding instead.\nThe site defines `[x-cloak=\"\"] { display: none !important }` in `global.css`.\n\n## Front matter requirements\n\nEvery content page under `content/` requires:\n\n- `title:` — page title\n- `description:` — short description for SEO/previews\n- `keywords:` — list of search keywords\n\nAdditional common fields:\n\n- `linkTitle:` — sidebar label (keep under 30 chars)\n- `weight:` — ordering within a section\n\n## Hugo shortcodes\n\nShortcodes are defined in `layouts/shortcodes/`. Syntax reference is in\nCOMPONENTS.md. Wrong shortcode syntax fails silently during build but\nproduces broken HTML — always check COMPONENTS.md for correct syntax.\n\n## Commands\n\n```sh\nnpx --no-install rumdl fmt <file>  # Format Markdown before committing\nnpx prettier --write <file>        # Format non-Markdown files\nscripts/lint.sh <file>...          # Lint specific files (rumdl + Vale)\ndocker buildx bake validate        # Run all validation checks\ndocker buildx bake lint            # Markdown linting only\ndocker buildx bake vale            # Style guide checks only\ndocker buildx bake test            # HTML and link checking\n```\n\nFor incremental work, prefer `scripts/lint.sh` over the `bake` targets —\nit runs the same checks on just the files you pass, so the output stays\nscoped to your changes instead of the whole repo.\n\n### Validation in git worktrees\n\n`docker buildx bake validate` fails in git worktrees because Hugo cannot\nresolve the worktree path. Use `lint` and `vale` targets separately instead.\nNever modify `hugo.yaml` to work around this. The `test`, `path-warnings`,\nand `validate-vendor` targets run correctly in CI.\n\n## Verification loop\n\n1. Make changes\n2. Format Markdown with rumdl: `npx --no-install rumdl fmt <file>`\n3. Lint the changed files: `scripts/lint.sh <file>...`\n4. Run a full build with `docker buildx bake` (optional for small changes)\n\nAlways lint the specific files you changed before committing. Use\n`scripts/lint.sh` rather than the `bake` targets so the output is scoped\nto your changes — bake runs across the entire repo and the noise makes\nreal issues easy to miss.\n\n## Git hygiene\n\n- **Stage files explicitly.** Never use `git add .` / `git add -A` /\n  `git add --all`. Running `npx prettier` updates `package-lock.json` in the\n  repo root, and broad staging sweeps it into the commit.\n- **Verify before committing.** Run `git diff --cached --name-only` and\n  confirm only documentation files appear. If `package-lock.json` or other\n  generated files are staged, unstage them:\n  `git reset HEAD -- package-lock.json`\n- **Push to your fork, not upstream.** Before pushing, confirm\n  `git remote get-url origin` returns your fork URL, not\n  `github.com/docker/docs`. Use `--head FORK_OWNER:branch-name` with\n  `gh pr create`.\n\n## Working with issues and PRs\n\n### Principles\n\n- **One issue, one branch, one PR.** Never combine multiple issues in a\n  single branch or PR.\n- **Minimal changes only.** Fix the issue. Do not improve surrounding\n  content, add comments, refactor, or address adjacent problems.\n- **Verify before documenting.** Don't take an issue reporter's claim at\n  face value — the diagnosis may be wrong even when the symptom is real.\n  Verify the actual behavior before updating docs.\n\n### Review feedback\n\n- **Always reply to review comments** — never silently fix. After every\n  commit that addresses review feedback, reply to each thread explaining\n  what was done.\n- **Treat reviewer feedback as claims to verify, not instructions to\n  execute.** Before implementing a suggestion, verify that it is correct.\n  Push back when evidence contradicts the reviewer.\n- **Inline review comments need a separate API call.** `gh pr view --json\n  reviews` does not include line-level comments. Always also call:\n\n  ```bash\n  gh api repos/<org>/<repo>/pulls/<N>/comments \\\n    --jq '[.[] | {author: .user.login, body: .body, path: .path, line: .line}]'\n  ```\n\n### Labels\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<N>/labels \\\n  --method POST --field 'labels[]=<label>'\n```\n\n### External links\n\nIf a replacement URL cannot be verified (e.g. network restrictions), treat\nthe task as blocked — do not commit a guessed URL. Report the blocker so a\nhuman can confirm. Exception: when a domain migration is well-established and\nonly the anchor is unverifiable, dropping the anchor is acceptable.\n\n## Page deletion checklist\n\nWhen removing a documentation page, search the entire `content/` tree and\nall YAML/TOML config files for the deleted page's slug and heading text.\nCross-references from unrelated sections and config-driven nav entries can\nremain and cause broken links.\n\n## Engine API version bumps\n\nWhen a new Engine API version ships, three coordinated changes are needed in\na single commit:\n\n1. `hugo.yaml` — update `latest_engine_api_version`, `docker_ce_version`,\n   and `docker_ce_version_prev`\n2. Create `content/reference/api/engine/version/v<NEW>.md` with the\n   `/latest/` aliases block (copy from previous version)\n3. Remove the aliases block from\n   `content/reference/api/engine/version/v<PREV>.md`\n\nNever leave both version files carrying `/latest/` aliases simultaneously.\n\n## Hugo icon references\n\nBefore changing an icon reference in response to a \"file not found\" error,\nverify the file actually exists via Hugo's virtual filesystem. Files may\nexist in `node_modules/@material-symbols/svg-400/rounded/` but not directly\nin `assets/icons/`. Check both locations before concluding an icon is\nmissing.\n\n## Self-improvement\n\nAfter completing work that reveals a non-obvious pattern or repo quirk not\nalready documented here, propose an update to this file. For automated\nsessions, note the learning in a comment on the issue. For human-supervised\nsessions, discuss with the user whether to update CLAUDE.md directly.\n\n\n<!-- Skill/Rule: Claude Agent Guidelines & System Prompt (CLAUDE.md) -->\nAGENTS.md\n\n<!-- Skill/Rule: Tools Skill (_vendor/github.com/docker/docker-agent/docs/concepts/tools/index.md) -->\n---\ntitle: \"Tools\"\ndescription: \"Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more.\"\nkeywords: docker agent, ai agents, concepts, tools\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/tools/\n---\n\n_Tools give agents the ability to interact with the world — read files, run commands, search the web, query databases, and more._\n\n## How Tools Work\n\nWhen an agent needs to perform an action, it makes a **tool call**. The Docker Agent runtime executes the tool and returns the result to the agent, which can then use it to continue its work.\n\n1. Agent receives a user message\n2. Agent decides it needs to use a tool (e.g., read a file)\n3. Docker Agent executes the tool and returns the result\n4. Agent incorporates the result and responds\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing tools that have side effects (shell commands, file writes). Use `--yolo` to auto-approve all tool calls.\n\n## Built-in Tools\n\nDocker Agent ships with several built-in tools that require no external dependencies. Each is enabled by adding its `type` to the agent's `toolsets` list:\n\n| Tool | Description |\n| --- | --- |\n| [Filesystem](../../tools/filesystem/index.md) | Read, write, list, search, and navigate files and directories |\n| [Shell](../../tools/shell/index.md) | Execute shell commands synchronously |\n| [Background Jobs](../../tools/background-jobs/index.md) | Run and manage long-running shell commands |\n| [Think](../../tools/think/index.md) | Step-by-step reasoning scratchpad for planning and decision-making |\n| [Todo](../../tools/todo/index.md) | Task list management for complex multi-step workflows |\n| [Tasks](../../tools/tasks/index.md) | Persistent task database shared across sessions |\n| [Memory](../../tools/memory/index.md) | Persistent key-value storage backed by SQLite |\n| [Fetch](../../tools/fetch/index.md) | Read content from HTTP/HTTPS URLs (GET only) |\n| [Script](../../tools/script/index.md) | Define custom shell scripts as named tools |\n| [LSP](../../tools/lsp/index.md) | Connect to Language Server Protocol servers for code intelligence |\n| [API](../../tools/api/index.md) | Create custom tools that call HTTP APIs without writing code |\n| [OpenAPI](../../tools/openapi/index.md) | Generate tools from an OpenAPI 3.x document |\n| [RAG](../../tools/rag/index.md) | Retrieval-augmented generation over indexed sources |\n| [Model Picker](../../tools/model-picker/index.md) | Let the agent pick between several models per turn |\n| [User Prompt](../../tools/user-prompt/index.md) | Ask users questions and collect interactive input |\n| [Open URL](../../tools/open-url/index.md) | Open a fixed URL in the user's default browser |\n| [Transfer Task](../../tools/transfer-task/index.md) | Delegate tasks to sub-agents (auto-enabled with `sub_agents`) |\n| [Background Agents](../../tools/background-agents/index.md) | Dispatch work to sub-agents concurrently |\n| [Handoff](../../tools/handoff/index.md) | Hand the conversation off to another local agent in the same config (auto-enabled with `handoffs:`) |\n| [A2A](../../tools/a2a/index.md) | Connect to remote agents via the Agent-to-Agent protocol |\n| [MCP Catalog](../../tools/mcp-catalog/index.md) | Discover and activate remote MCP servers from the Docker MCP Catalog on demand |\n| [Git](../../tools/git/index.md) | Read-only git repository inspection |\n| [Scheduler](../../tools/scheduler/index.md) | Schedule instructions to run at a time or on a recurring interval |\n| [Webhook](../../tools/webhook/index.md) | Outbound notifications to Slack, Discord, Telegram, IFTTT, and more |\n| [Plan](../../tools/plan/index.md) | Shared persistent scratchpad for multi-agent collaboration |\n| [Session Plan](../../tools/session_plan/index.md) | Per-session plan tracker for the draft/review/execute workflow |\n| [Session Context](../../tools/session_context/index.md) | Reference a previous session as context |\n\n## MCP Tools\n\nDocker Agent supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for extending agents with external tools. There are three ways to connect MCP tools:\n\n- **Docker MCP** (recommended) — Run MCP servers in Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway). Browse the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n- **Local MCP (stdio)** — Run MCP servers as local processes communicating over stdin/stdout.\n- **Remote MCP (Streamable HTTP / SSE)** — Connect to MCP servers running on a network. See [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n```\n\nSee [Tool Config](../../configuration/tools/index.md#mcp-tools) for full MCP configuration reference.\n\n> [!TIP]\n> **See also**\n>\n> For full configuration reference, see [Tool Config](../../configuration/tools/index.md).\n\n\n<!-- Skill/Rule: Tools Skill (_vendor/github.com/docker/docker-agent/docs/configuration/tools/index.md) -->\n---\ntitle: \"Tool Configuration\"\ndescription: \"Complete reference for configuring built-in tools, MCP tools, and Docker-based tools.\"\nkeywords: docker agent, ai agents, configuration, yaml, tool configuration\nlinkTitle: \"Tool Config\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/tools/\naliases:\n  - /ai/docker-agent/reference/toolsets/\n---\n\n_Complete reference for configuring built-in tools, MCP tools, and Docker-based tools._\n\n## Built-in Tools\n\nBuilt-in tools are included with Docker Agent and require no external dependencies. Add them to your agent's `toolsets` list by `type`. Each tool's dedicated page covers its full configuration options, available operations, and examples.\n\n| Type | Description | Page |\n| --- | --- | --- |\n| `filesystem` | Read, write, list, search, navigate | [Filesystem](../../tools/filesystem/index.md) |\n| `git` | Read-only repository inspection (status, log, branches, show, blame) | [Git](../../tools/git/index.md) |\n| `shell` | Execute shell commands synchronously | [Shell](../../tools/shell/index.md) |\n| `background_jobs` | Run and manage long-running shell commands | [Background Jobs](../../tools/background-jobs/index.md) |\n| `scheduler` | Schedule instructions to run at a time or on a recurring interval | [Scheduler](../../tools/scheduler/index.md) |\n| `think` | Reasoning scratchpad | [Think](../../tools/think/index.md) |\n| `plan` | Shared persistent scratchpad for multi-agent collaboration | [Plan](../../tools/plan/index.md) |\n| `session_plan` | Per-session markdown plan for the draft-review-execute workflow | [Session Plan](../../tools/session_plan/index.md) |\n| `session_context` | Reference a previous session as context (read-only) | [Session Context](../../tools/session_context/index.md) |\n| `todo` | Task list management | [Todo](../../tools/todo/index.md) |\n| `memory` | Persistent key-value storage (SQLite) | [Memory](../../tools/memory/index.md) |\n| `tasks` | Persistent task database shared across sessions | [Tasks](../../tools/tasks/index.md) |\n| `fetch` | HTTP `GET` requests with text/markdown/html output | [Fetch](../../tools/fetch/index.md) |\n| `script` | Custom shell scripts as tools | [Script](../../tools/script/index.md) |\n| `lsp` | Language Server Protocol integration | [LSP](../../tools/lsp/index.md) |\n| `api` | Custom HTTP API tools | [API](../../tools/api/index.md) |\n| `openapi` | Import every operation of an OpenAPI 3.x document as tools | [OpenAPI](../../tools/openapi/index.md) |\n| `rag` | Retrieval-augmented generation over indexed sources | [RAG](../../tools/rag/index.md) |\n| `model_picker` | Let the agent pick between several models per turn | [Model Picker](../../tools/model-picker/index.md) |\n| `user_prompt` | Interactive user input | [User Prompt](../../tools/user-prompt/index.md) |\n| `open_url` | Open a fixed URL in the user's default browser | [Open URL](../../tools/open-url/index.md) |\n| `transfer_task` | Delegate to sub-agents (auto-enabled) | [Transfer Task](../../tools/transfer-task/index.md) |\n| `background_agents` | Parallel sub-agent dispatch | [Background Agents](../../tools/background-agents/index.md) |\n| `webhook` | Reliable notifications to a configured destination, with retries (Slack, Discord, Telegram, IFTTT, Teams, …) | [Webhook](../../tools/webhook/index.md) |\n| `handoff` | Local conversation handoff to another agent in the same config (auto-enabled by `handoffs:`) | [Handoff](../../tools/handoff/index.md) |\n| `a2a` | A2A remote agent connection | [A2A](../../tools/a2a/index.md) |\n| `mcp_catalog` | Discover and activate remote MCP servers from the Docker MCP Catalog on demand | [MCP Catalog](../../tools/mcp-catalog/index.md) |\n\n**Example:**\n\n```yaml\ntoolsets:\n  - type: filesystem\n  - type: shell\n  - type: background_jobs\n  - type: think\n  - type: todo\n  - type: memory\n    path: ./dev.db\n```\n\n## MCP Tools\n\nExtend agents with external tools via the [Model Context Protocol](https://modelcontextprotocol.io/). For a standalone overview of the `mcp` toolset see the [MCP tool page](../../tools/mcp/index.md).\n\n> [!TIP]\n> **Reusable MCP definitions**\n>\n> Repeated MCP server definitions can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`. See [Reusable MCP Servers](../overview/index.md#reusable-mcp-servers-mcps).\n\n### Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the [MCP Gateway](https://github.com/docker/mcp-gateway):\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo # web search\n  - type: mcp\n    ref: docker:github-official # GitHub integration\n```\n\nBrowse available tools at the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`)                             |\n| `tools`       | array  | Optional: only expose these tools                                |\n| `instruction` | string | Custom instructions injected into the agent's context            |\n| `config`      | any    | MCP server-specific configuration (passed during initialization) |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n\n### Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `command` | string | Command to execute the MCP server |\n| `args` | array | Command arguments |\n| `tools` | array | Optional: only expose these tools |\n| `env` | object | Environment variables (key-value pairs) |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context |\n| `version` | string | Package reference for [auto-installing](#auto-installing-tools) the command binary |\n\n### Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp-server.example.com\"\n      transport_type: \"streamable\"\n      headers:\n        Authorization: \"Bearer your-token\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: true\n    tools: [\"search_web\", \"fetch_url\"]\n```\n\n| Property                | Type    | Description                                                                                                           |\n| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |\n| `remote.url`            | string  | URL of the MCP server. Accepts `https://`, `http://`, and `unix://` (Unix domain socket) schemes.                     |\n| `remote.transport_type` | string  | `streamable` or `sse`                                                                                                 |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. `${env.VAR}` reads an environment variable; `${headers.NAME}` forwards a header from the caller's incoming request (useful when Docker Agent runs as an API server). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers.        |\n\n## Auto-Installing Tools\n\nWhen configuring MCP or LSP tools that require a binary command, Docker Agent can **automatically download and install** the command if it's not already available on your system. This uses the [aqua registry](https://github.com/aquaproj/aqua-registry) — a curated index of CLI tool packages.\n\n### How It Works\n\n1. When a toolset with a `command` is loaded, Docker Agent checks if the command is available in your `PATH`\n2. If not found, it checks the Docker Agent tools directory (`~/.cagent/tools/bin/`)\n3. If still not found, it looks up the command in the aqua registry and installs it automatically\n\n### Explicit Package Reference\n\nUse the `version` property to specify exactly which package to install:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls\n    version: \"golang/tools@v0.21.0\"\n    args: [\"mcp\"]\n  - type: lsp\n    command: rust-analyzer\n    version: \"rust-lang/rust-analyzer@2024-01-01\"\n    file_types: [\".rs\"]\n```\n\nThe format is `owner/repo` or `owner/repo@version`. When a version is omitted, the latest release is used.\n\n### Automatic Detection\n\nIf the `version` property is not set, Docker Agent tries to auto-detect the package from the command name by searching the aqua registry:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: gopls  # auto-detected as golang/tools\n    args: [\"mcp\"]\n```\n\n### Checksum Verification\n\nWhere the aqua registry includes a checksum manifest, downloaded binaries are verified against it before installation. Verification behaviour depends on the checksum type advertised:\n\n- **Strong checksums (sha256, sha512, etc.)** — verified before the binary is installed. If the downloaded archive does not match, the install is aborted and an error is returned (fails closed).\n- **Unsupported or weak checksum types (e.g. md5, sha1)** — skipped with a warning; installation proceeds without verification.\n- **No manifest** — if no checksum is advertised in the registry entry, the binary is installed without verification.\n\n### version_overrides Resolution\n\nThe auto-installer correctly resolves **`version_overrides`** entries in the aqua registry. Many common tools (for example, `fzf`) keep their package configuration — including download URLs and checksums — under `version_overrides` rather than at the top level of their registry entry. These tools previously failed to install silently; they are now handled correctly.\n\n### Disabling Auto-Install\n\n**Per toolset** — set `version` to `\"false\"` or `\"off\"`:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: my-custom-server\n    version: \"false\"\n```\n\n**Globally** — set the `DOCKER_AGENT_AUTO_INSTALL` environment variable:\n\n```bash\nexport DOCKER_AGENT_AUTO_INSTALL=false\n```\n\n### Environment Variables\n\n| Variable                     | Default            | Description                                      |\n| ---------------------------- | ------------------ | ------------------------------------------------ |\n| `DOCKER_AGENT_AUTO_INSTALL`  | (enabled)          | Set to `false` to disable all auto-installation  |\n| `DOCKER_AGENT_TOOLS_DIR`     | `~/.cagent/tools/` | Base directory for installed tools               |\n| `GITHUB_TOKEN`               | —                  | GitHub token to raise API rate limits (optional) |\n\nInstalled binaries are placed in `~/.cagent/tools/bin/` and cached so they are only downloaded once.\n\n> [!TIP]\n> Auto-install supports both Go packages (via `go install`) and GitHub release binaries (via archive download). The aqua registry metadata determines which method is used.\n\n## Toolset Lifecycle\n\nLong-running toolsets — local MCP servers (stdio), remote MCP servers (Streamable HTTP / SSE), and LSP servers — are managed by a single supervisor that can auto-reconnect them when they crash, time out, or drop their session. The `lifecycle` block on the toolset lets you tune that supervisor per toolset. It applies to every `type: mcp` and `type: lsp` toolset.\n\nThe simplest knob is `profile`, which picks a preset:\n\n| Profile | Auto-restart | Use case |\n| --- | --- | --- |\n| `resilient` | Yes | Default. Exponential backoff on disconnect; the agent keeps running if the toolset is unavailable. Matches the historical Docker Agent behaviour. |\n| `strict` | No | Fail-fast. Marks the toolset as required. Intended for CI / headless runs where a missing dependency should be a hard error. |\n| `best-effort` | No | Single attempt, no retries. Good for experimental MCPs whose flakiness should not amplify into a restart loop. |\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; shown here for clarity\n\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: strict\n\n  - type: mcp\n    ref: docker:openbnb-airbnb\n    lifecycle:\n      profile: best-effort\n```\n\n### Tuning the defaults\n\nAny field set on `lifecycle` overrides the profile preset, so you can mix-and-match: pick a profile and only override the knobs you care about.\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: [\"docker\", \"mcp\", \"gateway\"]\n    lifecycle:\n      profile: resilient\n      max_restarts: 10        # keep trying longer than the default of 5\n      backoff:\n        initial: 500ms\n        max: 1m\n        multiplier: 2\n        jitter: 0.2           # 20% random offset to avoid thundering-herd retries\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `profile` | string | One of `resilient` (default), `strict`, `best-effort`. Picks defaults for every other field. |\n| `restart` | string | When the supervisor should reconnect after a disconnect: `never`, `on_failure` (default), or `always`. For **remote** MCP toolsets (Streamable HTTP / SSE), `on_failure` is automatically promoted to `always` so idle-timeout closes reconnect gracefully — `never` is still honored. |\n| `max_restarts` | int | Maximum consecutive restart attempts before the toolset is marked `Failed`. `0` uses the profile default (5); `-1` means unlimited. |\n| `backoff.initial` | duration | First wait between attempts (Go duration: `500ms`, `1s`, …). Default: `1s`. |\n| `backoff.max` | duration | Cap on the wait between attempts. Default: `32s`. |\n| `backoff.multiplier` | number | Multiplier applied each attempt. Default: `2`. |\n| `backoff.jitter` | number | Fraction (0..1) of the computed delay applied as a uniform random offset. `0` disables jitter (default). |\n| `required` | boolean | Marks the toolset as critical. Today this is informational; a future eager-startup phase will refuse to start the agent when a required toolset cannot reach Ready. Defaults to `true` under `strict`, `false` otherwise. |\n| `startup_timeout` | duration | Cap on the initial connect+initialize duration. Enforced since v1.94.0: on expiry the toolset stays stopped and the runtime retries on the next turn. |\n| `call_timeout` | duration | Cap on an individual tool call, including one reconnect-retry. Enforced: on expiry the call is cancelled and surfaced to the model as a tool error; cancellation is propagated to the server. `0`/unset means no timeout — opt-in only, no profile default. |\n\n> [!NOTE]\n> **`required` is not yet enforced**\n>\n> The schema validates this field and the supervisor stores it, but no code path acts on it yet. It is documented now so config files written today keep working when the planned eager-startup phase lands. Picking the `strict` profile is forward-compatible — it will start enforcing `required=true` automatically.\n\n### Inspecting and restarting toolsets at runtime\n\nThe TUI exposes the supervisor through two slash commands:\n\n- `/tools` — the unified tools dialog. Its top section lists every toolset on the current agent with its lifecycle state (`Stopped`, `Starting`, `Ready`, `Degraded`, `Restarting`, `Failed`), restart count, and last error; its bottom section lists every tool the agent can call, grouped by category. Use this to answer both \"what can the agent do?\" and \"is anything degraded?\" with one command.\n- `/toolset-restart <name>` — force the supervisor to reconnect the named toolset. Useful after completing OAuth, when a remote MCP server has been redeployed, or when an LSP like `gopls` is stuck.\n\nSee the [TUI reference](../../features/tui/index.md) for the full list of slash commands.\n\nSee [`examples/lifecycle.yaml`](https://github.com/docker/docker-agent/blob/main/examples/lifecycle.yaml) for a complete lifecycle configuration example.\n\n## TOON-Encoded Tool Outputs\n\nMany MCP servers return verbose JSON responses that consume a lot of context budget. The `toon` field on a toolset transparently re-encodes matching tools' JSON output as [TOON](https://github.com/alpkeskin/gotoon) — a compact, model-friendly key/value format — before the result is shown to the model.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"          # toonify every tool from this MCP server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `toon`   | string | Comma-delimited list of regular expressions matching tool names whose JSON output should be re-encoded as TOON. Non-JSON outputs and non-matching tools are passed through untouched. |\n\nWhen a tool's output is not valid JSON, it is returned unchanged — TOON encoding is best-effort and never breaks tools that emit plain text.\n\n> [!NOTE]\n> **When to use TOON**\n>\n> TOON typically yields 30-60% smaller payloads than equivalent JSON for MCP tools that return arrays of records (issue lists, search results, file listings, …). It works best when the schema is regular; one-off responses with deeply nested or heterogeneous shapes may benefit less.\n\n## Per-Toolset Model Routing\n\nThe `model` field on a toolset overrides which LLM is invoked for the **next turn** after a tool from that toolset returns — letting you process simple tool results (file reads, knowledge-base lookups, shell stdout) with a cheaper or faster model while keeping the agent's primary model for reasoning.\n\n```yaml\nmodels:\n  primary:\n    provider: anthropic\n    model: claude-sonnet-4-5\n  fast:\n    provider: anthropic\n    model: claude-haiku-4-5\n\nagents:\n  root:\n    model: primary\n    toolsets:\n      - type: filesystem\n        model: fast            # process file reads with the fast model\n      - type: shell\n        model: fast            # ditto for shell stdout\n      - type: mcp\n        ref: docker:github-official\n        model: openai/gpt-4o-mini  # inline provider/model also works\n```\n\n| Property | Type   | Description |\n| -------- | ------ | ----------- |\n| `model`  | string | Model used for the LLM turn that processes tool results from this toolset. Either a name from the `models:` section or an inline `provider/model` (e.g. `openai/gpt-4o-mini`). The override is **one-shot**: subsequent turns return to the agent's primary model. |\n\nWhen multiple tool calls in a single turn come from toolsets with different `model` overrides, the runtime picks the override of the **first** tool call that has one set. See [`examples/per_tool_model_routing.yaml`](https://github.com/docker/docker-agent/blob/main/examples/per_tool_model_routing.yaml) for a complete configuration.\n\n## Tool Filtering\n\nToolsets may expose many tools. Use the `tools` property to whitelist only the ones your agent needs. This works for any toolset type — not just MCP:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n  - type: filesystem\n    tools: [\"read_file\", \"search_files_content\"]\n  - type: shell\n    tools: [\"shell\"]\n```\n\n> [!TIP]\n> Filtering tools improves agent performance — fewer tools means less confusion for the model about which tool to use.\n\n## Tool Instructions\n\nAdd context-specific instructions that get injected when a toolset is loaded:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\nBy default, the `instruction:` field **replaces** the toolset's built-in instructions (if any). To keep the built-in guidance and add your own rules on top, include the `{ORIGINAL_INSTRUCTIONS}` placeholder anywhere in your instruction text. At runtime it expands to the toolset's default instructions:\n\n```yaml\ntoolsets:\n  # Enrich: keep built-in instructions, then add your own rules\n  - type: filesystem\n    instruction: |\n      {ORIGINAL_INSTRUCTIONS}\n\n      ## Project-specific rules\n      - Never modify files outside the `src/` directory.\n      - Always create a backup before overwriting a file.\n\n  # Enrich: prepend your rules before the built-in instructions\n  - type: shell\n    instruction: |\n      Important: only run commands inside the project root.\n      {ORIGINAL_INSTRUCTIONS}\n\n  # Replace: omit the placeholder to discard built-in instructions entirely\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Only read GitHub issues. Never create, edit, or close anything.\n```\n\nThree patterns at a glance:\n\n| Pattern | Description |\n| --- | --- |\n| `{ORIGINAL_INSTRUCTIONS}` then your text | Append your rules after the defaults |\n| Your text then `{ORIGINAL_INSTRUCTIONS}` | Prepend your rules before the defaults |\n| No placeholder | Replace the defaults entirely |\n\nSee [`examples/toolset_instructions.yaml`](https://github.com/docker/docker-agent/blob/main/examples/toolset_instructions.yaml) for a complete example.\n\n## Deferred Tool Loading\n\nLoad tools on-demand to speed up agent startup. When a toolset is deferred, its tools are registered lazily — the tool server process is not started until the agent first calls one of its tools. This is useful for large toolsets (e.g., an MCP server with hundreds of tools) where startup time matters.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  - type: mcp\n    ref: docker:slack\n    defer: true\n  - type: filesystem\n```\n\nOr defer specific tools within a toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer:\n      - \"list_issues\"\n      - \"search_repos\"\n```\n\nWhen `defer` is a list of tool names, only those specific tools are deferred; all other tools in the toolset load eagerly. Setting `defer: true` defers the entire toolset.\n\n### Tool Discovery with `search_tool`\n\nWhen an entire toolset is deferred (`defer: true`), the deferred toolset exposes two built-in tools to the agent:\n\n- **`search_tool`** — Discover available deferred tools by keyword. The search uses **fuzzy matching** against both tool names and descriptions: all characters of the query must appear in the target string in order (but not necessarily adjacently), so a query like `\"crfil\"` matches `\"create_file\"`. Returns a list of matching tool names with descriptions.\n- **`add_tool`** — Activate a discovered tool by name so it becomes available for use.\n\nThese tools let the agent browse a large toolset on-demand without activating every tool upfront.\n\nSee [`examples/deferred.yaml`](https://github.com/docker/docker-agent/blob/main/examples/deferred.yaml) for a complete example.\n\n## Combined Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Built-in tools\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n      - type: memory\n        path: ./dev.db\n      - type: user_prompt\n      # LSP for code intelligence\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      # Custom scripts\n      - type: script\n        shell:\n          run_tests:\n            description: Run the test suite\n            cmd: task test\n          lint:\n            description: Run the linter\n            cmd: task lint\n      # Custom API tool\n      - type: api\n        api_config:\n          name: get_status\n          method: GET\n          endpoint: \"https://api.example.com/status\"\n          instruction: Check service health\n      # Docker MCP tools\n      - type: mcp\n        ref: docker:github-official\n        tools: [\"list_issues\", \"create_issue\"]\n      - type: mcp\n        ref: docker:duckduckgo\n      # Remote MCP\n      - type: mcp\n        remote:\n          url: \"https://internal-api.example.com/mcp\"\n          transport_type: \"streamable\"\n          headers:\n            Authorization: \"Bearer ${env.INTERNAL_TOKEN}\"\n```\n\n> [!WARNING]\n> **Toolset Order Matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins. Order your toolsets intentionally.\n\n\n<!-- Skill/Rule: Skills Skill (_vendor/github.com/docker/docker-agent/docs/features/skills/index.md) -->\n---\ntitle: \"Skills\"\ndescription: \"Skills provide specialized instructions that agents can load on demand when a task matches a skill's description.\"\nkeywords: docker agent, ai agents, features, skills\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/features/skills/\n---\n\n_Skills provide specialized instructions that agents can load on demand when a task matches a skill's description._\n\n## How Skills Work\n\n1. Docker Agent scans standard directories for `SKILL.md` files\n2. Skill metadata (name, description) is injected into the agent's system prompt\n3. When a user request matches a skill, the agent reads the full instructions\n4. The agent follows the skill's detailed instructions to complete the task\n\n## Enabling Skills\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills: true\n    toolsets:\n      - type: filesystem # required for reading skill files\n```\n\n> [!TIP]\n> Skills are perfect for encoding team-specific workflows (PR review, deployment, coding standards) that apply across projects.\n\n## Filtering Skills\n\nThe `skills` field also accepts a list, letting you restrict the agent to a specific subset of skills instead of exposing every discovered one. List items are classified automatically:\n\n- `\"local\"` or any `http://` / `https://` URL → a **source** to load skills from\n- any other string → the **name** of a skill to include\n\nWhen only names are given, local sources are used by default.\n\n```yaml\nagents:\n  # Load every discovered local skill (same as `skills: true`).\n  full:\n    skills: true\n\n  # Load local skills, but only expose \"commit\" and \"poem\".\n  scoped:\n    skills:\n      - commit\n      - poem\n\n  # Combine an explicit source with a name filter.\n  remote_filtered:\n    skills:\n      - https://skills.example.com\n      - commit\n\n  # Disable skills entirely.\n  none:\n    skills: false\n```\n\nA name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored.\n\n## Inline Skills\n\nInstead of (or alongside) loading skills from files and URLs, you can define skills directly in the agent config. An inline skill is a mapping item in the `skills` list, freely mixed with the string items above:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    skills:\n      - name: changelog\n        description: Write a concise changelog entry from a diff or description.\n        instructions: |\n          Produce a single changelog entry in Keep a Changelog style.\n          Pick the right category (Added, Changed, Fixed, Removed) and write\n          one imperative sentence summarising the user-visible change.\n\n      # A fork-mode inline skill runs in an isolated sub-agent.\n      - name: triage\n        description: Triage a bug report in an isolated context.\n        context: fork\n        instructions: |\n          Restate the problem, list likely root causes most-probable-first,\n          and propose the smallest reproduction and next concrete action.\n\n      # Inline skills mix freely with sources and name filters.\n      - local\n    toolsets:\n      - type: filesystem\n```\n\nInline skills carry their body in the config itself, so they need no `SKILL.md` file and require no filesystem source. They are **always exposed** — the name filter only applies to file- and URL-based skills. Because inline skills travel inside the agent YAML, they also work in `--sandbox` mode without any kit staging, and they can be shared with the agent via `share push`.\n\n### Inline Skill Fields\n\n| Field           | Required | Description                                                                |\n| --------------- | -------- | -------------------------------------------------------------------------- |\n| `name`          | Yes      | Skill identifier used by `read_skill` / `run_skill` and the `/<name>` command |\n| `description`   | Yes      | Short description shown to the agent for skill matching                    |\n| `instructions`  | Yes      | The skill body (what a `SKILL.md` would contain below its frontmatter)     |\n| `context`       | No       | Set to `fork` to run the skill as an isolated sub-agent                    |\n| `model`         | No       | Override the model used while running a fork-mode skill                    |\n| `allowed_tools` | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (glob or exact). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`      | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session on top of the inherited tools. |\n\n> [!NOTE]\n> **Inline vs. file-based skills**\n>\n> Inline skills support the subset of the SKILL.md format that fits in YAML. They cannot bundle supporting files (no `read_skill_file`) or use `` !`command` `` expansion. For skills that need bundled resources or executable helpers, use a `SKILL.md` directory instead.\n\n## SKILL.md Format\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\nlicense: Apache-2.0\nmetadata:\n  author: my-org\n  version: \"1.0\"\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Minimize image size by using slim base images\n4. Follow security best practices (non-root user, etc.)\n```\n\n### Frontmatter Fields\n\n| Field            | Required | Description                                                                 |\n| ---------------- | -------- | --------------------------------------------------------------------------- |\n| `name`           | Yes      | Unique skill identifier                                                     |\n| `description`    | Yes      | Short description shown to the agent for skill matching                     |\n| `context`        | No       | Set to `fork` to run the skill as an isolated sub-agent (see below)         |\n| `model`          | No       | Override the model used while running the skill as a sub-agent (fork only)  |\n| `allowed-tools`  | No       | For a fork-mode skill, restricts the sub-session to the parent tools whose names match an entry (YAML list or comma-separated string). See [Scoping a fork skill's tools](#scoping-a-fork-skills-tools). |\n| `toolsets`       | No       | For a fork-mode skill, names of top-level [`toolsets`](../../configuration/overview/index.md#reusable-toolsets-toolsets) to expose in the sub-session (YAML list or comma-separated string). |\n| `license`        | No       | License identifier (e.g. `Apache-2.0`)                                      |\n| `compatibility`  | No       | Free-text compatibility notes                                               |\n| `metadata`       | No       | Arbitrary key-value pairs (e.g. `author`, `version`)                        |\n\n## Running a Skill as a Sub-Agent\n\nBy default, when an agent invokes a skill it reads the instructions inline into its own conversation. For complex, multi-step skills this can consume a large portion of the agent's context window and pollute the parent conversation with intermediate tool calls.\n\nAdding `context: fork` to the SKILL.md frontmatter tells the agent to run the skill in an **isolated sub-agent** instead:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\n---\n\n# Bump Dependencies\n\n1. List outdated deps\n2. Update each one, run tests, commit or revert\n3. Produce a summary table\n```\n\nWhen the agent encounters a task that matches a `context: fork` skill, it uses the `run_skill` tool instead of `read_skill`. This:\n\n- **Spawns a child session** with the skill content as the system prompt and the caller's task as the user message\n- **Isolates the context window** — the sub-agent has its own conversation history, so lengthy tool-call chains don't eat into the parent's token budget\n- **Folds the result** — only the sub-agent's final answer is returned to the parent as the tool result\n- **Inherits the parent's model and tools** — the sub-agent can use all tools available to the parent agent (scope this with `allowed_tools` / `toolsets`, see [Scoping a fork skill's tools](#scoping-a-fork-skills-tools))\n\n> [!TIP]\n> **When to use context: fork**\n>\n> Use `context: fork` for skills that involve many steps, heavy tool usage, or that should not clutter the main conversation — for example dependency bumping, large refactors, or code generation pipelines.\n\n### Overriding the model for a fork skill\n\nFork skills can declare a `model` field in their frontmatter to use a\ndifferent model than the parent agent for the duration of the sub-session.\nThis is useful when a skill is best handled by a faster, cheaper, or more\nspecialised model — for example a powerful reasoning model for refactors,\nor a fast model for routine bookkeeping work. The override only applies\nwhile the skill is running; the parent agent keeps its own model.\n\nThe `model` value accepts either a named model from the agent config or\nan inline `provider/model` reference (and the same comma-separated alloy\nsyntax as the rest of the agent config):\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: bump-go-dependencies\ndescription: Update Go module dependencies one by one\ncontext: fork\nmodel: openai/gpt-4o-mini\n---\n\n# Bump Dependencies\n\n1. ...\n```\n\nIf the model reference cannot be resolved (unknown name, missing\ncredentials, runtime not configured for model switching, …) the skill\nfalls back to the agent's currently-active model (its configured\ndefault, or any override the user previously set via the model picker)\nand a warning is logged.\n\nWhen the skill completes, the agent's previous model is restored — but\nonly if no one else changed the model in the meantime. If the user\nswitches the model via the TUI model picker while the fork skill is\nrunning, their choice is preserved (the deferred restore becomes a\nno-op).\n\n### Scoping a fork skill's tools\n\nBy default a fork skill inherits the parent agent's entire tool set. Two\noptional fields let you scope what the sub-session can use. Both apply\n**only to fork-mode skills** and work the same whether the skill is\ninline or loaded from a `SKILL.md` file.\n\n`allowed_tools` (frontmatter: `allowed-tools`) is an **allow-list** over\nthe inherited tools: only tools whose names match an entry are kept,\neverything else is hidden from the sub-session. Entries support glob\npatterns (e.g. `read_*`) and otherwise match exactly. This is the\nClaude-Code-compatible `allowed-tools` field, now enforced for fork\nskills rather than merely recorded.\n\n`toolsets` references reusable [top-level toolsets](../../configuration/overview/index.md#reusable-toolsets-toolsets)\nby name. The referenced toolsets are exposed in the sub-session **in\naddition to** the inherited tools, and they bypass the `allowed_tools`\nfilter (the skill explicitly asked for them).\n\n```yaml\ntoolsets:\n  web:\n    type: fetch\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: You are a helpful assistant.\n    toolsets:\n      - type: filesystem\n      - type: shell\n    skills:\n      # Inherits the parent tools but is restricted to read-only filesystem\n      # access while it runs — shell and write tools are hidden.\n      - name: audit\n        description: Review the repository layout without modifying anything.\n        context: fork\n        allowed_tools:\n          - read_file\n          - list_directory\n          - directory_tree\n        instructions: Inspect the repository structure and summarise it.\n\n      # Brings in the top-level `web` toolset on top of the parent's tools.\n      - name: research\n        description: Research a topic using web fetches in an isolated context.\n        context: fork\n        toolsets:\n          - web\n        instructions: Research the requested topic and summarise with links.\n```\n\nThe equivalent in a `SKILL.md` file uses frontmatter lists:\n\n<!-- yaml-lint:skip -->\n```yaml\n---\nname: research\ndescription: Research a topic using web fetches\ncontext: fork\nallowed-tools:\n  - fetch\ntoolsets:\n  - web\n---\n```\n\n> [!NOTE]\n> **Fork only**\n>\n> Both fields are rejected by config validation when set on a non-fork skill, and a `toolsets` entry that doesn't resolve to a top-level toolset is a load-time error.\n\n## Search Paths\n\nSkills are discovered from these locations (later overrides earlier):\n\n### Global\n\n| Path                | Search Type                             |\n| ------------------- | --------------------------------------- |\n| `~/.codex/skills/`  | Recursive (searches all subdirectories) |\n| `~/.claude/skills/` | Flat (immediate children only)          |\n| `~/.agents/skills/` | Recursive (searches all subdirectories) |\n\n### Project (from git root to current directory)\n\n| Path              | Search Type                                |\n| ----------------- | ------------------------------------------ |\n| `.claude/skills/` | Flat (cwd only)                            |\n| `.github/skills/` | Flat (each directory from git root to cwd) |\n| `.agents/skills/` | Flat (each directory from git root to cwd) |\n\n## Invoking Skills\n\nSkills can be invoked in multiple ways:\n\n- **Automatic:** The agent detects when your request matches a skill's description and loads it automatically\n- **Explicit:** Reference the skill name in your prompt: \"Use the create-dockerfile skill to...\"\n- **Slash command:** Use `/{skill-name}` to invoke a skill directly\n\n```bash\n# In the TUI, invoke skill directly:\n/create-dockerfile\n\n# Or mention it in your message:\n\"Create a dockerfile for my Python app (use the create-dockerfile skill)\"\n```\n\n## Precedence\n\nWhen multiple skills share the same name:\n\n1. Global skills load first\n2. Project skills load next, from git root toward current directory\n3. Skills closer to the current directory override those further away\n4. At the same directory level, `.agents/skills/` overrides `.github/skills/`\n\n## Skills in Sandbox Mode\n\nWhen you run an agent with [`--sandbox`](../../configuration/sandbox/index.md), the sandbox VM has its own filesystem with no access to your host's skill directories. Docker Agent handles this transparently via the [auto-kit](../../configuration/sandbox/index.md#auto-kit): every discovered local skill is staged into a per-agent kit on the host, run through best-effort secret redaction (see the [auto-kit](../../configuration/sandbox/index.md#secret-redaction) docs), and bind-mounted read-only into the sandbox so the agent sees the same skills inside the VM as on the host. No configuration is required — use `--no-kit` only if you explicitly want to run the sandbox without any host skills.\n\n## Creating a Skill\n\n```bash\n# Create the skill directory\n$ mkdir -p ~/.agents/skills/create-dockerfile\n\n# Write the SKILL.md file\n$ cat > ~/.agents/skills/create-dockerfile/SKILL.md << 'EOF'\n---\nname: create-dockerfile\ndescription: Create optimized Dockerfiles for applications\n---\n\n# Creating Dockerfiles\n\nWhen asked to create a Dockerfile:\n\n1. Analyze the application type and language\n2. Use multi-stage builds for compiled languages\n3. Use slim base images to minimize size\n4. Run as non-root user for security\nEOF\n```\n\nThe skill will automatically be available to any agent with skills enabled (`skills: true`, or a list that targets its name — see [Filtering Skills](#filtering-skills)).\n\n> [!NOTE]\n> **See also**\n>\n> Skills are enabled in the [Agent Config](../../configuration/agents/index.md) with the `skills` property (boolean or list). For tool-based capabilities, see [Tools](../../concepts/tools/index.md).\n>\n> Example configs: [`examples/skills_inline.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_inline.yaml) (inline skill definition), [`examples/skills_fork_toolsets.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_fork_toolsets.yaml) (scoping a fork skill's tools), [`examples/skills_filter.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_filter.yaml) (filtering which skills load).\n\n\n<!-- Skill/Rule: Tools Skill (_vendor/github.com/docker/docker-agent/docs/tools/_index.md) -->\n---\ntitle: \"Built-in Tools\"\ndescription: \"Built-in toolsets agents can use out of the box.\"\nweight: 40\n---\n\n\n<!-- Skill/Rule: A2a Skill (_vendor/github.com/docker/docker-agent/docs/tools/a2a/index.md) -->\n---\ntitle: \"A2A Tool\"\ndescription: \"Connect to remote agents via the Agent-to-Agent protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, a2a tool\nlinkTitle: \"A2A\"\nweight: 60\ncanonical: https://docs.docker.com/ai/docker-agent/tools/a2a/\n---\n\n_Connect to remote agents via the Agent-to-Agent protocol._\n\n## Overview\n\nThe A2A tool connects to a remote agent exposed over the A2A (Agent-to-Agent) protocol. Unlike [`handoff`](../handoff/index.md), which only targets local agents declared in the same config, `a2a` reaches out to an agent running on the network.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: a2a\n    url: \"http://localhost:8080/a2a\"\n    # Optional: custom tool name (defaults to a sanitized form of the URL / agent card name)\n    name: research_agent\n    # Optional: custom HTTP headers (typically for auth)\n    headers:\n      Authorization: \"Bearer ${env.A2A_TOKEN}\"\n      X-Tenant: \"acme\"\n```\n\nThe `Authorization` header shown above authenticates to endpoints served with `docker agent serve a2a --auth-token`.\n\n## Properties\n\n| Property   | Type             | Required | Description                                                                                              |\n| ---------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `url`      | string           | ✓        | A2A server endpoint URL (must include scheme).                                                           |\n| `name`     | string           | ✗        | Tool name registered for the remote agent. Defaults to a name derived from the server's agent card.     |\n| `headers`  | map\\[string\\]string | ✗     | Extra HTTP headers sent with every request (useful for `Authorization`, tenant selection, tracing, \\u2026). |\n\n> [!TIP]\n> **See also**\n>\n> For full details on the A2A protocol and serving agents as A2A endpoints, see [A2A Protocol](../../features/a2a/index.md).\n\n\n<!-- Skill/Rule: Api Skill (_vendor/github.com/docker/docker-agent/docs/tools/api/index.md) -->\n---\ntitle: \"API Tool\"\ndescription: \"Create custom tools that call HTTP APIs.\"\nkeywords: docker agent, ai agents, tools, toolsets, api tool\nlinkTitle: \"API\"\nweight: 240\ncanonical: https://docs.docker.com/ai/docker-agent/tools/api/\n---\n\n_Create custom tools that call HTTP APIs._\n\n## Overview\n\nThe API tool type lets you define custom tools that make HTTP requests to external APIs. This is useful for integrating agents with REST APIs, webhooks, or any HTTP-based service without writing code.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Integrating with REST APIs that don't have an MCP server\n> - Simple HTTP operations (GET, POST)\n> - Quick prototyping before building a full MCP server\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant with API access\n    instruction: You can look up weather information.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_weather\n          method: GET\n          endpoint: \"https://api.weather.example/v1/current?city=${city}\"\n          instruction: Get current weather for a city\n          args:\n            city:\n              type: string\n              description: City name to get weather for\n          required: [\"city\"]\n          headers:\n            Authorization: \"Bearer ${env.WEATHER_API_KEY}\"\n```\n\n## Properties\n\nThe `api` toolset accepts the following toolset-level fields in addition to the `api_config` block:\n\n| Property            | Type    | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `api_config`        | object  | ✓        | The HTTP tool definition. See the table below.                                                                                                                                                                                                                    |\n| `timeout`           | int     | ✗        | HTTP client timeout in seconds (default: `30`). Applies to every call the generated tool makes.                                                                                                                                                                   |\n| `allow_private_ips` | boolean | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the configured endpoint legitimately targets internal services. See [Reaching internal services](#reaching-internal-services). |\n\n### `api_config`\n\n| Property        | Type   | Required | Description                                      |\n| --------------- | ------ | -------- | ------------------------------------------------ |\n| `name`          | string | ✓        | Tool name (how the agent references it)          |\n| `method`        | string | ✓        | HTTP method: `GET` or `POST`                     |\n| `endpoint`      | string | ✓        | URL endpoint (supports `${param}` interpolation) |\n| `instruction`   | string | ✗        | Description shown to the agent                   |\n| `args`          | object | ✗        | Parameter definitions (JSON Schema properties)   |\n| `required`      | array  | ✗        | List of required parameter names                 |\n| `headers`       | object | ✗        | HTTP headers to include. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request, useful when docker agent is itself exposed as an HTTP server). |\n| `output_schema` | object | ✗        | JSON Schema for the response. Used by MCP / Code Mode consumers; tool responses are still returned to the model as raw strings.                                                                                          |\n\n## HTTP Methods\n\n### GET Requests\n\nFor GET requests, parameters are interpolated into the URL:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: search_users\n      method: GET\n      endpoint: \"https://api.example.com/users?q=${query}&limit=${limit}\"\n      instruction: Search for users by name\n      args:\n        query:\n          type: string\n          description: Search query\n        limit:\n          type: integer\n          description: Maximum results (default 10)\n      required: [\"query\"]\n```\n\n### POST Requests\n\nFor POST requests, parameters are sent as JSON in the request body:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: create_task\n      method: POST\n      endpoint: \"https://api.example.com/tasks\"\n      instruction: Create a new task\n      args:\n        title:\n          type: string\n          description: Task title\n        description:\n          type: string\n          description: Task description\n        priority:\n          type: string\n          enum: [\"low\", \"medium\", \"high\"]\n          description: Task priority\n      required: [\"title\"]\n      headers:\n        Content-Type: \"application/json\"\n        Authorization: \"Bearer ${env.API_TOKEN}\"\n```\n\n## URL Interpolation\n\nUse `${param}` syntax to insert parameter values into URLs:\n\n```yaml\nendpoint: \"https://api.example.com/users/${user_id}/posts/${post_id}\"\n```\n\nParameter values are inserted as strings by the template expansion. Add URL encoding in the template when needed (for example, `${encodeURIComponent(city)}`).\n\n## Headers\n\nHeaders can include environment variables:\n\n```yaml\nheaders:\n  Authorization: \"Bearer ${env.API_KEY}\"\n  X-Custom-Header: \"static-value\"\n  Content-Type: \"application/json\"\n```\n\n## Output Schema\n\nOptionally document the expected response format:\n\n```yaml\ntoolsets:\n  - type: api\n    api_config:\n      name: get_user\n      method: GET\n      endpoint: \"https://api.example.com/users/${id}\"\n      instruction: Get user details by ID\n      args:\n        id:\n          type: string\n          description: User ID\n      required: [\"id\"]\n      output_schema:\n        type: object\n        properties:\n          id:\n            type: string\n          name:\n            type: string\n          email:\n            type: string\n          created_at:\n            type: string\n```\n\n## Example: GitHub API\n\n```yaml\nagents:\n  github_assistant:\n    model: openai/gpt-4o\n    description: Assistant that can query GitHub\n    instruction: You can look up GitHub repositories and users.\n    toolsets:\n      - type: api\n        api_config:\n          name: get_repo\n          method: GET\n          endpoint: \"https://api.github.com/repos/${owner}/${repo}\"\n          instruction: Get information about a GitHub repository\n          args:\n            owner:\n              type: string\n              description: Repository owner (user or org)\n            repo:\n              type: string\n              description: Repository name\n          required: [\"owner\", \"repo\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n            Authorization: \"Bearer ${env.GITHUB_TOKEN}\"\n\n      - type: api\n        api_config:\n          name: get_user\n          method: GET\n          endpoint: \"https://api.github.com/users/${username}\"\n          instruction: Get information about a GitHub user\n          args:\n            username:\n              type: string\n              description: GitHub username\n          required: [\"username\"]\n          headers:\n            Accept: \"application/vnd.github.v3+json\"\n```\n\n## Limitations\n\n- Only supports GET and POST methods\n- Response body is limited to 1MB\n- Default 30-second timeout per request (override with the `timeout` field)\n- Only HTTP and HTTPS URLs are supported\n- No support for file uploads or multipart forms\n- By default, requests to non-public IP ranges (loopback, RFC1918, link-local, the cloud-metadata endpoint, multicast, the unspecified address) are refused at dial time — even when DNS for an otherwise-public host resolves there. Set `allow_private_ips: true` to disable that check.\n\n## Reaching internal services\n\n```yaml\ntoolsets:\n  - type: api\n    timeout: 60\n    allow_private_ips: true\n    api_config:\n      name: get_local_status\n      method: GET\n      endpoint: \"http://localhost:8080/health\"\n      instruction: Check the local service health\n```\n\n> [!WARNING]\n> **SSRF**\n>\n> Setting `allow_private_ips: true` re-exposes the SSRF surface for this tool. Only enable it when the configured `endpoint` is a trusted internal service — a prompt-injected agent cannot redirect the call elsewhere because the endpoint is fixed in config, but redirects from the configured host can still reach unexpected places.\n\n> [!TIP]\n> **For Complex APIs**\n>\n> For APIs that need authentication flows, pagination, or complex request/response handling, consider using an MCP server instead. The API tool is best for simple, stateless HTTP operations.\n\n> [!WARNING]\n> **Security**\n>\n> API keys and tokens in headers are visible in debug logs. Use environment variables (`${env.VAR}`) rather than hardcoding secrets in configuration files.\n\n\n<!-- Skill/Rule: Background-agents Skill (_vendor/github.com/docker/docker-agent/docs/tools/background-agents/index.md) -->\n---\ntitle: \"Background Agents Tool\"\ndescription: \"Dispatch work to sub-agents concurrently and collect results asynchronously.\"\nkeywords: docker agent, ai agents, tools, toolsets, background agents tool\nlinkTitle: \"Background Agents\"\nweight: 90\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-agents/\n---\n\n_Dispatch work to sub-agents concurrently and collect results asynchronously._\n\n## Overview\n\nThe background agents tool lets an orchestrator dispatch work to sub-agents concurrently and collect results asynchronously. Unlike [transfer_task](../transfer-task/index.md) (which blocks until the sub-agent finishes), background agent tasks run in parallel — the orchestrator can start several tasks, do other work, and check on them later.\n\n## Available Tools\n\n| Tool                     | Description                                                     |\n| ------------------------ | --------------------------------------------------------------- |\n| `run_background_agent`   | Start a sub-agent task in the background; returns a task ID     |\n| `list_background_agents` | List all background tasks with their status and runtime         |\n| `view_background_agent`  | View live output or final result of a task by ID                |\n| `stop_background_agent`  | Cancel a running task by ID                                     |\n\n### `run_background_agent` parameters\n\n| Parameter         | Type   | Required | Description                                                                 |\n| ----------------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to run. Must be listed under the caller's `sub_agents`. |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.        |\n| `expected_output` | string | ✗        | Optional description of the result format the caller expects.               |\n\n`run_background_agent` returns a **task ID** string. Tools run by the sub-agent inherit the parent session's permissions. Because background tasks run non-interactively, any tool call that would normally prompt the user for approval will be automatically denied. To allow background agents to run mutating tools, you must explicitly approve them in the parent session (e.g. via YOLO mode or explicit allow rules).\n\nBackground delegation shares the same runtime guards as `transfer_task`: delegation cycles are rejected and chains are capped at 10 nested delegations. See [Delegation Limits](../transfer-task/index.md#delegation-limits).\n\n### `view_background_agent` and `stop_background_agent` parameters\n\n| Parameter | Type   | Required | Description                                                    |\n| --------- | ------ | -------- | -------------------------------------------------------------- |\n| `task_id` | string | ✓        | Task ID returned by `run_background_agent` or `list_background_agents`. |\n\n`list_background_agents` takes no parameters.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: background_agents\n```\n\nNo configuration options. Requires the agent to have `sub_agents` configured so the background tasks have agents to dispatch to.\n\n## Example\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Orchestrates parallel research\n    instruction: Fan out research tasks and synthesize results.\n    sub_agents: [researcher]\n    toolsets:\n      - type: background_agents\n      - type: think\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Research topics thoroughly.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\n> [!TIP]\n> **When to Use**\n>\n> Use `background_agents` when your orchestrator needs to fan out work to multiple specialists in parallel — for example, researching several topics simultaneously or running independent code analyses side by side.\n\nIn the TUI, each background task's token usage is accounted for live: the sidebar's Agents panel shows the sub-agent's context usage percentage on its roster row, the Agent Inspector shows its exact token counts, and the task's cost joins the session total.\n\n## Using Harness Sub-Agents\n\nBackground agents work equally well with [harness-backed sub-agents](../../features/harnesses/index.md) — sub-agents driven by external coding CLIs such as Claude Code or Codex. This lets you dispatch multiple independent coding tasks in parallel:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Orchestrator that fans out coding tasks\n    instruction: |\n      Dispatch the frontend and backend tasks in parallel,\n      then collect results and produce a summary.\n    sub_agents:\n      - claude-coder\n      - codex-coder\n    toolsets:\n      - type: background_agents\n\n  claude-coder:\n    description: Frontend specialist (Claude Code)\n    harness:\n      type: claude-code\n      effort: medium\n\n  codex-coder:\n    description: Backend specialist (Codex)\n    harness:\n      type: codex\n```\n\nThe orchestrator calls `run_background_agent` for each coding task, then uses `list_background_agents` and `view_background_agent` to collect results when they finish.\n\n> [!NOTE]\n> **Harness toolsets are ignored**\n>\n> Harness agents use the external CLI's own tools — any `toolsets:` configured on the harness agent are silently ignored. See [Coding Harnesses](../../features/harnesses/index.md) for details and caveats.\n\nSee [`examples/coding_harness_background_agents.yaml`](https://github.com/docker/docker-agent/blob/main/examples/coding_harness_background_agents.yaml) for a complete configuration.\n\n\n<!-- Skill/Rule: Background-jobs Skill (_vendor/github.com/docker/docker-agent/docs/tools/background-jobs/index.md) -->\n---\ntitle: \"Background Jobs Tool\"\ndescription: \"Run and manage long-running shell commands.\"\nkeywords: docker agent, ai agents, tools, toolsets, background jobs, shell\nlinkTitle: \"Background Jobs\"\nweight: 21\ncanonical: https://docs.docker.com/ai/docker-agent/tools/background-jobs/\n---\n\n_Run and manage long-running shell commands._\n\n## Overview\n\nThe `background_jobs` toolset starts shell commands that should keep running while the agent continues with other work, such as local servers, file watchers, long builds, or test suites. It returns a job ID immediately, captures combined stdout/stderr up to 10 MB per job, and terminates all running jobs when the agent session ends.\n\nUse the [`shell`](../shell/index.md) toolset for short synchronous commands. Add both toolsets when an agent needs both synchronous commands and long-running processes.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n  - type: background_jobs\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                                                                          |\n| -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `env`    | object  | Environment variables to set for all background job commands.                                                                                        |\n| `recall` | boolean | Let `run_background_job` expose a `recall` parameter so jobs can steer the agent when they finish (see [Background job recall](#background-job-recall)). Default `false`. |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Background job recall\n\nSet `recall: true` to let the `run_background_job` tool expose a `recall` boolean parameter:\n\n```yaml\ntoolsets:\n  - type: background_jobs\n    recall: true\n```\n\nWhen the agent starts a background job with `recall: true`, Docker Agent sends a steering message back into the running agent loop after the job finishes. The message contains a short completion sentence and the job output, so the agent can react without polling `view_background_job`.\n\nUse recall for finite background work where completion matters (for example, a long build or test suite). Avoid it for servers and watchers that are expected to run until stopped. See [`examples/shell_recall.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shell_recall.yaml) for a complete configuration.\n\n## Available Tools\n\nThe background jobs toolset exposes five tools:\n\n| Tool Name              | Description                                                                                    |\n| ---------------------- | ---------------------------------------------------------------------------------------------- |\n| `run_background_job`   | Start a command asynchronously and return a job ID immediately. Use for servers/watchers/etc. |\n| `list_background_jobs` | List all background jobs with their status, runtime, and metadata.                             |\n| `view_background_job`  | View the buffered output and status of a specific background job by ID.                        |\n| `stop_background_job`  | Stop a running background job. Child processes are terminated too.                             |\n| `wait_background_job`  | Block until a job finishes and return its exit code and output. Safe on already-finished jobs. |\n\n### `run_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                                                 |\n| --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute in the background.                                                                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                                                                                     |\n| `recall`  | boolean | ✗        | Only available when the `background_jobs` toolset has `recall: true`. When true, send a steering message with the job output when it finishes. |\n\n`view_background_job` and `stop_background_job` each take a single required `job_id` string returned by `run_background_job` or `list_background_jobs`.\n\n### `wait_background_job` parameters\n\n| Parameter | Type    | Required | Description                                                                                                    |\n| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------- |\n| `job_id`  | string  | ✓        | Job ID returned by `run_background_job` or `list_background_jobs`.                                             |\n| `timeout` | integer | ✗        | Maximum seconds to wait (default: `60`). If the job is still running when the limit fires, the tool returns the current output with a notice and the job continues in the background. |\n\n> [!WARNING]\n> **Safety**\n>\n> Background jobs run shell commands with the same access as the agent process. Stop servers and watchers when they are no longer needed, and use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n\n\n<!-- Skill/Rule: Fetch Skill (_vendor/github.com/docker/docker-agent/docs/tools/fetch/index.md) -->\n---\ntitle: \"Fetch Tool\"\ndescription: \"Read content from HTTP/HTTPS URLs.\"\nkeywords: docker agent, ai agents, tools, toolsets, fetch tool\nlinkTitle: \"Fetch\"\nweight: 50\ncanonical: https://docs.docker.com/ai/docker-agent/tools/fetch/\n---\n\n_Read content from HTTP/HTTPS URLs._\n\n## Overview\n\nThe fetch tool lets agents retrieve content from one or more HTTP/HTTPS URLs. It is **read-only** — only `GET` requests are supported. The tool respects `robots.txt`, limits response size (1 MB per URL), and can return content as plain text, Markdown (converted from HTML), or raw HTML.\n\n> [!NOTE]\n> **GET only**\n>\n> The fetch tool does **not** support `POST`, `PUT`, `DELETE` or other methods, and does not expose request bodies or per-call custom headers (the toolset can still attach static [credential headers](#custom-headers) to every request). To call REST endpoints with other verbs, use the [API tool](../api/index.md) or an [OpenAPI toolset](../openapi/index.md).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: fetch\n```\n\n### Options\n\n| Property            | Type          | Default | Description                                                                                                                                                                                                                                                                                                      |\n| ------------------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `timeout`           | int           | `30`    | Default request timeout in seconds (overridable per tool call).                                                                                                                                                                                                                                                  |\n| `allowed_domains`   | array[string] | _none_  | Allow-list of hosts the tool may fetch. When set, every URL whose host is **not** in the list is rejected before any network call is made. Mutually exclusive with `blocked_domains`.                                                                                                                            |\n| `blocked_domains`   | array[string] | _none_  | Deny-list of hosts the tool must not fetch. URLs whose host matches one of these patterns are rejected before any network call (including `robots.txt`) is made. Mutually exclusive with `allowed_domains`.                                                                                                      |\n| `allow_private_ips` | boolean       | `false` | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast, and the unspecified address). Required to reach `localhost` / internal services. See [SSRF protection](#ssrf-protection-and-reaching-localhost) below. |\n| `headers`           | map[string]string | _none_ | Static HTTP headers attached to **every** request the toolset issues (including `robots.txt`). Values support `${env.VAR}` for secrets. Caller-supplied entries override the default `User-Agent` and the format-driven `Accept` header. Headers are stripped on cross-host redirects so credentials never leak to a third-party host. See [Custom headers](#custom-headers) below. |\n\n### Domain matching\n\nDomain patterns in `allowed_domains` and `blocked_domains` use the following rules (case-insensitive):\n\n- **Bare domain** — `example.com` matches the host `example.com` _and_ any subdomain such as `docs.example.com`. It does **not** match unrelated hosts that share a suffix (e.g. `badexample.com`).\n- **Leading dot** — `.example.com` matches **only** strict subdomains (`docs.example.com`, `a.b.example.com`), not the apex `example.com`.\n- **Wildcard glob** — `*.example.com` is an alias for the leading-dot form; the apex is excluded. The `*` is only valid as a leading `*.` token (entries like `foo.*`, `*.*.example.com`, or a bare `*` are rejected at config-load time).\n- **IP literal** — IP addresses are matched exactly (`169.254.169.254`).\n- **CIDR range** — `169.254.0.0/16`, `10.0.0.0/8`, `::1/128`, `fc00::/7`. Matches when the URL's host parses as an IP inside the network. Hostname hosts never match a CIDR pattern. Malformed CIDRs are rejected at config-load time.\n- **Trailing dots** in FQDN-form URLs (`http://example.com./`) are stripped before matching, so they cannot bypass a deny-list entry.\n\nThe lists are mutually exclusive: a single fetch toolset may set either `allowed_domains` or `blocked_domains`, but not both.\n\nWhen a list is configured, every redirect target is re-checked against the same list. A request to an allowed origin that redirects to a forbidden host is rejected before any data is read from the redirect.\n\n> [!WARNING]\n> **Limitations**\n>\n> Matching is purely string-based on the URL host. It does **not** perform DNS resolution and does **not** normalise alternative IP encodings (decimal `2852039166`, hex `0xa9.0xfe.0xa9.0xfe`, octal, etc. IPv4-mapped IPv6 addresses ARE normalized to their IPv4 form). If you need to deny access to a specific IP, also list its alternative encodings, or block at the network layer.\n\n### Custom Timeout\n\n```yaml\ntoolsets:\n  - type: fetch\n    timeout: 60\n```\n\n### Custom headers\n\nAttach static headers — typically credentials — to every request. Values support `${env.VAR}` interpolation so secrets stay out of YAML, and headers are dropped on cross-host redirects so a redirect chain cannot leak them to a third-party host:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docs.internal.example.com\n    headers:\n      Authorization: \"Bearer ${env.INTERNAL_DOCS_TOKEN}\"\n      X-Internal-Client: \"docker-agent\"\n```\n\n> [!WARNING]\n> **Pair credential headers with an allow-list**\n>\n> When `headers` carries credentials (e.g. `Authorization`), set `allowed_domains` to the specific hosts that should receive them. Stdlib already strips a small allow-list (`Authorization`, `Cookie`, `WWW-Authenticate`) on cross-domain redirects, and the fetch tool additionally strips every operator-supplied header on cross-host redirects — but an allow-list is the strongest guarantee against accidental exfiltration.\n\n### Restrict to specific domains\n\n```yaml\ntoolsets:\n  - type: fetch\n    allowed_domains:\n      - docker.com          # docker.com and *.docker.com\n      - github.com          # github.com and *.github.com\n      - .githubusercontent.com  # only subdomains, e.g. raw.githubusercontent.com\n```\n\n### Block sensitive hosts\n\n```yaml\ntoolsets:\n  - type: fetch\n    blocked_domains:\n      - 169.254.169.254       # cloud metadata endpoint (literal IP)\n      - 169.254.0.0/16        # entire link-local range (CIDR)\n      - 10.0.0.0/8            # RFC1918 private range\n      - \"*.internal.example.com\"  # any subdomain (wildcard)\n      - internal.example.com  # internal corporate hostname\n```\n\n> [!NOTE]\n> **Already blocked by default**\n>\n> You do **not** need to add loopback, RFC1918, link-local (incl. `169.254.169.254`), multicast or the unspecified address to `blocked_domains` to be safe — the fetch tool already refuses connections to those ranges at dial time, after DNS resolution. The example above is only useful if you also want to reject those hosts _before_ any network call (and to surface a clearer error message to the agent), or if you have set `allow_private_ips: true` and want to deny a specific subset.\n\n### SSRF protection and reaching localhost\n\nBy default, the fetch tool refuses connections to **non-public IP addresses** — even when DNS for an otherwise-public host resolves to one of them (so DNS rebinding is also blocked). The check happens at dial time, after DNS resolution, and rejects:\n\n- **Loopback** — `127.0.0.0/8`, `::1` (this is what blocks `http://localhost/...` and `http://127.0.0.1/...`)\n- **RFC1918 private ranges** — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n- **Link-local** — `169.254.0.0/16` (IPv4, including the cloud-metadata endpoint `169.254.169.254`) and `fe80::/10` (IPv6)\n- **Multicast** and the **unspecified** address (`0.0.0.0`, `::`)\n- **IPv4-mapped IPv6** — addresses like `::ffff:127.0.0.1` or `::ffff:169.254.169.254` are normalized to their IPv4 form and blocked accordingly\n\nThis is the default because LLM-driven fetches are a classic Server-Side Request Forgery (SSRF) vector: a prompt-injected URL can otherwise reach internal services, cloud metadata, or admin interfaces on the host running the agent.\n\nIf an agent legitimately needs to call **localhost** or an **internal service**, opt in with `allow_private_ips: true`:\n\n```yaml\ntoolsets:\n  - type: fetch\n    allow_private_ips: true\n    allowed_domains:\n      - localhost\n      - 127.0.0.1\n      - 10.0.0.0/8            # internal corporate range\n```\n\n> [!WARNING]\n> **Pair with an allow-list**\n>\n> Setting `allow_private_ips: true` alone re-exposes the SSRF surface. We strongly recommend combining it with an `allowed_domains` entry that restricts the tool to the specific internal hosts or CIDRs the agent actually needs (e.g. `localhost`, `127.0.0.1`, or your internal CIDR).\n>\n> **Note:** `allowed_domains` is checked _before_ DNS resolution (string-based on hostname), while the SSRF check happens _after_ DNS resolution (on the resolved IP). This means `allowed_domains` and `blocked_domains` are evaluated independently of `allow_private_ips` and continue to apply. A public hostname in `allowed_domains` that resolves to a private IP will still be blocked unless `allow_private_ips: true` is set.\n\n## Tool Interface\n\nThe toolset exposes a single tool, `fetch`, with the following parameters:\n\n| Parameter | Type           | Required | Description                                                                                                 |\n| --------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------- |\n| `urls`    | array[string]  | ✓        | One or more HTTP/HTTPS URLs to fetch (all via `GET`).                                                       |\n| `format`  | string         | ✓        | Output format: `text`, `markdown`, or `html`. HTML responses are converted to text/markdown when requested. |\n| `timeout` | integer        | ✗        | Per-call request timeout in seconds. Overrides the toolset default. Valid range: `1`–`300`.                 |\n\nResponses are capped at **1 MB** per URL. Hosts that disallow the agent's user-agent via `robots.txt` are skipped with a clear error.\n\n> [!TIP]\n> **Fetch vs. API Tool**\n>\n> Use `fetch` when the agent needs to read arbitrary public URLs at runtime. Use the [API tool](../api/index.md) to expose specific, structured HTTP endpoints (including non-`GET` verbs) as named tools.\n\n## Domain Filtering\n\nThe `allowed_domains`, `blocked_domains`, and `allow_private_ips` options let you control which hosts the fetch tool may reach. The complete reference is in the [Options](#options) table and [Domain matching](#domain-matching) section above.\n\n**Key points:**\n\n- `allowed_domains` — allow-list; only listed hosts (and their subdomains for bare-domain entries) are reachable\n- `blocked_domains` — deny-list; mutually exclusive with `allowed_domains` (a config error is thrown if both are set)\n- `allow_private_ips` — defaults to `false`; set to `true` to reach loopback / RFC-1918 / link-local addresses\n- The same `allow_private_ips` flag is also supported on `api`, `openapi`, `a2a`, and remote `mcp` toolsets\n\nSee [`examples/fetch_domain_filtering.yaml`](https://github.com/docker/docker-agent/blob/main/examples/fetch_domain_filtering.yaml) for a complete filtering example, and [`examples/remote_mcp_allow_private_ips.yaml`](https://github.com/docker/docker-agent/blob/main/examples/remote_mcp_allow_private_ips.yaml) for the equivalent pattern on remote MCP toolsets.\n\n\n<!-- Skill/Rule: Filesystem Skill (_vendor/github.com/docker/docker-agent/docs/tools/filesystem/index.md) -->\n---\ntitle: \"Filesystem Tool\"\ndescription: \"Read, write, list, search, and navigate files and directories.\"\nkeywords: docker agent, ai agents, tools, toolsets, filesystem tool\nlinkTitle: \"Filesystem\"\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/tools/filesystem/\n---\n\n_Read, write, list, search, and navigate files and directories._\n\n## Overview\n\nThe filesystem tool gives agents the ability to explore codebases, read and edit files, create new files, search across files, and navigate directory structures.\n\n### Path resolution\n\nPaths are resolved relative to the **working directory** (the directory where the agent session started, or the directory specified with `--workdir`):\n\n- **Relative paths** (e.g., `src/main.go`, `../README.md`) are joined with the working directory.\n- **Absolute paths** must match the host operating system:\n  - Unix/Linux/macOS: `/home/user/project/file.txt`\n  - Windows: `C:\\Users\\user\\project\\file.txt` or `C:/Users/user/project/file.txt`\n- **Home directory expansion**: paths starting with `~` or `~/` expand to the user's home directory.\n\nWhen a file is not found, error messages include the resolved absolute path to help diagnose incorrect base directories or path formats.\n\n> [!IMPORTANT]\n> Agents must use paths appropriate for the host OS. A Windows absolute path like `C:\\file.txt` on a Unix system (or vice versa) is rejected with a clear error message.\n\n### Empty directory detection\n\nWhen `list_directory` encounters an empty directory or a directory where all entries are hidden by ignore patterns (e.g., only a `.git` folder when `ignore_vcs: true`), it explicitly reports the state:\n\n- **Empty directory**: \"Directory is empty: /path/to/dir\"\n- **All entries ignored**: \"Directory has no visible entries (N hidden by ignore patterns): /path/to/dir\"\n\nThis helps agents distinguish between an empty directory and a tool failure, avoiding unnecessary retries with shell commands.\n\n## Available Tools\n\n| Tool                   | Description                                                               |\n| ---------------------- | ------------------------------------------------------------------------- |\n| `read_file`            | Read the contents of a file (whole file, or a line range of a text file)  |\n| `read_multiple_files`  | Read several files in one call (more efficient than multiple `read_file`) |\n| `write_file`           | Create or overwrite a file with new content                               |\n| `edit_file`            | Make line-based edits (find-and-replace) in an existing file. Each edit must specify a non-empty `oldText` to match and replace; empty `oldText` values are rejected with an error. |\n| `list_directory`       | List files and directories at a given path (explicitly reports empty directories) |\n| `directory_tree`       | Recursive tree view of a directory                                        |\n| `create_directory`     | Create a new directory (creates parent directories as needed)             |\n| `remove_directory`     | Remove an empty directory                                                 |\n| `search_files_content` | Search for text or regex patterns across files                            |\n\n## edit_file Validation\n\nThe `edit_file` tool applies a sequence of find-and-replace edits to a file in memory, then writes the result back atomically. Each edit must provide a non-empty `oldText` value:\n\n- **Valid**: `{\"oldText\": \"line one\", \"newText\": \"LINE ONE\"}`\n- **Invalid**: `{\"oldText\": \"\", \"newText\": \"INJECTED\"}` — rejected with error\n\nAn empty `oldText` is never a meaningful edit: Go's `strings.Contains(s, \"\")` is always `true`, and `strings.Replace(s, \"\", new, 1)` silently inserts at offset 0. Without validation, this would prepend content to the file while still reporting success. The tool now returns an explicit error (\"oldText must not be empty\") when an edit has an empty `oldText`, and no changes are written to disk.\n\nWhen a sequence contains multiple edits and a later one is rejected, the entire operation fails and the file is left untouched — edits are applied in memory and only written once at the end, so partial application is not possible.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: filesystem\n```\n\n### Options\n\n| Property | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ignore_vcs` | boolean | `true` | When `true` (default), `.git` directories and `.gitignore` patterns are excluded from listings and searches. Set to `false` to include them. |\n| `post_edit` | array | `[]` | Commands to run after editing files matching a path pattern |\n| `post_edit[].path` | string | — | Glob pattern for files (e.g., `*.go`, `src/*/*.ts`) |\n| `post_edit[].cmd` | string | — | Command to run (use `${file}` for the edited file path) |\n| `allow_list` | array | `[]` | Directories the tools may access. Empty = unrestricted (default). |\n| `deny_list` | array | `[]` | Directories the tools must not access. Takes precedence over `allow_list`. |\n\n### Path access control\n\nBy default the filesystem tools are unrestricted: relative paths resolve\nfrom the working directory, but absolute paths and `..` traversals can\nreach anywhere the agent process can. Configure `allow_list` and/or\n`deny_list` to sandbox the toolset.\n\nEntries in either list are expanded as follows:\n\n- `\".\"` — the agent's working directory\n- `\"~\"` or `\"~/...\"` — the user's home directory\n- `\"$VAR\"` / `\"${VAR}\"` / `\"${env.VAR}\"` — environment variable expansion\n- absolute paths — used as-is\n- relative paths — anchored at the working directory\n\nSymlinks are resolved before the containment check, so a symlink inside an\nallowed root cannot be used to escape it. When an `allow_list` is set,\neach entry is opened as a Go [`*os.Root`](https://pkg.go.dev/os#Root) so\nthat the kernel's rooted-lookup semantics also reject `..` and symlink\nescapes at I/O time, not just at resolve time.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    # Restrict every operation to the working directory and the user's\n    # home folder, then carve credentials out of the home folder.\n    allow_list:\n      - \".\"\n      - \"~\"\n    deny_list:\n      - \"~/.ssh\"\n      - \"~/.aws\"\n```\n\nWhen the path supplied by the agent is rejected, the tool returns a\nstructured error rather than performing any filesystem I/O. This makes the\nrestriction visible to the model so it can adjust its plan.\n\n### Post-Edit Hooks\n\nAutomatically run formatting, linting, or other commands after the agent edits a file. The command fires once per file after each edit operation (`write_file` and `edit_file`). Use `${file}` as a placeholder for the absolute path of the edited file.\n\n```yaml\ntoolsets:\n  - type: filesystem\n    ignore_vcs: false\n    post_edit:\n      - path: \"*.go\"\n        cmd: \"gofmt -w ${file}\"\n      - path: \"*.ts\"\n        cmd: \"prettier --write ${file}\"\n      - path: \"src/*/*.py\"\n        cmd: \"black ${file}\"\n```\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `path` | string | Glob pattern matched against the file path. `*.go` matches any `.go` file; `src/*/*.ts` matches `.ts` files inside `src/`. |\n| `cmd` | string | Shell command to run. `${file}` expands to the absolute path of the just-edited file. |\n\nPost-edit commands run with the same working directory as the agent. If a command exits non-zero, the error is logged and surfaced to the model as a warning, but the edit is not rolled back.\n\nSee [`examples/post_edit.yaml`](https://github.com/docker/docker-agent/blob/main/examples/post_edit.yaml) for a complete example.\n\n\n<!-- Skill/Rule: Git Skill (_vendor/github.com/docker/docker-agent/docs/tools/git/index.md) -->\n---\ntitle: \"Git Tool\"\ndescription: \"Read-only inspection of the working git repository.\"\nkeywords: docker agent, ai agents, tools, toolsets, git tool\nlinkTitle: \"Git\"\nweight: 125\ncanonical: https://docs.docker.com/ai/docker-agent/tools/git/\n---\n\n_Read-only inspection of the working git repository._\n\n## Overview\n\nThe git toolset gives an agent structured, **read-only** access to the working repository — status, history, branches, a commit's changes, and line-level authorship. It is implemented with go-git, so it needs **no `git` binary**.\n\nCompared with running `git` through the `shell` tool, the git toolset returns clean, structured output the model can read reliably, is **safe by construction** (no command can modify the repository), and works even when `shell` is disabled or no `git` binary is installed.\n\n> [!NOTE]\n> The git toolset is read-only. To stage, commit, or check out, use the [`shell`](../shell/index.md) tool.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: git\n```\n\nNo configuration options. The repository is opened at the agent's working directory; a subdirectory still resolves to the repository root.\n\n> [!WARNING]\n> **The repository is discovered by walking up parent directories.** If the working\n> directory is not itself a repository but an ancestor is (for example a\n> home directory tracked as dotfiles), the toolset resolves to that ancestor and\n> `git_show` / `git_blame` can expose its full history and file contents. The\n> filesystem toolset's allow/deny lists do **not** apply here. Only enable this\n> toolset where the surrounding repository is safe to read.\n\n> [!NOTE]\n> **Performance.** go-git is pure Go, which costs speed on large repositories:\n> `git_status` rehashes the whole worktree, and `git_blame` scales with history\n> depth times file size — its 400-line output cap is applied *after* the full\n> computation, so it does not make blaming a large file cheaper.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `git_status` | Current branch and changed files (staged / unstaged / untracked). |\n| `git_log` | Recent commits (hash, date, author, subject). |\n| `git_branches` | Local branches, current one marked with `*`. |\n| `git_show` | A commit's metadata, message, and changed files with +/- counts. |\n| `git_blame` | Line-by-line authorship for a file. |\n\n### `git_log`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `limit` | No | Maximum number of commits to return (default 20). |\n| `path` | No | Only show commits that touch this path. |\n\n### `git_show`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `ref` | No | Commit hash or revision to show (default HEAD). |\n\n### `git_blame`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `path` | Yes | File path to blame, relative to the repository root. |\n| `rev` | No | Commit or revision to blame at (default HEAD). |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A code review assistant\n    instruction: |\n      Review the working changes: check git_status, then git_show the latest\n      commit, and summarize what changed.\n    toolsets:\n      - type: git\n      - type: filesystem\n```\n\nExample `git_status` output:\n\n```text\nOn branch master\n1 changed file(s) [XY = staged/worktree; M=modified A=added D=deleted R=renamed ?=untracked]:\n   M main.go\n```\n\n> [!TIP]\n> **When to use**\n>\n> Use the git toolset whenever the agent needs repository context — before editing, to review recent history, or to find who last touched a line — without exposing the writable `shell` surface.\n\n\n<!-- Skill/Rule: Handoff Skill (_vendor/github.com/docker/docker-agent/docs/tools/handoff/index.md) -->\n---\ntitle: \"Handoff Tool\"\ndescription: \"Hand off the active conversation to another local agent defined in the same config.\"\nkeywords: docker agent, ai agents, tools, toolsets, handoff tool\nlinkTitle: \"Handoff\"\nweight: 70\ncanonical: https://docs.docker.com/ai/docker-agent/tools/handoff/\n---\n\n_Hand off the active conversation to another local agent defined in the same config._\n\n## Overview\n\nThe `handoff` tool lets an agent transfer control of the **current conversation** to another agent in the **same config file**. Unlike [`transfer_task`](../transfer-task/index.md), which delegates a sub-task and collects the result, `handoff` rewires the session so the receiving agent continues the conversation directly with the user.\n\nThis is the core mechanism for **handoffs routing** — a pattern where a router agent classifies the user's request and hands it off to a specialist, which then owns the rest of the session.\n\n> [!NOTE]\n> **Local only**\n>\n> The `handoff` tool only targets agents declared in the **same** config file by their local name. It does **not** open network connections. To delegate to a remote agent over the network, use the [A2A toolset](../a2a/index.md) instead.\n\n## Configuration\n\nThe tool is enabled implicitly when an agent declares a non-empty `handoffs:` list. You do **not** add `- type: handoff` under `toolsets:` — it is not a toolset type.\n\n```yaml\nagents:\n  router:\n    model: openai/gpt-4o\n    description: Routes questions to the right specialist\n    instruction: |\n      Classify the user's question and hand off to the most appropriate\n      specialist. If unsure, ask a clarifying question first.\n    handoffs: [billing, support]\n\n  billing:\n    model: openai/gpt-4o\n    description: Billing specialist\n    instruction: Answer billing questions.\n\n  support:\n    model: openai/gpt-4o\n    description: Technical support specialist\n    instruction: Help with technical issues.\n```\n\nThe router agent automatically gets a `handoff` tool it can call to switch the conversation to `billing` or `support`.\n\n## Tool Interface\n\nThe `handoff` tool takes a single parameter:\n\n| Parameter | Type   | Required | Description                                                       |\n| --------- | ------ | -------- | ----------------------------------------------------------------- |\n| `agent`   | string | ✓        | The local name of the agent to hand off the conversation to.      |\n\nOnly names listed in the current agent's `handoffs:` field are valid targets.\n\n> [!TIP]\n> **See also**\n>\n> For sub-task delegation (caller stays in control, waits for the result), see [Transfer Task](../transfer-task/index.md). For remote agent connections over the network, see the [A2A toolset](../a2a/index.md). For the broader pattern, see [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).\n\n\n<!-- Skill/Rule: Lsp Skill (_vendor/github.com/docker/docker-agent/docs/tools/lsp/index.md) -->\n---\ntitle: \"LSP Tool\"\ndescription: \"Connect to Language Server Protocol servers for code intelligence.\"\nkeywords: docker agent, ai agents, tools, toolsets, lsp tool\nlinkTitle: \"LSP\"\nweight: 220\ncanonical: https://docs.docker.com/ai/docker-agent/tools/lsp/\n---\n\n_Connect to Language Server Protocol servers for code intelligence._\n\n## Overview\n\nThe LSP tool connects your agent to any Language Server Protocol (LSP) server, providing comprehensive code intelligence capabilities like go-to-definition, find references, diagnostics, and more.\n\n> [!NOTE]\n> **What is LSP?**\n>\n> The [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) is a standard for providing language features like autocomplete, go-to-definition, and diagnostics. Most programming languages have LSP servers available.\n\n## Available Tools\n\nThe LSP toolset provides these tools to the agent:\n\n| Tool                    | Description                                   | Read-Only |\n| ----------------------- | --------------------------------------------- | --------- |\n| `lsp_workspace`         | Get workspace info and available capabilities | ✓         |\n| `lsp_hover`             | Get type info and documentation for a symbol  | ✓         |\n| `lsp_definition`        | Find where a symbol is defined                | ✓         |\n| `lsp_references`        | Find all references to a symbol               | ✓         |\n| `lsp_document_symbols`  | List all symbols in a file                    | ✓         |\n| `lsp_workspace_symbols` | Search symbols across the workspace           | ✓         |\n| `lsp_diagnostics`       | Get errors and warnings for a file            | ✓         |\n| `lsp_code_actions`      | Get available quick fixes and refactorings    | ✓         |\n| `lsp_rename`            | Rename a symbol across the workspace          | ✗         |\n| `lsp_format`            | Format a file                                 | ✗         |\n| `lsp_call_hierarchy`    | Find incoming/outgoing calls                  | ✓         |\n| `lsp_type_hierarchy`    | Find supertypes/subtypes                      | ✓         |\n| `lsp_implementations`   | Find interface implementations                | ✓         |\n| `lsp_signature_help`    | Get function signature at call site           | ✓         |\n| `lsp_inlay_hints`       | Get type annotations and parameter names      | ✓         |\n\n## Configuration\n\n```yaml\nagents:\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code developer with LSP support\n    instruction: You are a software developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        args: []\n        file_types: [\".go\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Properties\n\n| Property      | Type   | Required | Description                                                                                                                  |\n| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `command`     | string | ✓        | LSP server executable command                                                                                                |\n| `args`        | array  | ✗        | Command-line arguments for the LSP server                                                                                    |\n| `env`         | object | ✗        | Environment variables for the LSP process                                                                                    |\n| `file_types`  | array  | ✗        | File extensions this LSP handles (e.g., `[\".go\", \".mod\"]`)                                                                   |\n| `working_dir` | string | ✗        | Working directory for the LSP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. |\n| `version`     | string | ✗        | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary |\n\n## Common LSP Servers\n\nHere are configurations for popular languages:\n\n### Go (gopls)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    version: \"golang/tools@v0.21.0\" # optional: auto-install if not in PATH\n    file_types: [\".go\"]\n```\n\nIf your Go module lives in a subdirectory (e.g. a monorepo where `go.mod` is under `./backend`), set `working_dir` so `gopls` is started from the module root:\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    working_dir: ./backend # gopls must be started from the module root\n```\n\n### TypeScript/JavaScript (typescript-language-server)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: typescript-language-server\n    args: [\"--stdio\"]\n    file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n```\n\n### Python (pylsp)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: pylsp\n    file_types: [\".py\"]\n```\n\n### Rust (rust-analyzer)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: rust-analyzer\n    file_types: [\".rs\"]\n```\n\n### C/C++ (clangd)\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: clangd\n    file_types: [\".c\", \".cpp\", \".h\", \".hpp\"]\n```\n\n## Multiple LSP Servers\n\nYou can configure multiple LSP servers for different file types:\n\n```yaml\nagents:\n  polyglot:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-language developer\n    instruction: You are a full-stack developer.\n    toolsets:\n      - type: lsp\n        command: gopls\n        file_types: [\".go\"]\n      - type: lsp\n        command: typescript-language-server\n        args: [\"--stdio\"]\n        file_types: [\".ts\", \".tsx\", \".js\", \".jsx\"]\n      - type: lsp\n        command: pylsp\n        file_types: [\".py\"]\n      - type: filesystem\n      - type: shell\n```\n\n## Workflow Instructions\n\nThe LSP tool includes built-in instructions that guide the agent on how to use it effectively. The agent learns to:\n\n1. Start with `lsp_workspace` to understand available capabilities\n2. Use `lsp_workspace_symbols` to find relevant code\n3. Use `lsp_references` before modifying any symbol\n4. Check `lsp_diagnostics` after every code change\n5. Apply `lsp_format` after edits are complete\n\n> [!TIP]\n> **Best Practice**\n>\n> Always include the `filesystem` tool alongside LSP. The agent needs filesystem access to read and write code files, while LSP provides intelligence about the code.\n\n## Capability Detection\n\nNot all LSP servers support all features. During the `initialize` handshake, Docker Agent reads the server's `ServerCapabilities` and **filters out the `lsp_*` tools the server does not advertise**. The model never sees, for example, `lsp_inlay_hints` against a server that doesn't support it, so it can't waste a turn calling a tool that would only fail.\n\nThe agent uses `lsp_workspace` to discover what's available:\n\n```text\nWorkspace Information:\n- Root: /path/to/project\n- Server: gopls v0.14.0\n- File types: .go\n\nAvailable Capabilities:\n- Hover: Yes\n- Go to Definition: Yes\n- Find References: Yes\n- Rename: Yes\n- Code Actions: Yes\n- Formatting: Yes\n- Call Hierarchy: Yes\n- Type Hierarchy: Yes\n...\n```\n\n## Auto-Restart and Lifecycle\n\nLSP toolsets are managed by the same supervisor as MCP toolsets, so a crashed `gopls` (or any other language server) is reconnected automatically with exponential backoff. Use the [`lifecycle`](../../configuration/tools/index.md#toolset-lifecycle) block to tune the policy per toolset — for example, mark `gopls` as `strict` if your CI flow requires it to be available, or use `/toolset-restart gopls` from the TUI to force a reconnect when the server gets stuck.\n\n```yaml\ntoolsets:\n  - type: lsp\n    command: gopls\n    file_types: [\".go\"]\n    lifecycle:\n      profile: resilient # default: auto-restart on crash with exponential backoff\n```\n\n## Position Format\n\nAll LSP tools use **1-based** line and character positions:\n\n- Line 1 is the first line of the file\n- Character 1 is the first character on a line\n\n```json\n{\n  \"file\": \"/path/to/file.go\",\n  \"line\": 42,\n  \"character\": 15\n}\n```\n\n> [!TIP]\n> **Auto-Installation**\n>\n> Docker Agent can automatically download and install LSP servers if they are not found in your PATH. Use the `version` property to specify a package, or let Docker Agent auto-detect it from the command name. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) for details.\n\n\n<!-- Skill/Rule: Mcp-catalog Skill (_vendor/github.com/docker/docker-agent/docs/tools/mcp-catalog/index.md) -->\n---\ntitle: \"MCP Catalog Tool\"\ndescription: \"Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp catalog tool\nlinkTitle: \"MCP Catalog\"\nweight: 120\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp-catalog/\n---\n\n_Let the agent discover and activate remote MCP servers from the Docker MCP Catalog on demand._\n\n## Overview\n\nThe `mcp_catalog` toolset gives an agent access to a curated subset of the [Docker MCP Catalog](https://hub.docker.com/search?q=&type=mcp) — every server in this subset is reachable over the **streamable-http** transport, so Docker Agent can talk to it directly without the MCP gateway or a local subprocess.\n\nServers are **not** active by default. Instead, the toolset exposes a small set of meta-tools the agent uses to search, enable, and disable servers as a turn unfolds. Tools from un-enabled servers stay hidden, so the prompt is not flooded with hundreds of tool definitions the agent will never use.\n\n> [!NOTE]\n> **When to use it**\n>\n> Use `mcp_catalog` when you want the agent to _decide at runtime_ which third-party services it needs (Notion, Stripe, Brave Search, …) instead of pinning that decision in YAML up front. For a fixed set of servers, declare each one with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) directly — the catalog adds an extra layer of meta-tools that pure `type: mcp` entries do not need.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n```\n\nThe catalog is embedded in the `docker-agent` binary and refreshed with each release. By default every server in the embedded subset is offered.\n\n### Restricting the offered servers\n\nTwo optional lists narrow what the toolset offers, so an agent sees a focused, predictable menu instead of the full catalog:\n\n- **`allowed_servers`** — when non-empty, **only** these catalog server ids are searchable and enableable; every other entry is hidden.\n- **`blocked_servers`** — removes individual ids from the offered set. It is applied **after** `allowed_servers`, so a server listed in both is blocked (block wins over allow).\n\nBoth take server ids (the `id` field returned by `search_remote_mcp_servers`). An empty or omitted list disables that filter.\n\n```yaml\ntoolsets:\n  - type: mcp_catalog\n    allowed_servers:\n      - docker-docs\n      - microsoft-learn\n      - hugging-face\n    blocked_servers:\n      - gitmcp\n```\n\n## Meta-Tools\n\nUp to five tools are exposed to the model. The disable / reset-auth pair only appears once at least one server is enabled, so the meta-tool surface stays minimal until the agent activates something.\n\n| Tool                            | When visible            | Description                                                                                                                                          |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `search_remote_mcp_servers`     | Always                  | Case-insensitive fuzzy search over id, title, description, category and tags. Returns id, auth requirements (`oauth` / `none`) and URL. |\n| `enable_remote_mcp_server`      | Always                  | Activate a server by id. **Blocks** until the connection (and any required OAuth handshake) completes; on success the server's tools are immediately live and the model continues with the user's original request in the same turn. |\n| `list_remote_mcp_servers`       | Always                  | Show currently enabled servers and their connection state.                                                                                           |\n| `disable_remote_mcp_server`     | After first enable      | Stop a server and remove its tools from the active set.                                                                                              |\n| `reset_remote_mcp_server_auth`  | After first enable      | Drop persisted OAuth credentials so the next enable triggers a fresh authorization flow. No-op for `none` servers.                       |\n\n### Workflow\n\n1. The agent calls `search_remote_mcp_servers` with a keyword matching the user's intent (`\"notion\"`, `\"stripe\"`, `\"docs\"`, `\"browser\"`, `\"grafana\"`, …).\n2. It picks a matching server id and calls `enable_remote_mcp_server`. **`enable` blocks** until the MCP handshake (and any required OAuth flow) completes:\n   - on success the server's tools are available **in the same turn** — the agent goes straight to the user's original request, no re-ask required;\n   - on failure (user dismissed the authorization dialog, server refused) the tool returns an error result naming the specific reason so the agent can recover instead of pretending the server is connected.\n3. It uses the newly activated tools as it would any other.\n4. When done, it calls `disable_remote_mcp_server` to remove the server from the active set.\n\n## Authentication\n\nThe catalog only includes servers Docker Agent can authenticate itself, so there are two auth flavours:\n\n- **`oauth`** — `enable_remote_mcp_server` surfaces an authorization URL through the elicitation pipeline (the same one used by YAML-declared remote MCP toolsets) and blocks until the user either authorizes or cancels. Once the user authorizes, tokens are persisted in the OS keyring and re-used on subsequent runs. Use `reset_remote_mcp_server_auth` to wipe them. If the user dismisses the dialog, `enable` returns an error result naming the decline so the agent can ask whether to retry.\n- **`none`** — No authentication. The server is reachable as soon as it is enabled.\n\nServers that require a caller-provided API key are intentionally excluded from the catalog. To use one, declare it explicitly with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) and supply the key via an environment variable.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Agent that can on-demand connect to remote MCP servers from the Docker MCP Catalog.\n    instruction: |\n      You can discover and activate remote MCP servers on demand.\n      Use search_remote_mcp_servers to find a server matching the\n      user's intent, then enable_remote_mcp_server to activate it.\n      Be conservative: enable only the servers you actually need for\n      the task at hand. Disable a server with disable_remote_mcp_server\n      once you are done with it.\n    toolsets:\n      - type: mcp_catalog\n```\n\nA complete, runnable configuration lives in [`examples/mcp_catalog.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog.yaml). A curated, allow/block-listed variant lives in [`examples/mcp_catalog_filtered.yaml`](https://github.com/docker/docker-agent/blob/main/examples/mcp_catalog_filtered.yaml).\n\n## Notes and Limitations\n\n- **Streamable-http only.** The catalog deliberately excludes servers that require a local subprocess or the MCP gateway — declare those with [`type: mcp`](../../configuration/tools/index.md#mcp-tools) instead.\n- **Catalog membership changes between releases.** The set of available servers is updated with each Docker Agent release as integrations are added or removed. Servers present in one release may not appear in the next.\n- **Blocking enable.** DNS, TCP, MCP handshake and any OAuth flow happen synchronously inside `enable_remote_mcp_server` so the agent gets a deterministic result in the same turn. On startup, however, the runtime probes tools non-interactively (`mcp.WithoutInteractivePrompts`); OAuth-pending servers fail fast there and are silently deferred to the next interactive turn — including the sidebar-only tool-count pass, where a dialog would be impossible.\n- **No prompt discovery.** MCP prompt lookups (`/prompts`) walk YAML-declared `mcp` toolsets directly; prompts exposed by servers activated through the catalog are not surfaced. Tools — the primary interface — work fine.\n- **Frozen at build time.** The list of servers is embedded in the binary. New entries land with each Docker Agent release.\n\n> [!TIP]\n> **Pair with permissions**\n>\n> Because the agent decides which third-party services to talk to, this toolset works best with explicit [permissions](../../configuration/permissions/index.md) on the surrounding tools (filesystem writes, shell commands) so a misrouted server cannot exfiltrate data unnoticed.\n\n\n<!-- Skill/Rule: Mcp Skill (_vendor/github.com/docker/docker-agent/docs/tools/mcp/index.md) -->\n---\ntitle: \"MCP Tool\"\ndescription: \"Extend agents with external tools via the Model Context Protocol.\"\nkeywords: docker agent, ai agents, tools, toolsets, mcp tool\nlinkTitle: \"MCP\"\nweight: 130\ncanonical: https://docs.docker.com/ai/docker-agent/tools/mcp/\naliases:\n  - /ai/docker-agent/integrations/mcp/\n---\n\n_Extend agents with external tools via the Model Context Protocol (MCP)._\n\n## Overview\n\nThe `mcp` toolset connects your agent to any MCP server — a process or remote service that exposes tools, resources, and prompts over the [Model Context Protocol](https://modelcontextprotocol.io/). Three flavours are supported:\n\n| Flavour | Transport | Best for |\n| --- | --- | --- |\n| **Docker MCP** | Container via the [MCP Gateway](https://github.com/docker/mcp-gateway) | Curated, sandboxed servers from the [Docker MCP Catalog](https://hub.docker.com/u/mcp) |\n| **Local stdio** | Subprocess over stdin/stdout | Custom or community MCP servers run from a binary or `npx`/`pip` package |\n| **Remote** | Streamable HTTP or SSE | Cloud services with hosted MCP endpoints (Linear, Notion, Atlassian, …) |\n\n> [!NOTE]\n> **What is MCP?**\n>\n> The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI tools. Docker Agent can both _use_ MCP servers (this page) and _expose_ agents as MCP servers — see [MCP Mode](../../features/mcp-mode/index.md).\n\n## Docker MCP (Recommended)\n\nRun MCP servers as secure Docker containers via the MCP Gateway. The `ref: docker:<name>` syntax pulls a curated definition from the Docker MCP Catalog:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo        # web search\n  - type: mcp\n    ref: docker:github-official   # GitHub integration\n    tools: [\"list_issues\", \"create_issue\"]\n```\n\nBrowse available servers at the [Docker MCP Catalog](https://hub.docker.com/u/mcp).\n\n| Property      | Type   | Description                                                      |\n| ------------- | ------ | ---------------------------------------------------------------- |\n| `ref`         | string | Docker MCP reference (`docker:name`) or a name from the [reusable `mcps:`](../../configuration/overview/index.md#reusable-mcp-servers-mcps) block. |\n| `tools`       | array  | Optional whitelist — only expose these tools to the model.       |\n| `instruction` | string | Custom instructions injected into the agent's context.           |\n| `config`      | any    | MCP server-specific configuration passed during initialization.  |\n| `working_dir` | string | Working directory for the MCP gateway subprocess. Only applies when the catalog entry runs as a local process (not remote). Relative paths are resolved against the agent's working directory. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n\n## Local MCP (stdio)\n\nRun MCP servers as local processes communicating over stdin/stdout:\n\n```yaml\ntoolsets:\n  - type: mcp\n    command: python\n    args: [\"-m\", \"mcp_server\"]\n    tools: [\"search\", \"fetch\"]\n    env:\n      API_KEY: value\n```\n\n| Property      | Type   | Description |\n| ------------- | ------ | ----------- |\n| `command`     | string | Command to execute the MCP server. |\n| `args`        | array  | Command arguments. |\n| `tools`       | array  | Optional whitelist — only expose these tools. |\n| `env`         | object | Environment variables (key-value pairs). |\n| `working_dir` | string | Working directory for the MCP server process. Relative paths are resolved against the agent's working directory. Defaults to the agent's working directory when omitted. Supports `${env.VAR}` (canonical), plus `~` and shell-style `$VAR`/`${VAR}` expansion ([details](../../configuration/overview/index.md#variable-expansion-in-config-fields)). |\n| `instruction` | string | Custom instructions injected into the agent's context. |\n| `version`     | string | Package reference for [auto-installing](../../configuration/tools/index.md#auto-installing-tools) the command binary. |\n\n> [!TIP]\n> **Auto-installation**\n>\n> If the `command` is not in your `PATH`, Docker Agent looks it up in the [aqua registry](https://github.com/aquaproj/aqua-registry) and installs it for you. Use `version: \"false\"` to opt out, or set `DOCKER_AGENT_AUTO_INSTALL=false` globally. See [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools).\n\n## Remote MCP (Streamable HTTP / SSE)\n\nConnect to MCP servers over the network. OAuth flows (including [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591)) are handled automatically — Docker Agent opens your browser when authentication is required and caches tokens for subsequent sessions. Tokens are refreshed silently when they expire or are revoked server-side; if a silent refresh is not possible, the OAuth prompt reappears on the next message.\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://mcp.linear.app/mcp\"\n      transport_type: \"streamable\"               # or \"sse\" for legacy servers\n      headers:\n        Authorization: \"Bearer ${env.LINEAR_TOKEN}\"\n    # Optional: allow OAuth helper requests to reach private/internal IPs.\n    allow_private_ips: false\n    tools: [\"search_issues\", \"create_issue\"]\n```\n\n| Property                | Type    | Description |\n| ----------------------- | ------- | ----------- |\n| `remote.url`            | string  | Base URL of the MCP server. |\n| `remote.transport_type` | string  | `streamable` or `sse`. |\n| `remote.headers`        | object  | HTTP headers sent on every request. Values support `${env.VAR}` and `${headers.NAME}` placeholders, resolved per request. See [Remote MCP Servers](../../features/remote-mcp/index.md#per-request-header-template-expansion) for details. |\n| `remote.oauth`          | object  | Explicit OAuth client credentials for servers that don't support DCR. See [Remote MCP Servers](../../features/remote-mcp/index.md#oauth-for-servers-without-dynamic-client-registration). |\n| `allow_private_ips`     | boolean | Permit remote MCP OAuth helper requests to dial non-public IP addresses. Use only for trusted internal servers. |\n\nFor a curated list of public remote MCP endpoints (Linear, GitHub, Vercel, Notion, …) and full OAuth configuration details, see [Remote MCP Servers](../../features/remote-mcp/index.md).\n\n## MCP Prompts\n\nMCP servers can expose **prompts** — named, parameterized templates that the server provides via the `/prompts` endpoint. Docker Agent discovers these at toolset startup and registers them as **slash commands** in the TUI, so you can invoke them directly from the input box.\n\n```text\n# Type / to see available prompts alongside built-in commands\n/review         # invoke an MCP prompt named \"review\"\n/summarize My text here   # invoke with the first argument filled in\n```\n\n**How it works:**\n\n- Each MCP prompt appears in the command palette (accessible via <kbd>Ctrl</kbd>+<kbd>K</kbd>) under the **MCP Prompts** category.\n- Typing `/<prompt-name>` in the input box invokes the prompt immediately.\n- If the prompt declares arguments and you provide text after the slash command, that text is mapped to the first declared argument.\n- If a required argument is missing, Docker Agent opens the argument input dialog before running the prompt.\n- When no argument is needed or all required arguments are supplied, the prompt runs immediately.\n\n> [!NOTE]\n> MCP prompt discovery requires a YAML-declared `mcp` toolset. Prompts from servers activated through the [Docker MCP Catalog](../../tools/mcp-catalog/index.md) (`ref: docker:<name>`) are not currently surfaced.\n\n## Embedded Resources\n\nMCP tool results can include embedded resources — images, PDFs, and text files returned directly in the tool response. Docker Agent preserves these as attachments and forwards them to the model as native content blocks:\n\n- **Anthropic** — images become `image` blocks in the `tool_result`; PDFs and other documents become `document` blocks.\n- **OpenAI** — images are forwarded as `input_image` data URIs; PDFs as `input_file` data URIs in the tool result content.\n- **Bedrock** and **Gemini** — receive equivalent provider-native representations.\n\nNo configuration is required. When an MCP server returns an embedded resource alongside its text output, the resource is automatically attached and sent to the model on the next turn. This is useful for MCP servers that generate charts, export PDFs, or return binary data as part of their responses.\n\n## Reusable Definitions (`mcps:`)\n\nRepeated MCP server configurations can be hoisted into the top-level `mcps:` section and referenced by name with `{type: mcp, ref: <name>}`:\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n  playwright:\n    command: npx\n    args: [\"-y\", \"@modelcontextprotocol/server-playwright\"]\n\nagents:\n  root:\n    model: openai/gpt-5\n    toolsets:\n      - type: mcp\n        ref: github\n      - type: mcp\n        ref: playwright\n```\n\nSee [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) for the full reference.\n\n## Common Options\n\nThese properties apply to every MCP toolset regardless of flavour:\n\n### Tool filtering\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    tools: [\"list_issues\", \"create_issue\", \"get_pull_request\"]\n```\n\nWhitelisting tools improves model accuracy — fewer choices means less confusion.\n\n### Deferred loading\n\nSkip the toolset's startup cost until its tools are actually called:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    defer: true\n  # Or defer specific tools within a toolset:\n  - type: mcp\n    ref: docker:slack\n    defer: [\"list_channels\", \"search_messages\"]\n```\n\n### Custom instructions\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    instruction: |\n      Use these tools to manage GitHub issues.\n      Always check for existing issues before creating new ones.\n      Label new issues with 'triage' by default.\n```\n\n### TOON-encoded outputs\n\nRe-encode verbose JSON outputs as the compact [TOON](https://github.com/alpkeskin/gotoon) format to save context budget. Typically yields 30–60% smaller payloads on list/search tools.\n\n`toon` is a regex string that is matched against tool names. Any tool whose name matches the pattern has its JSON output transparently re-encoded as TOON before it is shown to the model. The re-encoding reduces schema verbosity, which is especially useful when a model struggles with large or repetitive tool output.\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    toon: \".*\"            # toonify every tool from this server\n  - type: mcp\n    command: my-server\n    toon: \"list_.*,get_.*\" # only toonify list_/get_ tools\n```\n\nThe value is a comma-separated list of regexes (or a single regex). A tool name must match at least one pattern to be re-encoded. Setting `toon: \".*\"` re-encodes all tools from that toolset.\n\nSee [`examples/github-toon.yaml`](https://github.com/docker/docker-agent/blob/main/examples/github-toon.yaml) for a practical example using the GitHub MCP server.\n\n### Per-toolset model routing\n\nProcess tool results from this toolset with a different (typically cheaper / faster) model. The override is one-shot — subsequent turns return to the agent's primary model:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:github-official\n    model: openai/gpt-4o-mini\n```\n\nSee [Per-Toolset Model Routing](../../configuration/tools/index.md#per-toolset-model-routing).\n\n### Lifecycle (auto-restart, profiles)\n\nLocal stdio and remote MCP servers are supervised: crashed servers reconnect automatically with exponential backoff. **Remote** MCP servers (Streamable HTTP / SSE) also reconnect after idle/clean connection closes — services like Notion and Linear periodically close idle connections, and Docker Agent reconnects transparently. Tune the policy with the `lifecycle` block:\n\n```yaml\ntoolsets:\n  - type: mcp\n    ref: docker:duckduckgo\n    lifecycle:\n      profile: resilient   # default; auto-restart with backoff\n  - type: mcp\n    command: docker\n    args: [\"mcp\", \"gateway\"]\n    lifecycle:\n      profile: strict      # fail-fast: required, no retries\n```\n\nSee [Toolset Lifecycle](../../configuration/tools/index.md#toolset-lifecycle) for all profiles and tuning knobs, and [`/toolset-restart`](../../features/tui/index.md) to force a reconnect from the TUI.\n\n## Combined Example\n\n```yaml\nmcps:\n  github:\n    remote:\n      url: https://api.githubcopilot.com/mcp\n      transport_type: sse\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Full-featured developer assistant\n    instruction: You are an expert developer.\n    toolsets:\n      # Docker MCP catalog entry\n      - type: mcp\n        ref: docker:duckduckgo\n\n      # Reusable definition from the top-level mcps: block\n      - type: mcp\n        ref: github\n        tools: [\"list_issues\", \"create_issue\"]\n        toon: \"list_.*\"\n\n      # Local stdio server with auto-install\n      - type: mcp\n        command: gopls\n        version: \"golang/tools@v0.21.0\"\n        args: [\"mcp\"]\n\n      # Remote MCP with OAuth (handled automatically)\n      - type: mcp\n        remote:\n          url: \"https://mcp.linear.app/mcp\"\n          transport_type: \"streamable\"\n        instruction: Use Linear for issue tracking.\n```\n\n> [!WARNING]\n> **Toolset order matters**\n>\n> If multiple toolsets provide a tool with the same name, the first one wins: the duplicate from the later toolset is ignored and a warning identifies both toolsets. Order your toolsets intentionally. To keep both tools callable, give the MCP toolset a unique `name:` (its tools are then exposed as `<name>_<tool>`) or restrict the overlapping toolset with its `tools:` filter.\n\n## See Also\n\n- [Tool Configuration](../../configuration/tools/index.md) — full reference for every toolset type, plus shared options (lifecycle, TOON, model routing, …).\n- [Reusable MCP Servers](../../configuration/overview/index.md#reusable-mcp-servers-mcps) — the top-level `mcps:` block.\n- [Remote MCP Servers](../../features/remote-mcp/index.md) — catalog of public remote MCP endpoints + OAuth recipes.\n- [MCP Mode](../../features/mcp-mode/index.md) — expose your own agents as MCP tools to Claude Desktop, Claude Code, etc.\n- [Auto-Installing Tools](../../configuration/tools/index.md#auto-installing-tools) — automatic installation of MCP server binaries.\n\n\n<!-- Skill/Rule: Memory Skill (_vendor/github.com/docker/docker-agent/docs/tools/memory/index.md) -->\n---\ntitle: \"Memory Tool\"\ndescription: \"Persistent key-value storage backed by SQLite for cross-session recall.\"\nkeywords: docker agent, ai agents, tools, toolsets, memory tool\nlinkTitle: \"Memory\"\nweight: 100\ncanonical: https://docs.docker.com/ai/docker-agent/tools/memory/\n---\n\n_Persistent key-value storage backed by SQLite for cross-session recall._\n\n## Overview\n\nThe memory tool provides persistent key-value storage backed by SQLite. Data survives across sessions, allowing agents to remember facts, user preferences, project context, and past decisions. Memories can be organized with categories and searched by keyword.\n\nBy default, the database is stored at `~/.cagent/memory/<config-name>/memory.db`, where `<config-name>` is derived from the loaded configuration (typically the YAML file name) and falls back to `default` when unavailable. When the agent is loaded from an OCI reference (e.g. `docker/my-agent:latest`), characters that are reserved in filesystem paths (such as `:`) are sanitised in the `<config-name>` segment — the agent's display name elsewhere is unchanged. Agents declared in the same configuration share this database by default; set an explicit `path` per toolset to isolate them.\n\n## Available Tools\n\n| Tool              | Description                                                                      |\n| ----------------- | -------------------------------------------------------------------------------- |\n| `add_memory`      | Store a new memory with optional category                                        |\n| `get_memories`    | Retrieve all stored memories                                                     |\n| `delete_memory`   | Delete a specific memory by ID                                                   |\n| `search_memories` | Search memories by keywords and/or category (more efficient than `get_memories`) |\n| `update_memory`   | Update an existing memory's content and/or category by ID                        |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: memory\n```\n\n### Options\n\n| Property | Type   | Default                                   | Description                      |\n| -------- | ------ | ----------------------------------------- | -------------------------------- |\n| `path`   | string | `~/.cagent/memory/<config-name>/memory.db` | Path to the SQLite database file |\n\n### Custom Database Path\n\n```yaml\ntoolsets:\n  - type: memory\n    path: ./agent_memory.db\n```\n\n## Categories\n\nMemories support an optional `category` field for organization and filtering. Common categories include:\n\n- `preference` — User preferences and settings\n- `fact` — Factual information about the project or user\n- `project` — Project-specific context\n- `decision` — Past decisions and their rationale\n\n> [!TIP]\n> Memory is especially useful for long-running assistants that need to recall information across conversations — like coding preferences, project conventions, or context discovered during previous sessions.\n\n\n<!-- Skill/Rule: Model-picker Skill (_vendor/github.com/docker/docker-agent/docs/tools/model-picker/index.md) -->\n---\ntitle: \"Model Picker Tool\"\ndescription: \"Let the agent pick between several models per turn.\"\nkeywords: docker agent, ai agents, tools, toolsets, model picker tool\nlinkTitle: \"Model Picker\"\nweight: 200\ncanonical: https://docs.docker.com/ai/docker-agent/tools/model-picker/\n---\n\n_Let the agent pick between several models per turn._\n\n## Overview\n\nThe model picker tool gives an agent the ability to dynamically choose which model to use for each turn of the conversation. This is useful when you want the agent to route different types of requests to different models — for example, using a fast, inexpensive model for simple queries and a more capable model for complex reasoning tasks.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: model_picker\n    models:\n      - openai/gpt-5-mini\n      - anthropic/claude-sonnet-4-5\n      - openai/gpt-5\n```\n\n### Options\n\n| Property | Type           | Required | Description                                                  |\n| -------- | -------------- | -------- | ------------------------------------------------------------ |\n| `models` | array[string]  | ✓        | List of model references the agent can choose from. Use `provider/model` format. |\n\n## How It Works\n\nWhen the model picker toolset is enabled, the agent gets two tools: `change_model` to switch to one of the configured models, and `revert_model` to return to its default model. The agent decides which model to use based on the complexity of the task, cost considerations, or other factors you describe in its instruction.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini  # Default model\n    instruction: |\n      You are a helpful assistant. For simple questions, use gpt-5-mini.\n      For complex reasoning or coding tasks, switch to claude-sonnet-4-5 or gpt-5.\n    toolsets:\n      - type: model_picker\n        models:\n          - openai/gpt-5-mini\n          - anthropic/claude-sonnet-4-5\n          - openai/gpt-5\n```\n\n> [!TIP]\n> **Cost optimization**\n>\n> The model picker tool is particularly useful for cost optimization: let the agent use a cheap model by default and only escalate to expensive models when necessary.\n\n## Tool Interface\n\nThe toolset exposes two tools:\n\n### `change_model`\n\n| Parameter | Type   | Required | Description                                                                 |\n| --------- | ------ | -------- | --------------------------------------------------------------------------- |\n| `model`   | string | ✓        | The model to switch to. Must be one of the configured models.               |\n\n### `revert_model`\n\nTakes no parameters. Reverts the agent to its original/default model.\n\nThe switch takes effect immediately: the next inference call — including the remainder of the current agentic loop — uses the new model.\n\n\n<!-- Skill/Rule: Open-url Skill (_vendor/github.com/docker/docker-agent/docs/tools/open-url/index.md) -->\n---\ntitle: \"Open URL Tool\"\ndescription: \"Open a fixed URL in the user's default browser.\"\nkeywords: docker agent, ai agents, tools, toolsets, open url tool\nlinkTitle: \"Open URL\"\nweight: 40\ncanonical: https://docs.docker.com/ai/docker-agent/tools/open-url/\n---\n\n_Open a fixed URL in the user's default browser._\n\n## Overview\n\nThe `open_url` toolset exposes a single, argument-less tool that opens a URL\nbaked into the toolset definition in the user's default browser. The model\nnever supplies the URL — it just calls the tool by name. Launching the browser\nis cross-platform: Docker Agent uses `open` on macOS, `xdg-open` on Linux, and\n`rundll32` on Windows.\n\n> [!NOTE]\n> **When to Use**\n>\n> - Letting an agent open a dashboard, documentation page, or deep link on demand\n> - Deep-linking into a desktop app via a custom URI scheme (e.g. `docker-desktop://`)\n> - Any \"take me there\" action where the destination is fixed and known up front\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Assistant that can open the dashboard\n    instruction: When the user asks to see the dashboard, call open_dashboard.\n    toolsets:\n      - type: open_url\n        name: open_dashboard\n        url: https://example.com/dashboard\n```\n\n## Properties\n\n| Property | Type   | Required | Description                                                                                          |\n| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |\n| `url`    | string | ✓        | URL to open. Supports `${env.VAR}` interpolation. Any scheme the OS can dispatch is allowed.         |\n| `name`   | string | ✗        | Tool name the agent references. Defaults to `open_url`. Use a descriptive name when configuring several. |\n\n## Multiple URLs\n\nAdd one toolset entry per destination, each with its own `name`:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_dashboard\n    url: https://example.com/dashboard\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## URL Interpolation\n\nThe `url` field supports `${env.VAR}` placeholders, expanded at call time\nagainst the runtime environment:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_docs\n    url: https://docs.example.com/${env.DOCS_VERSION}\n```\n\n## Custom URI Schemes\n\nAny scheme the operating system knows how to dispatch works, including deep\nlinks into desktop applications:\n\n```yaml\ntoolsets:\n  - type: open_url\n    name: open_in_docker_desktop\n    url: docker-desktop://dashboard/apps\n```\n\n## Limitations\n\n- The URL must include a scheme (e.g. `https://`); bare paths are rejected.\n- URLs that look like a command-line flag (starting with `-`) are refused to\n  prevent argument injection into the platform `open` helper.\n- The tool opens the URL on the **host** running Docker Agent; in headless or\n  remote environments where no browser/launcher is available, the call fails\n  gracefully and reports the error to the agent.\n\nSee [`examples/open_url.yaml`](https://github.com/docker/docker-agent/blob/main/examples/open_url.yaml) for a complete configuration.\n\n\n<!-- Skill/Rule: Openapi Skill (_vendor/github.com/docker/docker-agent/docs/tools/openapi/index.md) -->\n---\ntitle: \"OpenAPI Tool\"\ndescription: \"Automatically generate tools from an OpenAPI specification.\"\nkeywords: docker agent, ai agents, tools, toolsets, openapi tool\nlinkTitle: \"OpenAPI\"\nweight: 230\ncanonical: https://docs.docker.com/ai/docker-agent/tools/openapi/\n---\n\n_Automatically generate tools from an OpenAPI specification._\n\n## Overview\n\nThe OpenAPI tool fetches an OpenAPI 3.x specification from a URL and creates one tool per API operation. Each endpoint's parameters, request body, and description are translated into a callable tool that the agent can invoke directly.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://petstore3.swagger.io/api/v3/openapi.json\"\n```\n\n### With custom headers\n\nPass custom headers to every HTTP request made by the generated tools (for example, for authentication):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    headers:\n      Authorization: \"Bearer ${env.API_TOKEN}\"\n      X-Custom-Header: \"my-value\"\n```\n\n### Custom timeout\n\nOverride the default 30-second HTTP timeout (applies both to fetching the spec and to the generated tool calls):\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"https://api.example.com/openapi.json\"\n    timeout: 60\n```\n\n### Reaching internal services\n\nBy default the OpenAPI tool refuses connections to non-public IP addresses, blocking SSRF attempts even when DNS resolves an otherwise-public host to an internal range. Opt in with `allow_private_ips` when the spec or its `servers` entries legitimately target localhost or your internal network:\n\n```yaml\ntoolsets:\n  - type: openapi\n    url: \"http://localhost:8080/openapi.json\"\n    allow_private_ips: true\n```\n\n## Properties\n\n| Property            | Type              | Required | Description                                                                                                                                                                                                                                                       |\n| ------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `url`               | string            | ✓        | URL of the OpenAPI specification (JSON format). Supports `${env.VAR}` interpolation.                                                                                                                                                                              |\n| `headers`           | map[string]string | ✗        | Custom HTTP headers sent with every request — both the spec fetch and every generated tool call. Values support `${env.VAR}` and `${headers.NAME}` placeholders (the latter forwards a header from the caller's incoming request when docker agent is exposed as a server). |\n| `timeout`           | int               | ✗        | HTTP client timeout in seconds (default: `30`). Applies to both the spec fetch and the generated tools' requests.                                                                                                                                                 |\n| `allow_private_ips` | boolean           | ✗        | Opt in to dialling **non-public** IP addresses (loopback, RFC1918, link-local — including the cloud-metadata endpoint at `169.254.169.254` — multicast and the unspecified address). Set to `true` only when the spec or its servers legitimately target internal services. By default such addresses are refused at dial time, after DNS resolution, so DNS rebinding cannot bypass the check. |\n\n## How it works\n\n1. The spec is fetched from the configured `url` at startup.\n2. Each operation (GET, POST, PUT, …) becomes a separate tool named after its `operationId` (or `method_path` when no `operationId` is set).\n3. Path and query parameters are exposed as tool parameters. Request body properties are prefixed with `body_`.\n4. Read-only operations (GET, HEAD, OPTIONS) are annotated accordingly.\n5. Responses are returned as text; errors include the HTTP status code.\n\n## Limits\n\n- The OpenAPI spec must be **10 MB or less**.\n- Individual API responses are truncated at **1 MB**.\n\n## Example\n\nSee the full [Pet Store example](https://github.com/docker/docker-agent/blob/main/examples/openapi-petstore.yaml) for a working agent configuration.\n\n\n<!-- Skill/Rule: Plan Skill (_vendor/github.com/docker/docker-agent/docs/tools/plan/index.md) -->\n---\ntitle: \"Plan Tool\"\ndescription: \"Shared persistent scratchpad for multi-agent collaboration.\"\nkeywords: docker agent, ai agents, tools, toolsets, plan tool\nlinkTitle: \"Plan\"\nweight: 150\ncanonical: https://docs.docker.com/ai/docker-agent/tools/plan/\n---\n\n_Shared persistent scratchpad for multi-agent collaboration._\n\n## Overview\n\nThe plan tool gives agents a shared, persistent scratchpad of named documents. Any agent in a multi-agent config that loads the `plan` toolset can read and write the same plans, and those plans survive across sessions. This makes it straightforward to wire a planner agent that sketches work and one or more executor agents that consume it without any custom tool wiring.\n\nPlans are stored as JSON files in the Docker Agent data directory (`~/.cagent/plans/` by default). Agents that share a process serialize on a single mutex, and every write or delete additionally holds an advisory lock on a sentinel file in the plans directory, so writers in *separate* Docker Agent processes are serialized too: concurrent edits can never silently overwrite each other, and a stale revision always fails with a deterministic version conflict. Writes are atomic (temp file + rename), so a reader never observes partial content.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: plan\n```\n\nNo additional options are required. All agents that include `type: plan` in their toolsets share the same plans.\n\n## Available Tools\n\n| Tool                    | Description                                                                                       |\n| ----------------------- | ------------------------------------------------------------------------------------------------- |\n| `write_plan`            | Create or update a shared plan by name. Replaces the entire plan content — read it first to preserve what you want to keep. Each write bumps the revision number. |\n| `read_plan`             | Read a shared plan by name, including its title, content, author, status, revision number, and last-updated timestamp. |\n| `list_plans`            | List all shared plans with their name, title, author, status, revision, and last-updated timestamp. |\n| `delete_plan`           | Delete a shared plan by name.                                                                     |\n| `update_plan_from_file` | Create or update a plan, taking the new content from a file on disk instead of inline. Use it with `export_plan_to_file` to edit a large plan without re-sending its whole body. |\n| `export_plan_to_file`   | Write a plan's content to a file. The content goes to disk and is **not** returned as tool output, so materialising a plan costs no tokens. |\n| `set_plan_status`       | Set a plan's free-form status without rewriting its body. The plan must already exist. |\n| `get_plan_status`       | Read a plan's status and current revision without fetching its body.                  |\n\n### Cheap edits with file-based revisions\n\nRe-sending a whole plan on every revision is expensive. The file-based tools let\nan agent edit a plan without paying input-token cost for its body:\n\n1. `export_plan_to_file` writes the current plan content to a path. The content\n   is written to disk and is **not** returned.\n2. The agent edits that file in place with its filesystem tools.\n3. `update_plan_from_file` commits the file's new contents as the next revision.\n\n### Free-form status\n\nEach plan carries a free-form `status` string. There is no fixed vocabulary:\ndefine your own in the system prompt (e.g. `idle`, `in-progress`, `blocked`,\n`done`, `canceled`). Read and write it independently of the body with\n`get_plan_status` and `set_plan_status`, or pass `status` to `write_plan` and\n`update_plan_from_file`. The TUI surfaces the status next to the plan title.\n\n### Optimistic locking\n\nWhen several sessions edit the same plan, concurrent writes could silently\noverwrite each other. Every read returns a `revision` number; pass the value you\nlast read as `last_known_revision` to `write_plan`, `update_plan_from_file`,\n`set_plan_status`, or `delete_plan`. If the plan changed since (its current\nrevision no longer matches), the write is rejected with a version-conflict\nerror and the caller should re-read the plan and retry. The revision check and\nthe write happen under the storage's cross-process file lock, so the conflict\nis detected reliably even when the competing writer runs in a different Docker\nAgent process. Omit `last_known_revision` to write unconditionally (last\nwriter wins).\n\n### Plan Names\n\nPlan names must match the pattern `[a-z0-9][a-z0-9_-]*` (lowercase letters, digits, `-`, `_`). This is enforced structurally so two different inputs can never collapse onto the same file and path-traversal is impossible by construction.\n\n### Plan Fields\n\nEach plan document contains:\n\n| Field      | Description                                               |\n| ---------- | --------------------------------------------------------- |\n| `name`     | The plan's unique slug name                               |\n| `title`    | A short human-readable title (optional)                   |\n| `content`  | The full Markdown or free-form plan text                  |\n| `author`   | Free-form label identifying who last wrote the plan       |\n| `status`   | Free-form lifecycle label (optional), e.g. `in-progress`  |\n| `revision` | Monotonically increasing version counter, bumped on every write |\n| `updatedAt`| ISO 8601 timestamp of the last write                      |\n\n## Example\n\nTwo agents collaborate on a shared plan — the architect drafts it and the builder refines it:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Coordinator\n    instruction: |\n      Route work between the architect and the builder.\n    handoffs: [architect, builder]\n\n  architect:\n    model: anthropic/claude-sonnet-4-5\n    description: Drafts high-level plans\n    instruction: |\n      Use list_plans and read_plan to inspect existing plans, then write_plan\n      to create or revise one. Always read before writing. When done, hand off\n      to the builder.\n    toolsets:\n      - type: plan\n    handoffs: [builder]\n\n  builder:\n    model: openai/gpt-4o\n    description: Adds implementation steps to plans\n    instruction: |\n      Read the architect's plan with read_plan, then use write_plan to append\n      concrete implementation steps. Always read before writing. When done,\n      hand off back to root.\n    toolsets:\n      - type: plan\n    handoffs: [root]\n```\n\nSee [`examples/shared_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_plan` returns a distinct \"not found\" error when a plan does not exist, as opposed to any other I/O error, so callers can tell \"plan missing\" from \"plan unreadable.\"\n- `list_plans` skips corrupt entries but reports them in a `warnings` field so an agent can detect and recover from a bad state (e.g., by calling `delete_plan`).\n- `delete_plan` can remove a corrupt plan to recover from a bad state.\n\n## Managing plans from the host\n\nShared plans can also be inspected and managed outside a session with the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group: list, get, create, update, set status, export, and delete — with the same optimistic-locking semantics as the tools (`--expected-version` guards a write and a stale version fails with exit code 3; `--force` writes unconditionally). Session plans (the per-session \"draft, review, execute\" plan) can be listed, read, and exported through the same commands but stay owned by their session and cannot be mutated from the host.\n\n```bash\n$ docker agent plans list\n$ docker agent plans get release > plan.md\n$ docker agent plans update release --file ./plan.md --expected-version 1\n```\n\n### The `/plans` browser in the TUI\n\nInside the full-screen TUI, the `/plans` slash command (also in the <kbd>Ctrl</kbd>+<kbd>K</kbd> command palette) opens a plan browser over the same store the agents use, so changes made by agents mid-session appear immediately. The list shows every shared plan plus the current session's [session plan](../session_plan/index.md), with each plan's scope, identity (name, or session ID for the session plan), status, version (`-` for the unversioned session plan), last update time, and title.\n\nKeybindings:\n\n| Key | Action |\n| --- | ------ |\n| <kbd>↑</kbd>/<kbd>↓</kbd>, mouse | Navigate; <kbd>Enter</kbd> or double-click opens a detail view with the full metadata and scrollable markdown content |\n| <kbd>/</kbd> | Filter by name, title, status, or scope (<kbd>Esc</kbd> leaves filter mode) |\n| <kbd>r</kbd> | Refresh from storage |\n| <kbd>x</kbd> | Export the selected plan to `<name>.md` (shared) or `session-plan-<short-id>.md` (session) in the session's working directory. An existing file is never overwritten — the export fails with a notification instead |\n| <kbd>s</kbd> | Set a shared plan's free-form status via a small input dialog |\n| <kbd>e</kbd> | Edit a shared plan's content in `$VISUAL`/`$EDITOR` |\n| <kbd>n</kbd> | Create a new shared plan: pick a name, then draft the content in `$VISUAL`/`$EDITOR` (an empty draft aborts) |\n| <kbd>d</kbd> | Delete a shared plan after a confirmation that names the plan and its version |\n| <kbd>Esc</kbd> | Close the detail view / the browser |\n\nEvery mutation is guarded by the version shown on screen (the same optimistic locking as `last_known_revision`): if an agent changed the plan in the meantime, the write is rejected, a notification reports the current version, the newer content is left intact and re-read into the browser, and an edit draft is kept in a temp file so nothing is lost. Session plans are read-only here — status, edit, and delete report why instead of attempting the write. The browser also refreshes live when agents in the same process write, re-status, or delete plans (and when this session's agent updates its session plan); in the lean TUI, which has no overlays, `/plans` is unavailable.\n\n> [!TIP]\n> **Plan vs. Todo vs. Tasks**\n>\n> Use **plan** for shared, free-form documents that multiple agents collaborate on (design docs, requirements, work items). Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n\n\n<!-- Skill/Rule: Rag Skill (_vendor/github.com/docker/docker-agent/docs/tools/rag/index.md) -->\n---\ntitle: \"RAG Tool\"\ndescription: \"Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search.\"\nkeywords: docker agent, ai agents, tools, toolsets, rag tool\nlinkTitle: \"RAG\"\nweight: 110\ncanonical: https://docs.docker.com/ai/docker-agent/tools/rag/\naliases:\n  - /ai/docker-agent/rag/\n---\n\n_Give your agents access to document knowledge bases with background indexing, multiple retrieval strategies, and hybrid search._\n\n## Overview\n\nThe `rag` toolset lets agents search through your documents to find relevant information before responding. Knowledge bases are declared once at the top of the config under `rag:` and then referenced from any agent via `type: rag, ref: <name>`. Docker Agent supports:\n\n- **Background indexing** — Files are indexed automatically and re-indexed on change\n- **Multiple strategies** — Semantic embeddings, BM25 keyword search, and LLM-enhanced search\n- **Hybrid search** — Combine strategies with result fusion for best results\n- **Reranking** — Re-score results with specialized models for improved relevance\n\nRAG is the strategy to reach for when a document collection is too large to inline directly, or gets queried repeatedly across turns/sessions — see [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how it compares to `@`/`/attach` attachments and prompt files.\n\n## Quick Start\n\n```yaml\nrag:\n  my_docs:\n    tool:\n      description: \"Technical documentation\"\n    docs: [./documents, ./some-doc.md]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./docs.db\n        vector_dimensions: 1536\n\nagents:\n  root:\n    model: openai/gpt-4o\n    instruction: |\n      You have access to a knowledge base. Use it to answer questions.\n    toolsets:\n      - type: rag\n        ref: my_docs\n```\n\n## Retrieval Strategies\n\n### Chunked Embeddings (Semantic Search)\n\nUses embedding models to find semantically similar content. Best for understanding intent, synonyms, and paraphrasing.\n\n```yaml\nstrategies:\n  - type: chunked-embeddings\n    embedding_model: openai/text-embedding-3-small\n    database: ./vector.db\n    vector_dimensions: 1536\n    similarity_metric: cosine_similarity\n    threshold: 0.5\n    limit: 10\n    embedding_batch_size: 50\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n### Semantic Embeddings (LLM-Enhanced)\n\nUses an LLM to generate semantic summaries of each chunk before embedding, capturing meaning and intent. Best for code search and understanding implementations.\n\n```yaml\nstrategies:\n  - type: semantic-embeddings\n    embedding_model: openai/text-embedding-3-small\n    vector_dimensions: 1536\n    chat_model: openai/gpt-4o-mini\n    database: ./semantic.db\n    ast_context: true # include AST metadata\n    chunking:\n      size: 1000\n      code_aware: true # AST-aware chunking\n```\n\n> [!NOTE]\n> **Trade-offs**\n>\n> Semantic embeddings provide higher quality retrieval but slower indexing (LLM call per chunk) and additional API costs.\n\n### BM25 (Keyword Search)\n\nTraditional keyword matching using the BM25 algorithm. Best for exact terms, technical jargon, and code identifiers.\n\n```yaml\nstrategies:\n  - type: bm25\n    database: ./bm25.db\n    k1: 1.5 # term frequency saturation\n    b: 0.75 # length normalization\n    threshold: 0.3\n    limit: 10\n    chunking:\n      size: 1000\n      overlap: 100\n```\n\n## Hybrid Search\n\nCombine multiple strategies for best results. Strategies run in parallel and results are fused together:\n\n```yaml\nrag:\n  hybrid:\n    docs: [./docs]\n    strategies:\n      - type: chunked-embeddings\n        embedding_model: openai/text-embedding-3-small\n        database: ./vector.db\n        vector_dimensions: 1536\n        limit: 20\n        chunking: { size: 1000, overlap: 100 }\n      - type: bm25\n        database: ./bm25.db\n        limit: 15\n        chunking: { size: 1000, overlap: 100 }\n    results:\n      fusion:\n        strategy: rrf # Reciprocal Rank Fusion\n        k: 60\n      deduplicate: true\n      limit: 5\n```\n\n## Fusion Strategies\n\n| Strategy   | Best For                          | Description                                                        |\n| ---------- | --------------------------------- | ------------------------------------------------------------------ |\n| `rrf`      | General use (recommended)         | Reciprocal Rank Fusion — rank-based, no score normalization needed |\n| `weighted` | Known performance characteristics | Weight strategies differently (e.g., embeddings: 0.7, BM25: 0.3)   |\n| `max`      | Same scoring scale                | Takes the maximum score from any strategy                          |\n\n## Reranking\n\nRe-score retrieved documents with a specialized model to improve relevance:\n\n```yaml\nresults:\n  reranking:\n    model: openai/gpt-4o-mini\n    top_k: 10 # only rerank top 10\n    threshold: 0.3 # minimum score after reranking\n    criteria: |\n      Prioritize official documentation over blog posts.\n      Prefer recent information and practical examples.\n  limit: 5\n```\n\nSupported reranking providers: **DMR** (native `/rerank` endpoint), **OpenAI**, **Anthropic**, **Gemini**.\n\n## Code-Aware Chunking\n\nFor source code, enable AST-based chunking to keep functions and methods intact:\n\n```yaml\nchunking:\n  size: 2000\n  code_aware: true # Uses tree-sitter for AST-based chunking\n```\n\n> [!NOTE]\n> **Language Support**\n>\n> Currently supports Go (`.go`) files. More languages will be added. Falls back to plain text chunking for unsupported file types.\n\n## Debugging RAG\n\nEnable debug logging to see retrieval details:\n\n```bash\n$ docker agent run config.yaml --debug --log-file debug.log\n```\n\nLook for log tags: `[RAG Manager]`, `[Chunked-Embeddings Strategy]`, `[BM25 Strategy]`, `[RRF Fusion]`, `[Reranker]`.\n\n**Permanent model errors abort early.** If the embedding model, semantic-LLM model, or reranking model returns a permanent error (HTTP 400, 401, 404, or 429 — invalid config, bad auth, unknown model, or rate limit), Docker Agent treats the model configuration as invalid and stops immediately rather than retrying doomed requests:\n\n- **Indexing** — the entire indexing run is aborted after the first permanent failure (including 429). The error is surfaced in the logs so you know immediately if a model name or API key is wrong, rather than silently producing incomplete results.\n- **Reranking** — a permanent error (including 429) permanently disables the reranker for the lifetime of the manager. Subsequent queries fall back to un-reranked results. Only transient errors (5xx, timeouts) fall back and retry on the next query.\n\n> [!TIP]\n> **Examples**\n>\n> See the [RAG examples](https://github.com/docker/docker-agent/tree/main/examples/rag) in the GitHub repo for complete, runnable configurations.\n\n## Configuration Reference\n\n### Top-Level RAG Fields\n\n| Field         | Type     | Default | Description                                                    |\n| ------------- | -------- | ------- | -------------------------------------------------------------- |\n| `docs`        | []string | —       | Document paths/directories (shared across strategies)          |\n| `description` | string   | —       | Human-readable description of this RAG source                  |\n| `respect_vcs` | boolean  | `true`  | Respect `.gitignore` files when indexing documents             |\n| `strategies`  | []object | —       | Array of retrieval strategy configurations                     |\n| `results`     | object   | —       | Post-processing: fusion, reranking, deduplication, final limit |\n\n### Chunked-Embeddings Strategy\n\n| Field                       | Type   | Default             | Description                                                  |\n| --------------------------- | ------ | ------------------- | ------------------------------------------------------------ |\n| `embedding_model`           | string | —                   | **Required.** Embedding model reference                      |\n| `database`                  | string | —                   | Path to local SQLite database                                |\n| `vector_dimensions`         | int    | —                   | Embedding dimensions (e.g., 1536 for text-embedding-3-small) |\n| `similarity_metric`         | string | `cosine_similarity` | Similarity metric                                            |\n| `threshold`                 | float  | `0.5`               | Minimum similarity score (0–1)                               |\n| `limit`                     | int    | `5`                 | Max results from this strategy                               |\n| `embedding_batch_size`      | int    | `50`                | Chunks per embedding request                                 |\n| `max_embedding_concurrency` | int    | `3`                 | Max concurrent embedding requests                            |\n| `chunking.size`             | int    | `1500`              | Chunk size in characters (`4000` when `code_aware` is set)   |\n| `chunking.overlap`          | int    | `75`                | Overlap between chunks in characters                         |\n| `chunking.code_aware`       | bool   | `false`             | AST-based chunking (Go files only)                           |\n\n### Semantic-Embeddings Strategy\n\n| Field                      | Type   | Default    | Description                                                        |\n| -------------------------- | ------ | ---------- | ------------------------------------------------------------------ |\n| `embedding_model`          | string | —          | **Required.** Embedding model reference                            |\n| `chat_model`               | string | —          | **Required.** LLM for generating semantic summaries                |\n| `vector_dimensions`        | int    | —          | **Required.** Embedding dimensions                                 |\n| `database`                 | string | —          | Path to local SQLite database                                      |\n| `semantic_prompt`          | string | (built-in) | Custom prompt template (`${path}`, `${content}`, `${ast_context}`) |\n| `ast_context`              | bool   | `false`    | Include tree-sitter AST metadata in prompts                        |\n| `threshold`                | float  | `0.5`      | Minimum similarity score (0–1)                                     |\n| `limit`                    | int    | `5`        | Max results                                                        |\n| `max_indexing_concurrency` | int    | `3`        | Max concurrent file indexing                                       |\n| `chunking.size`            | int    | `1500`     | Chunk size in characters (`4000` when `code_aware` is set)         |\n| `chunking.overlap`         | int    | `75`       | Overlap between chunks                                             |\n| `chunking.code_aware`      | bool   | `false`    | AST-based chunking                                                 |\n\n### BM25 Strategy\n\n| Field              | Type   | Default | Description                                     |\n| ------------------ | ------ | ------- | ----------------------------------------------- |\n| `database`         | string | —       | Path to local SQLite database                   |\n| `k1`               | float  | `1.5`   | Term frequency saturation (1.2–2.0 recommended) |\n| `b`                | float  | `0.75`  | Length normalization (0–1)                      |\n| `threshold`        | float  | `0.0`   | Minimum BM25 score                              |\n| `limit`            | int    | `5`     | Max results                                     |\n| `chunking.size`    | int    | `1500`  | Chunk size in characters                        |\n| `chunking.overlap` | int    | `75`    | Overlap between chunks                          |\n\n### Results (Post-Processing)\n\n| Field                 | Type   | Default | Description                                                 |\n| --------------------- | ------ | ------- | ----------------------------------------------------------- |\n| `fusion.strategy`     | string | `rrf`   | Fusion method: `rrf`, `weighted`, or `max`                  |\n| `fusion.k`            | int    | `60`    | RRF rank constant                                           |\n| `deduplicate`         | bool   | `true`  | Remove duplicate results                                    |\n| `limit`               | int    | `15`    | Final number of results                                     |\n| `include_score`       | bool   | `false` | Include relevance scores in results                         |\n| `return_full_content` | bool   | `false` | Return full document content instead of just matched chunks |\n| `reranking.model`     | string | —       | Reranking model reference                                   |\n| `reranking.top_k`     | int    | (`limit`) | Only rerank top K results. Defaults to the results `limit` when set.  |\n| `reranking.threshold` | float  | `0.5`   | Minimum relevance score after reranking                     |\n| `reranking.criteria`  | string | —       | Custom relevance guidance for the reranking model           |\n\n\n<!-- Skill/Rule: Scheduler Skill (_vendor/github.com/docker/docker-agent/docs/tools/scheduler/index.md) -->\n---\ntitle: \"Scheduler Tool\"\ndescription: \"Schedule instructions to run at a time or on a recurring interval.\"\nkeywords: docker agent, ai agents, tools, toolsets, scheduler tool, cron\nlinkTitle: \"Scheduler\"\nweight: 135\ncanonical: https://docs.docker.com/ai/docker-agent/tools/scheduler/\n---\n\n_Schedule instructions to run at a time or on a recurring interval._\n\n## Overview\n\nThe scheduler toolset lets an agent make something happen at a chosen time or on a repeating cadence during a session. You give it an instruction and a schedule; when the schedule is due, the instruction is delivered back to the agent, which then carries out the action with its normal tools (`shell`, `api`, `fetch`, and so on).\n\nThe scheduler does not run shell or API calls itself. When a schedule fires it injects the instruction into the agent loop via the runtime's recall mechanism — the same primitive [`background_jobs`](../background-jobs/index.md) uses to report completed work — and the agent decides how to act. This keeps every action under the agent's normal tools and permissions rather than adding a second, unattended\ncommand runner.\n\n> [!NOTE]\n> Schedules only fire while the session is running (interactive TUI or a server mode) and are not persisted across restarts. Scheduling requires a host that supports recall; if it does not, `create_schedule` returns an error.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: scheduler\n```\n\nNo configuration options.\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `create_schedule` | Register an instruction to run at a time or interval. |\n| `list_schedules` | List active schedules with their id, spec, and next fire time. |\n| `cancel_schedule` | Remove a schedule by id. |\n\n### `create_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `prompt` | Yes | The instruction to deliver to the agent when the schedule fires. |\n| `when` | Yes | When to fire (see [Schedule specs](#schedule-specs)). |\n| `name` | No | Optional human-readable label. |\n\nReturns the new schedule's id and its next fire time.\n\n### `cancel_schedule`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `id` | Yes | The id of the schedule to cancel (from `create_schedule` or `list_schedules`). |\n\n## Schedule specs\n\nThe `when` argument accepts:\n\n| Form | Meaning | Example |\n| --- | --- | --- |\n| `in:<duration>` | One-shot, after a delay | `in:10m` |\n| `at:<RFC3339>` | One-shot, at an absolute future time | `at:2026-07-14T09:00:00Z` |\n| `every:<duration>` | Recurring, at a fixed interval | `every:1h` |\n| `minutely` / `hourly` / `daily` / `weekly` | Recurring preset intervals | `hourly` |\n\nDurations use Go's duration syntax (`30s`, `15m`, `2h`). Preset and `every:` intervals are measured from the schedule's creation time (for example `hourly` fires every hour after it is created), not aligned to wall-clock slots.\n\n> [!IMPORTANT]\n> **Recurring schedules have a one-minute minimum.** Every fire injects a message into the agent loop and typically costs an LLM turn, so `every:` values below `1m` are rejected — a typo such as `every:1s` in place of `every:1h` would otherwise become a runaway token burn. One-shot schedules (`in:` / `at:`) are not restricted, since they fire once.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    description: A monitoring assistant\n    instruction: |\n      Every 15 minutes, run `git fetch` and tell me if origin/main moved.\n    toolsets:\n      - type: scheduler\n      - type: shell\n```\n\nThe agent calls:\n\n```text\ncreate_schedule(prompt=\"Run git fetch and report if origin/main moved\", when=\"every:15m\")\n```\n\nEvery 15 minutes it is reminded, runs the command with the `shell` tool, and reports back.\n\n> [!TIP]\n> **When to use**\n>\n> Use the scheduler for recurring monitoring, timed one-shots, and unattended housekeeping loops during a long-running session. For work that should run immediately and be awaited, use [`background_jobs`](../background-jobs/index.md) instead.\n\n\n<!-- Skill/Rule: Script Skill (_vendor/github.com/docker/docker-agent/docs/tools/script/index.md) -->\n---\ntitle: \"Script Tool\"\ndescription: \"Define custom shell scripts as named tools with typed parameters.\"\nkeywords: docker agent, ai agents, tools, toolsets, script tool\nlinkTitle: \"Script\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/tools/script/\n---\n\n_Define custom shell scripts as named tools with typed parameters._\n\n## Overview\n\nThe script tool lets you define custom shell scripts as named tools. Unlike the generic [shell tool](../shell/index.md) where the agent writes the command, script tools execute predefined commands — ideal for exposing safe, well-scoped operations with descriptive names.\n\n## Configuration\n\n### Simple Scripts\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      run_tests:\n        cmd: task test\n        description: Run the project test suite\n      lint:\n        cmd: task lint\n        description: Run the linter\n```\n\n### Scripts with Parameters\n\nUse `${param}` interpolation and JSON Schema to define typed arguments:\n\n```yaml\ntoolsets:\n  - type: script\n    shell:\n      deploy:\n        cmd: ./scripts/deploy.sh ${env}\n        description: Deploy to an environment\n        args:\n          env:\n            type: string\n            enum: [staging, production]\n        required: [env]\n```\n\n## Properties\n\n| Property                          | Type   | Description                                                |\n| --------------------------------- | ------ | ---------------------------------------------------------- |\n| `shell.<name>.cmd`                | string | Shell command to execute (supports `${arg}` interpolation) |\n| `shell.<name>.description`        | string | Description shown to the model                             |\n| `shell.<name>.args`               | object | Parameter definitions (JSON Schema properties)             |\n| `shell.<name>.required`           | array  | Required parameter names                                   |\n| `shell.<name>.env`                | object | Environment variables for this script                      |\n| `shell.<name>.working_dir`        | string | Working directory for script execution                     |\n\n> [!TIP]\n> **Script vs. Shell**\n>\n> Use the [shell tool](../shell/index.md) when the agent needs to run arbitrary commands. Use the script tool when you want to expose specific, predefined operations with clear names and typed parameters — giving the agent less freedom but more safety.\n\n\n<!-- Skill/Rule: Session_context Skill (_vendor/github.com/docker/docker-agent/docs/tools/session_context/index.md) -->\n---\ntitle: \"Session Context Tool\"\ndescription: \"Reference a previous session as context in the current one.\"\nkeywords: docker agent, ai agents, tools, toolsets, session context tool\nlinkTitle: \"Session Context\"\nweight: 210\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_context/\n---\n\n_Reference a previous session as context, without manual export/import._\n\n## Overview\n\nThe `session_context` toolset lets an agent discover earlier sessions and pull one in as context for the current session. It removes the manual workaround of exporting a conversation to HTML and re-attaching it with an `@` mention.\n\nThe tool surface is two read-only tools:\n\n| Tool            | Description                                                                                                  |\n| --------------- | ------------------------------------------------------------------------------------------------------------ |\n| `list_sessions` | List previous sessions (most recent first) with id, title, creation time and message count.                  |\n| `read_session`  | Return the transcript of a previous session, by id or by a relative reference like `-1`.                      |\n\nThe session the agent is currently running in is never listed by `list_sessions` and cannot be read by `read_session` (a circular reference returns an error).\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_context\n```\n\nNo configuration options. Both tools are read-only and operate against the same session store the runtime already uses for persistence.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that may browse but never pull a full transcript into context.\ntoolsets:\n  - type: session_context\n    tools:\n      - list_sessions\n```\n\n## Selecting a session\n\n`read_session` accepts either form:\n\n- A concrete id returned by `list_sessions`, e.g. `read_session(\"a1b2c3...\")`.\n- A relative reference: `-1` is the most recent session, `-2` the second most recent, and so on. Relative references resolve against the same ordering `list_sessions` uses (most recent first), excluding sub-sessions.\n\n## Transcript size\n\nA long session could overflow the current context window, so `read_session` caps the rendered transcript. When a transcript is larger than the budget, the oldest messages are dropped (the most recent are usually the most useful for continuing work) and a note records how many were omitted:\n\n```text\n[12 earlier message(s) omitted to fit the context budget; showing the most recent 8]\n```\n\n## Notes\n\n- `list_sessions` defaults to 20 sessions and is capped at 100; pass `limit` to request fewer.\n- `read_session` returns an error when the session is not found, when the reference cannot be resolved, or when it points at the current session.\n- Both tools are read-only: they never modify, branch, or delete sessions.\n\n## Example\n\nSee [`examples/session_context.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_context.yaml) for a complete working example.\n\n\n<!-- Skill/Rule: Session_plan Skill (_vendor/github.com/docker/docker-agent/docs/tools/session_plan/index.md) -->\n---\ntitle: \"Session Plan Tool\"\ndescription: \"Per-session plan tracker for the draft, review, execute workflow.\"\nkeywords: docker agent, ai agents, tools, toolsets, session plan tool\nlinkTitle: \"Session Plan\"\nweight: 160\ncanonical: https://docs.docker.com/ai/docker-agent/tools/session_plan/\n---\n\n_Per-session plan tracker for the \"draft, review, execute\" workflow._\n\n## Overview\n\nThe `session_plan` toolset gives one agent a place to write a plan for the current session, signal that the plan is ready, and let the host route the next turn to an executing agent.\n\nDifferent from the [`plan` toolset](../plan/index.md) — `plan` is for shared, named plans multiple agents collaborate on over many sessions. `session_plan` is for one ephemeral plan per session, scoped to that session by ID.\n\nPlans live as Markdown files under:\n\n```text\n~/.cagent/session_plans/<session-id>.md\n```\n\nThe tool surface is three tools:\n\n| Tool                 | Description                                                                                          |\n| -------------------- | ---------------------------------------------------------------------------------------------------- |\n| `write_session_plan` | Create or replace this session's plan as markdown. There's exactly one plan per session.             |\n| `read_session_plan`  | Read the plan written for the current session and return it as markdown.                             |\n| `exit_plan_mode`     | Signal that the plan is ready for review. Does not switch agents on its own.                         |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: session_plan\n```\n\nNo configuration options. The plan path is derived from the session ID; the agent does not name plans.\n\nRestrict the toolset to a subset of tools the standard way:\n\n```yaml\n# An agent that consumes a plan but should not be able to (re)write or finalize one.\ntoolsets:\n  - type: session_plan\n    tools:\n      - read_session_plan\n```\n\n## When to call exit_plan_mode\n\nCall `exit_plan_mode` once the plan is complete and you do not intend to change it on the next turn. It validates that a plan exists for the session and returns a \"ready for review\" tool result. It does **not** switch agents or solicit user approval on its own — the host application owns the next-turn routing (for example, by reading the tool result, by a UI affordance the user toggles, or by a `handoff` declared on the agent).\n\nThis separation keeps the tool reusable across UIs: a CLI that prints tool results inline, a chat UI with a plan-mode toggle, and a server that auto-routes the next turn through a `handoff` can all consume the same signal without one stepping on another.\n\n## Storage and cleanup\n\n- Plans are markdown files written atomically (temp + rename), so concurrent readers — in this process or another — never observe a partial write.\n- A best-effort sweep on first use of the toolset removes plan files older than 30 days under the plans directory. Stranded plans for long-gone sessions do not accumulate.\n- The session ID identifies the file directly. There is no in-process mutex or revision counter, because two sessions cannot map to the same path.\n\n## Events\n\nA `session_plan_updated` event is emitted whenever `write_session_plan` succeeds:\n\n```json\n{\n  \"type\": \"session_plan_updated\",\n  \"session_id\": \"...\",\n  \"path\": \"/Users/.../.cagent/session_plans/<session-id>.md\",\n  \"content\": \"# my plan\\n...\",\n  \"agent_name\": \"planner\"\n}\n```\n\nEmbedders that render the plan inline can subscribe and update without re-reading the file.\n\n## Managing session plans from the host\n\nA session plan belongs to its session: hosts can read and export it, never change it.\n\n- **CLI** — the [`docker agent plans`](../../features/cli/index.md#docker-agent-plans) command group lists, reads (`get --session <session-id>`), and exports session plans alongside shared plans. Mutations (`update`, `status`, `delete`) are refused with an `unsupported` error explaining the ownership rule.\n- **TUI** — the `/plans` browser (see the [plan toolset docs](../plan/index.md#the-plans-browser-in-the-tui) for the full keybinding table) includes the **current session's** plan as the `session` scope row; plans of other sessions are never enumerated. Its identity is the session ID and its version column shows `-` — session plans have no versions. <kbd>Enter</kbd> opens the detail view (scope, session ID, update time, scrollable markdown) and <kbd>x</kbd> exports to `session-plan-<short-id>.md` in the working directory (refusing to overwrite an existing file). <kbd>e</kbd> opens the plan body in your external editor (`$VISUAL` or `$EDITOR`) for editing — the write is unguarded and last-write-wins by design. Status and delete visibly report that session plans don't support them (session plans belong to their session and carry no shared-plan metadata). The browser refreshes live on the `session_plan_updated` event, so a plan the agent just wrote appears without reopening.\n\n## Example\n\nA two-agent workflow: `root` executes, `planner` plans. `/plan` hands off to the planner; `exit_plan_mode` signals \"ready\", and the host decides what happens next.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Executes approved plans\n    instruction: |\n      You execute plans the planner has handed off. When you see a message\n      that a plan has been approved, read it with read_session_plan and work\n      through its steps in order.\n    toolsets:\n      - type: session_plan\n        tools:\n          - read_session_plan\n      - type: filesystem\n      - type: shell\n    commands:\n      plan:\n        description: \"Switch to the planner\"\n        agent: planner\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Investigates and writes plans for review\n    instruction: |\n      Investigate the user's request, then write the plan with\n      write_session_plan. Iterate with the user until the plan is complete,\n      then call exit_plan_mode to mark it ready for review.\n    toolsets:\n      - type: session_plan\n      - type: filesystem\n        readonly: true\n      - type: user_prompt\n```\n\nSee [`examples/session_plan.yaml`](https://github.com/docker/docker-agent/blob/main/examples/session_plan.yaml) for a complete working example.\n\n## Error Handling\n\n- `read_session_plan` and `exit_plan_mode` return a \"no plan written yet\" error when called before `write_session_plan`.\n- `write_session_plan` validates the session ID and refuses to write anything that could escape the plans directory; in practice the runtime generates UUIDs so this only triggers if an embedder supplies a hand-crafted ID.\n\n> [!TIP]\n> **session_plan vs. plan vs. todo vs. tasks**\n>\n> Use **session_plan** when one agent drafts an approach for the user to review before another agent executes it (ephemeral, one per session). Use [plan](../plan/index.md) for shared, named plans multiple agents collaborate on over many sessions. Use [todo](../todo/index.md) for lightweight in-session task lists. Use [tasks](../tasks/index.md) for a structured, persistent task database with priorities and dependencies.\n\n\n<!-- Skill/Rule: Shell Skill (_vendor/github.com/docker/docker-agent/docs/tools/shell/index.md) -->\n---\ntitle: \"Shell Tool\"\ndescription: \"Execute arbitrary shell commands in the user's environment.\"\nkeywords: docker agent, ai agents, tools, toolsets, shell tool\nlinkTitle: \"Shell\"\nweight: 20\ncanonical: https://docs.docker.com/ai/docker-agent/tools/shell/\n---\n\n_Execute arbitrary shell commands in the user's environment._\n\n## Overview\n\nThe shell tool allows agents to execute arbitrary shell commands synchronously. This is one of the most powerful tools — it lets agents run builds, install dependencies, query APIs, and interact with the system. Each call runs in a fresh, isolated shell session — no state persists between calls.\n\nCommands have a default 30-second timeout and require user confirmation unless `--yolo` is used. For servers, watchers, and other long-running commands, add the [`background_jobs`](../background-jobs/index.md) toolset alongside `shell`.\n\n### Shell interpreter detection\n\nThe shell tool automatically detects and names the resolved shell interpreter (e.g., `bash`, `zsh`, `powershell`, `pwsh`, `cmd`) in its description to the model, along with the operating system (Linux, macOS, Windows). This helps models use the correct shell syntax for the host environment.\n\nFor example:\n\n- On Linux with bash: \"Executes the given shell command with bash on Linux.\"\n- On Windows with PowerShell: \"Executes the given shell command with powershell on Windows. Use Windows PowerShell 5.1 syntax: chain commands with \";\" (not \"&&\"), and avoid POSIX commands/flags like \"ls -la\".\"\n\nThis reduces wasted turns where models assume POSIX syntax on Windows or vice versa.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: shell\n```\n\n### Options\n\n| Property       | Type    | Description                                                                                          |\n| -------------- | ------- | --------------------------------------------------------------------------------------------------- |\n| `env`          | object  | Environment variables to set for all shell commands                                                 |\n| `safer`        | boolean | Deprecated and ignored — shell commands are always classified now (see [Command classification](#command-classification)). Kept so existing YAMLs still parse. |\n| `sudo_askpass` | boolean | Opt in to prompting for a `sudo` password (see [Sudo support](#sudo-support)). Default `false`.     |\n\n### Custom Environment Variables\n\n```yaml\ntoolsets:\n  - type: shell\n    env:\n      MY_VAR: \"value\"\n      PATH: \"${env.PATH}:/custom/bin\"\n```\n\n### Command classification\n\nEvery shell command is classified against an embedded taxonomy before the approval decision — no opt-in required:\n\n- **Destructive matches** (`rm -rf <path>`, `docker volume rm`, `mkfs`, `dd if=… of=/dev/<disk>`, …) are labelled `destructive` with a `blast_radius` (`low` / `medium` / `high`) and a `category` tag. The TUI confirmation dialog renders the blast radius with a color badge.\n- **Known-safe reads** (`ls`, `cat`, `git status`, `git diff`, `docker ps`, `docker logs`, `kubectl get`, …) are labelled `safe`.\n- **Everything else** is labelled `unknown`.\n\nThe session's [safety mode](../../configuration/permissions/index.md#safety-modes) decides what each label means: `strict` asks about everything, `balanced` auto-runs safe commands and asks about destructive/unknown ones, `restricted` auto-runs safe commands and denies destructive/unknown ones without asking (fail-closed for unattended runs), `autonomous` runs everything. Custom permission rules always win over the mode.\n\nCompound shell (`a && b`, `a; b`, `a | b`) is never matched against the safe allowlist; any destructive segment falls through to ask. The full taxonomy lives in [`pkg/safety/safety_patterns.json`](https://github.com/docker/docker-agent/blob/main/pkg/safety/safety_patterns.json).\n\nSee [`examples/safety_modes.yaml`](https://github.com/docker/docker-agent/blob/main/examples/safety_modes.yaml) for a full example. The legacy `safer: true` toolset flag is deprecated and ignored.\n\n### Sudo support\n\nBy default a shell command has no controlling terminal, so a `sudo` command that needs a password hangs until it times out (the agent usually gives up and falls back to printing manual instructions).\n\nSet `sudo_askpass: true` to enable a sudo privilege escalation flow:\n\n```yaml\ntoolsets:\n  - type: shell\n    sudo_askpass: true\n```\n\nWhen enabled, `sudo` commands prompt you for your password through the host UI (the input is masked). The password is handed to `sudo` over a private, per-session socket via the standard `SUDO_ASKPASS` mechanism — it is never written to the command line, the logs, or stored by the agent.\n\nThe bridge environment variables (`SUDO_ASKPASS`, `CAGENT_ASKPASS_SOCKET`, `CAGENT_ASKPASS_TOKEN`) are added only to commands that invoke `sudo`, but within such a command they are visible to every child process, not just `sudo`. They carry a socket path and a session token, not the password; the socket lives in a `0700` directory, so only your own user can reach it.\n\nNotes and limitations:\n\n- Unix only. The flag has no effect on Windows.\n- Interactive UI only. In headless / non-interactive runs the prompt is declined automatically and `sudo` fails as before.\n- Only a bare `sudo ...` invocation in a POSIX shell (`sh`, `bash`, `zsh`, ...) is handled. `sudo` called by absolute path (`/usr/bin/sudo`), via `env sudo`, from inside a nested script, or under a non-POSIX shell (e.g. `fish`) is not intercepted and behaves as before.\n- Caching is `sudo`'s own. Because each shell tool call runs in a fresh shell with no controlling terminal, `sudo`'s credential cache does not persist across separate tool calls: you are prompted once per shell command that uses `sudo`. Within a single command, multiple `sudo` calls (e.g. `sudo a && sudo b`) usually share one prompt, subject to `sudo`'s own timestamp configuration.\n- The prompt must be answered within the command's timeout; raise the `timeout` parameter for `sudo` commands that may wait on input.\n- Prompts are serialized: if a single command runs two `sudo` calls in parallel (e.g. `sudo a & sudo b`), the second waits for the first prompt to be answered rather than opening two dialogs at once.\n\n## Available Tools\n\nThe shell toolset exposes one tool:\n\n| Tool Name | Description                                                                  |\n| --------- | ---------------------------------------------------------------------------- |\n| `shell`   | Run a command synchronously and return its combined output when it finishes. |\n\n### `shell` parameters\n\n| Parameter | Type    | Required | Description                                                               |\n| --------- | ------- | -------- | ------------------------------------------------------------------------- |\n| `cmd`     | string  | ✓        | The shell command to execute.                                             |\n| `cwd`     | string  | ✗        | Working directory to run the command in (default: `.`).                   |\n| `timeout` | integer | ✗        | Per-call execution timeout in seconds (default: `30`).                    |\n\n> [!WARNING]\n> **Safety**\n>\n> The shell tool gives agents full access to the system shell. Always set `max_iterations` on agents that use the shell tool to prevent infinite loops. A value of 20–50 is typical for development agents. Use [Sandbox Mode](../../configuration/sandbox/index.md) for additional isolation.\n\n> [!NOTE]\n> **Tool Confirmation**\n>\n> By default, Docker Agent asks for user confirmation before executing shell commands. Use `--yolo` to auto-approve all tool calls.\n\n\n<!-- Skill/Rule: Tasks Skill (_vendor/github.com/docker/docker-agent/docs/tools/tasks/index.md) -->\n---\ntitle: \"Tasks Tool\"\ndescription: \"Persistent task database with priorities and dependencies, shared across sessions.\"\nkeywords: docker agent, ai agents, tools, toolsets, tasks tool\nlinkTitle: \"Tasks\"\nweight: 180\ncanonical: https://docs.docker.com/ai/docker-agent/tools/tasks/\n---\n\n_Persistent task database with priorities and dependencies, shared across sessions._\n\n## Overview\n\nThe tasks tool provides a persistent task database that survives across agent sessions. Unlike the [Todo tool](../todo/index.md), which maintains an in-memory task list for the current session only, the tasks tool stores tasks in a JSON file on disk so they can be accessed and updated across multiple sessions. Tasks support priorities and dependencies — a task is _blocked_ until every task it depends on is `done`.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: tasks\n    path: ./tasks.json  # Optional: custom database path\n```\n\n### Options\n\n| Property | Type   | Default       | Description                                                                                                                  |\n| -------- | ------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `path`   | string | `tasks.json`  | Path to the JSON task database. Relative paths resolve against the agent config directory (or `--working-dir` when set).     |\n\n## Available Tools\n\nThe tasks toolset exposes these tools:\n\n| Tool Name           | Description                                                                                                              |\n| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `create_task`       | Create a new task with a title, description (or markdown file path), optional priority, and optional dependencies.       |\n| `get_task`          | Get full details of a single task by ID, including its effective status (`blocked` if any dependency is not `done`).     |\n| `update_task`       | Update a task's title, description, priority, status, or dependency list.                                                |\n| `delete_task`       | Delete a task by ID. Also removes it from other tasks' dependency lists.                                                 |\n| `list_tasks`        | List tasks sorted by priority (critical first) with blocked tasks last. Optionally filter by status or priority.         |\n| `next_task`         | Return the highest-priority actionable task — one that is not blocked and not done. Great for \"what should I work on?\". |\n| `add_dependency`    | Add a dependency: a task is blocked until the task it depends on is `done`.                                              |\n| `remove_dependency` | Remove a dependency from a task.                                                                                         |\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-4o\n    toolsets:\n      - type: tasks\n        path: ./project-tasks.json\n```\n\n> [!TIP]\n> **Tasks vs. Todo**\n>\n> Use the **tasks** tool when you need persistence across sessions, priorities, or dependencies (e.g., long-running projects, recurring work). Use the [todo tool](../todo/index.md) for ephemeral, session-scoped task lists.\n\n\n<!-- Skill/Rule: Think Skill (_vendor/github.com/docker/docker-agent/docs/tools/think/index.md) -->\n---\ntitle: \"Think Tool\"\ndescription: \"Step-by-step reasoning scratchpad for planning and decision-making.\"\nkeywords: docker agent, ai agents, tools, toolsets, think tool\nlinkTitle: \"Think\"\nweight: 140\ncanonical: https://docs.docker.com/ai/docker-agent/tools/think/\n---\n\n_Step-by-step reasoning scratchpad for planning and decision-making._\n\n## Overview\n\nThe think tool is a reasoning scratchpad that lets agents think step-by-step before acting. The agent can write its thoughts without producing visible output to the user — ideal for planning complex tasks, breaking down problems, and reasoning through multi-step solutions.\n\nThis is a lightweight tool with no side effects. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: think\n```\n\nNo configuration options.\n\n> [!TIP]\n> **When to use**\n>\n> Use the think tool with models that don't have native reasoning capabilities. If your model already supports a [thinking budget](../../configuration/models/index.md#thinking-budget), you likely don't need this tool.\n\n\n<!-- Skill/Rule: Todo Skill (_vendor/github.com/docker/docker-agent/docs/tools/todo/index.md) -->\n---\ntitle: \"Todo Tool\"\ndescription: \"Task list management for complex multi-step workflows.\"\nkeywords: docker agent, ai agents, tools, toolsets, todo tool\nlinkTitle: \"Todo\"\nweight: 170\ncanonical: https://docs.docker.com/ai/docker-agent/tools/todo/\n---\n\n_Task list management for complex multi-step workflows._\n\n## Overview\n\nThe todo tool provides task list management. Agents can create, update, list, and track progress on tasks with status tracking (pending, in-progress, completed). Useful for complex multi-step workflows where the agent needs to stay organized and ensure all steps are completed.\n\n## Available Tools\n\n| Tool           | Description                              |\n| -------------- | ---------------------------------------- |\n| `create_todo`  | Create a new task                        |\n| `create_todos` | Create multiple tasks at once            |\n| `update_todos` | Update status of one or more tasks       |\n| `list_todos`   | List all current tasks with their status |\n\n### Task Statuses\n\n| Status        | Description                  |\n| ------------- | ---------------------------- |\n| `pending`     | Task has not been started    |\n| `in-progress` | Task is currently being done |\n| `completed`   | Task is finished             |\n\n## Configuration\n\n```yaml\ntoolsets:\n  - type: todo\n```\n\n### Options\n\n| Property | Type    | Default | Description                                                             |\n| -------- | ------- | ------- | ----------------------------------------------------------------------- |\n| `shared` | boolean | `false` | When `true`, todos are shared across all agents in a multi-agent config |\n\n### Shared Todos\n\nIn multi-agent setups, enable shared todos so all agents can see and update the same task list:\n\n```yaml\ntoolsets:\n  - type: todo\n    shared: true\n```\n\n\n<!-- Skill/Rule: Transfer-task Skill (_vendor/github.com/docker/docker-agent/docs/tools/transfer-task/index.md) -->\n---\ntitle: \"Transfer Task Tool\"\ndescription: \"Delegate tasks to sub-agents in multi-agent setups.\"\nkeywords: docker agent, ai agents, tools, toolsets, transfer task tool\nlinkTitle: \"Transfer Task\"\nweight: 80\ncanonical: https://docs.docker.com/ai/docker-agent/tools/transfer-task/\n---\n\n_Delegate tasks to sub-agents in multi-agent setups._\n\n## Overview\n\nThe `transfer_task` tool allows an agent to delegate tasks to specialized sub-agents and receive their results. This is the core mechanism for multi-agent orchestration.\n\n**You don't need to add it manually** — it's automatically available when an agent has `sub_agents` configured.\n\n## Configuration\n\nThe tool is enabled implicitly when `sub_agents` is set:\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-4o\n    description: Coordinates work across specialists\n    instruction: Analyze requests and delegate to the right specialist.\n    sub_agents: [developer, researcher]\n\n  developer:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: Write clean, production-ready code.\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  researcher:\n    model: openai/gpt-4o\n    description: Web researcher\n    instruction: Search for information online.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n```\n\nThe coordinator agent automatically gets a `transfer_task` tool that can delegate to `developer` or `researcher`.\n\n## Tool Interface\n\nThe `transfer_task` tool takes three parameters:\n\n| Parameter         | Type   | Required | Description                                                                                 |\n| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |\n| `agent`           | string | ✓        | Name of the sub-agent to delegate to. Must be listed under the caller's `sub_agents`.        |\n| `task`            | string | ✓        | Clear, concise description of the task the sub-agent should achieve.                        |\n| `expected_output` | string | ✓        | Description of the result/format the caller expects back.                                   |\n\nThe call blocks until the sub-agent returns its result, which becomes the tool's response. For non-blocking parallel delegation, use [`background_agents`](../background-agents/index.md) instead.\n\n## Delegation Limits\n\nSub-agents can have `sub_agents` of their own, so multi-level delegation chains are supported. Two runtime guards keep chains sane, applied to both `transfer_task` and `run_background_agent`:\n\n- **Cycles are rejected.** A delegation targeting an agent that is already part of the active delegation chain (for example `a -> b -> a`) fails with an error naming the cycle.\n- **Depth is capped at 10 nested delegations.** The root agent delegating to its first sub-agent counts as depth 1; a call that would exceed the cap fails with an error stating the attempted depth.\n\nA rejected delegation returns a tool error to the calling agent and never starts the sub-agent.\n\n> [!TIP]\n> **See also**\n>\n> For parallel task delegation, see [Background Agents](../background-agents/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n\n\n<!-- Skill/Rule: User-prompt Skill (_vendor/github.com/docker/docker-agent/docs/tools/user-prompt/index.md) -->\n---\ntitle: \"User Prompt Tool\"\ndescription: \"Ask the user questions and collect interactive input during agent execution.\"\nkeywords: docker agent, ai agents, tools, toolsets, user prompt tool\nlinkTitle: \"User Prompt\"\nweight: 190\ncanonical: https://docs.docker.com/ai/docker-agent/tools/user-prompt/\n---\n\n_Ask the user questions and collect interactive input during agent execution._\n\n## Overview\n\nThe user prompt tool allows agents to ask questions and collect input from users during execution. This enables interactive workflows where the agent needs clarification, confirmation, or additional information before proceeding.\n\n> [!NOTE]\n> **When to Use**\n>\n> - When the agent needs clarification before proceeding\n> - Collecting credentials or configuration values\n> - Presenting choices and getting user decisions\n> - Confirming destructive or important actions\n\n## Configuration\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-4o\n    description: Interactive assistant\n    instruction: |\n      You are a helpful assistant. When you need information\n      from the user, use the user_prompt tool to ask them.\n    toolsets:\n      - type: user_prompt\n      - type: filesystem\n      - type: shell\n```\n\n## Tool Interface\n\nThe `user_prompt` tool takes these parameters:\n\n| Parameter | Type   | Required | Description                                                                                        |\n| --------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |\n| `message` | string | ✓        | The question or prompt to display.                                                                 |\n| `title`   | string | ✗        | Optional title for the dialog window in the TUI. Defaults to `\"Question\"` when not provided.       |\n| `schema`  | object | ✗        | JSON Schema defining the expected response structure (object or primitive).                        |\n\n## Response Format\n\nThe tool returns a JSON response:\n\n```json\n{\n  \"action\": \"accept\",\n  \"content\": {\n    \"field1\": \"user value\",\n    \"field2\": true\n  }\n}\n```\n\n### Action Values\n\n| Action    | Meaning                                    |\n| --------- | ------------------------------------------ |\n| `accept`  | User provided a response (check `content`) |\n| `decline` | User declined to answer                    |\n| `cancel`  | User cancelled the prompt                  |\n\n## Schema Examples\n\n### Simple String Input\n\n```json\n{\n  \"type\": \"string\",\n  \"title\": \"API Key\",\n  \"description\": \"Enter your API key\"\n}\n```\n\n### Multiple Choice\n\n```json\n{\n  \"type\": \"string\",\n  \"enum\": [\"development\", \"staging\", \"production\"],\n  \"title\": \"Environment\",\n  \"description\": \"Select the target environment\"\n}\n```\n\n### Boolean Confirmation\n\n```json\n{\n  \"type\": \"boolean\",\n  \"title\": \"Confirm\",\n  \"description\": \"Are you sure you want to proceed?\"\n}\n```\n\n### Object with Multiple Fields\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"username\": {\n      \"type\": \"string\",\n      \"description\": \"Your username\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"description\": \"Your password\"\n    },\n    \"remember\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remember credentials\"\n    }\n  },\n  \"required\": [\"username\", \"password\"]\n}\n```\n\n### Number Input\n\n```json\n{\n  \"type\": \"integer\",\n  \"title\": \"Port Number\",\n  \"description\": \"Enter the port number (1024-65535)\",\n  \"minimum\": 1024,\n  \"maximum\": 65535\n}\n```\n\n## Example Usage\n\nHere's how an agent might use the user prompt tool:\n\n```text\nAgent: I need to deploy this application. Let me ask which environment to target.\n\n[Calls user_prompt with message: \"Which environment should I deploy to?\"\n and schema with enum: [\"development\", \"staging\", \"production\"]]\n\nUser selects: \"staging\"\n\nAgent: Great, I'll deploy to staging. Let me confirm this action.\n\n[Calls user_prompt with message: \"Deploy to staging? This will replace the current version.\"\n and schema with type: \"boolean\"]\n\nUser confirms: true\n\nAgent: Deploying to staging...\n```\n\n## UI Presentation\n\nHow the prompt appears depends on the interface:\n\n- **TUI**: Displays an interactive dialog with appropriate input controls\n- **CLI (exec mode)**: Prints the prompt and reads from stdin\n- **API/MCP**: Returns an elicitation request to the client\n\n> [!TIP]\n> **Best Practice**\n>\n> Provide clear, concise messages. Include context about why you're asking and what the information will be used for. Use schemas with descriptions to guide users on expected input format.\n\n## Handling Responses\n\nThe agent should handle all possible actions:\n\n- **accept**: Process the `content` and continue\n- **decline**: Acknowledge and try an alternative approach or explain what's needed\n- **cancel**: Stop the current operation gracefully\n\n> [!WARNING]\n> **Context Requirement**\n>\n> The user prompt tool requires an elicitation handler to be configured. It works in the TUI and CLI modes but may not be available in all contexts (e.g., some MCP client configurations).\n\n\n<!-- Skill/Rule: Webhook Skill (_vendor/github.com/docker/docker-agent/docs/tools/webhook/index.md) -->\n---\ntitle: \"Webhook Tool\"\ndescription: \"Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more.\"\nkeywords: docker agent, ai agents, tools, toolsets, webhook, slack, discord, telegram, ifttt, notifications\nlinkTitle: \"Webhook\"\nweight: 145\ncanonical: https://docs.docker.com/ai/docker-agent/tools/webhook/\n---\n\n_Reliable outbound notifications to Slack, Discord, Telegram, IFTTT, and more._\n\n## Overview\n\nThe webhook toolset delivers a notification to a destination **you configure**. The\nagent supplies only the message text: it never sees or chooses the URL, because a\nwebhook URL is itself a credential (Slack and Mattermost embed a secret path,\nDiscord a token, IFTTT a key, Telegram a bot token).\n\nThis is not a general HTTP client — that is the [`api`](../api/index.md) toolset.\nThe webhook toolset owns *delivery*:\n\n- **At-least-once delivery.** Transient failures (`429`, `5xx`, network errors) are\n  retried with exponential backoff, honouring the server's `Retry-After`. A `4xx`\n  is permanent and fails immediately without wasting retries.\n- **Non-blocking.** The call returns as soon as the notification is queued, so a\n  slow or retrying endpoint never stalls the agent's turn. The agent is messaged\n  back **only if delivery ultimately fails**.\n- **Storm protection.** An identical message to the same destination inside a short\n  window is suppressed, and notifications are rate limited, so a looping agent\n  cannot flood a channel.\n- **Provider-shaped payloads.** Each service's wire format is applied for you.\n\n## Configuration\n\nThe destination lives in `webhook_config`. Use `${env.VAR}` for anything secret —\nvalues are expanded at call time and never stored in the config file.\n\n```yaml\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n| Field | Required | Description |\n| --- | --- | --- |\n| `url` | Yes | Webhook endpoint. Usually embeds a secret — prefer `${env.VAR}`. |\n| `provider` | No | Payload shape (default `generic`). |\n| `headers` | No | Extra headers, for endpoints authenticating with a token. |\n| `chat_id` | No | Destination chat — required for `provider: telegram`. |\n\n`timeout` on the toolset (seconds) overrides the per-request HTTP timeout.\n\n## Providers\n\n| Provider | Payload sent | Where the secret lives |\n| --- | --- | --- |\n| `slack`, `mattermost`, `rocketchat`, `googlechat`, `teams`, `generic` | `{\"text\": message}` | secret webhook URL |\n| `discord` | `{\"content\": message}` | token in the webhook URL |\n| `ifttt` | `{\"value1\": message, \"value2\": …, \"value3\": …}` | key in the webhook URL |\n| `telegram` | `{\"chat_id\": …, \"text\": message}` | bot token in the URL, plus `chat_id` |\n\nAliases are accepted: `msteams`/`microsoft_teams` → `teams`, `google_chat`/`gchat`\n→ `googlechat`, `rocket.chat` → `rocketchat`.\n\n### Per-service examples\n\n```yaml\n# Slack / Mattermost / Rocket.Chat — the URL is the credential\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: slack\n      url: ${env.SLACK_WEBHOOK_URL}\n```\n\n```yaml\n# Discord — the token is part of the webhook URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: discord\n      url: ${env.DISCORD_WEBHOOK_URL}\n```\n\n```yaml\n# Telegram — bot token in the URL, chat_id selects the destination chat\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: telegram\n      url: https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage\n      chat_id: \"123456789\"\n```\n\n```yaml\n# IFTTT — the key is part of the trigger URL\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: ifttt\n      url: https://maker.ifttt.com/trigger/build_failed/with/key/${env.IFTTT_KEY}\n```\n\n```yaml\n# Generic endpoint authenticating with a bearer token\ntoolsets:\n  - type: webhook\n    webhook_config:\n      provider: generic\n      url: https://alerts.example.com/notify\n      headers:\n        Authorization: Bearer ${env.ALERTS_TOKEN}\n```\n\n## `send_webhook`\n\n| Parameter | Required | Description |\n| --- | --- | --- |\n| `message` | Yes | The message text to deliver. |\n| `value2`, `value3` | No | Extra IFTTT data fields (`provider: ifttt`). |\n\nReturns immediately once queued. On success nothing further happens; if delivery\nultimately fails, the agent receives a message saying so.\n\n## Example\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5-mini\n    instruction: If a check fails, notify the team with send_webhook.\n    toolsets:\n      - type: webhook\n        webhook_config:\n          provider: slack\n          url: ${env.SLACK_WEBHOOK_URL}\n```\n\n> [!NOTE]\n> Requests to non-public addresses are refused (the SSRF-safe HTTP client), and the\n> configured URL is never echoed back to the model or into error messages.\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/_index.md) -->\n---\ntitle: Build checks\ndescription: |\n  BuildKit has built-in support for analyzing your build configuration based on\n  a set of pre-defined rules for enforcing Dockerfile and building best\n  practices.\nkeywords: buildkit, linting, dockerfile, frontend, rules\n---\n\nBuildKit has built-in support for analyzing your build configuration based on a\nset of pre-defined rules for enforcing Dockerfile and building best practices.\nAdhering to these rules helps avoid errors and ensures good readability of your\nDockerfile.\n\nChecks run as a build invocation, but instead of producing a build output, it\nperforms a series of checks to validate that your build doesn't violate any of\nthe rules. To run a check, use the `--check` flag:\n\n```console\n$ docker build --check .\n```\n\nTo learn more about how to use build checks, see\n[Checking your build configuration](https://docs.docker.com/build/checks/).\n\n<table>\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><a href=\"./stage-name-casing/\">StageNameCasing</a></td>\n      <td>Stage names should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-as-casing/\">FromAsCasing</a></td>\n      <td>The 'as' keyword should match the case of the 'from' keyword</td>\n    </tr>\n    <tr>\n      <td><a href=\"./no-empty-continuation/\">NoEmptyContinuation</a></td>\n      <td>Empty continuation lines will become errors in a future release</td>\n    </tr>\n    <tr>\n      <td><a href=\"./consistent-instruction-casing/\">ConsistentInstructionCasing</a></td>\n      <td>All commands within the Dockerfile should use the same casing (either upper or lower)</td>\n    </tr>\n    <tr>\n      <td><a href=\"./duplicate-stage-name/\">DuplicateStageName</a></td>\n      <td>Stage names should be unique</td>\n    </tr>\n    <tr>\n      <td><a href=\"./reserved-stage-name/\">ReservedStageName</a></td>\n      <td>Reserved words should not be used as stage names</td>\n    </tr>\n    <tr>\n      <td><a href=\"./json-args-recommended/\">JSONArgsRecommended</a></td>\n      <td>JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals</td>\n    </tr>\n    <tr>\n      <td><a href=\"./maintainer-deprecated/\">MaintainerDeprecated</a></td>\n      <td>The MAINTAINER instruction is deprecated, use a label instead to define an image author</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-arg-in-from/\">UndefinedArgInFrom</a></td>\n      <td>FROM command must use declared ARGs</td>\n    </tr>\n    <tr>\n      <td><a href=\"./workdir-relative-path/\">WorkdirRelativePath</a></td>\n      <td>Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes</td>\n    </tr>\n    <tr>\n      <td><a href=\"./undefined-var/\">UndefinedVar</a></td>\n      <td>Variables should be defined before their use</td>\n    </tr>\n    <tr>\n      <td><a href=\"./multiple-instructions-disallowed/\">MultipleInstructionsDisallowed</a></td>\n      <td>Multiple instructions of the same type should not be used in the same stage</td>\n    </tr>\n    <tr>\n      <td><a href=\"./legacy-key-value-format/\">LegacyKeyValueFormat</a></td>\n      <td>Legacy key/value format with whitespace separator should not be used</td>\n    </tr>\n    <tr>\n      <td><a href=\"./redundant-target-platform/\">RedundantTargetPlatform</a></td>\n      <td>Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior</td>\n    </tr>\n    <tr>\n      <td><a href=\"./secrets-used-in-arg-or-env/\">SecretsUsedInArgOrEnv</a></td>\n      <td>Sensitive data should not be used in the ARG or ENV commands</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-default-arg-in-from/\">InvalidDefaultArgInFrom</a></td>\n      <td>Default value for global ARG results in an empty or invalid base image name</td>\n    </tr>\n    <tr>\n      <td><a href=\"./from-platform-flag-const-disallowed/\">FromPlatformFlagConstDisallowed</a></td>\n      <td>FROM --platform flag should not use a constant value</td>\n    </tr>\n    <tr>\n      <td><a href=\"./copy-ignored-file/\">CopyIgnoredFile</a></td>\n      <td>Attempting to Copy file that is excluded by .dockerignore</td>\n    </tr>\n    <tr>\n      <td><a href=\"./invalid-definition-description/\">InvalidDefinitionDescription (experimental)</a></td>\n      <td>Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-proto-casing/\">ExposeProtoCasing</a></td>\n      <td>Protocol in EXPOSE instruction should be lowercase</td>\n    </tr>\n    <tr>\n      <td><a href=\"./expose-invalid-format/\">ExposeInvalidFormat</a></td>\n      <td>IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release</td>\n    </tr>\n  </tbody>\n</table>\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/consistent-instruction-casing.md) -->\n---\ntitle: ConsistentInstructionCasing\ndescription: >-\n  All commands within the Dockerfile should use the same casing (either upper or lower)\naliases:\n  - /go/dockerfile/rule/consistent-instruction-casing/\n---\n\n## Output\n\n```text\nCommand 'EntryPoint' should be consistently cased\n```\n\n## Description\n\nInstruction keywords should use consistent casing (all lowercase or all\nuppercase). Using a case that mixes uppercase and lowercase, such as\n`PascalCase` or `snakeCase`, letters result in poor readability.\n\n## Examples\n\n❌ Bad: don't mix uppercase and lowercase.\n\n```dockerfile\nFrom alpine\nRun echo hello > /greeting.txt\nEntRYpOiNT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all uppercase.\n\n```dockerfile\nFROM alpine\nRUN echo hello > /greeting.txt\nENTRYPOINT [\"cat\", \"/greeting.txt\"]\n```\n\n✅ Good: all lowercase.\n\n```dockerfile\nfrom alpine\nrun echo hello > /greeting.txt\nentrypoint [\"cat\", \"/greeting.txt\"]\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/copy-ignored-file.md) -->\n---\ntitle: CopyIgnoredFile\ndescription: >-\n  Attempting to Copy file that is excluded by .dockerignore\naliases:\n  - /go/dockerfile/rule/copy-ignored-file/\n---\n\n## Output\n\n```text\nAttempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n```\n\n## Description\n\nWhen you use the Add or Copy instructions from within a Dockerfile, you should\nensure that the files to be copied into the image do not match a pattern\npresent in `.dockerignore`.\n\nFiles which match the patterns in a `.dockerignore` file are not present in the\ncontext of the image when it is built. Trying to copy or add a file which is\nmissing from the context will result in a build error.\n\n## Examples\n\nWith the given `.dockerignore` file:\n\n```text\n*/tmp/*\n```\n\n❌ Bad: Attempting to Copy file \"./tmp/Dockerfile\" that is excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./tmp/helloworld.txt /helloworld.txt\n```\n\n✅ Good: Copying a file which is not excluded by .dockerignore\n\n```dockerfile\nFROM scratch\nCOPY ./forever/helloworld.txt /helloworld.txt\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/duplicate-stage-name.md) -->\n---\ntitle: DuplicateStageName\ndescription: >-\n  Stage names should be unique\naliases:\n  - /go/dockerfile/rule/duplicate-stage-name/\n---\n\n## Output\n\n```text\nDuplicate stage name 'foo-base', stage names should be unique\n```\n\n## Description\n\nDefining multiple stages with the same name results in an error because the\nbuilder is unable to uniquely resolve the stage name reference.\n\n## Examples\n\n❌ Bad: `builder` is declared as a stage name twice.\n\n```dockerfile\nFROM debian:latest AS builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS builder\n```\n\n✅ Good: stages have unique names.\n\n```dockerfile\nFROM debian:latest AS deb-builder\nRUN apt-get update; apt-get install -y curl\n\nFROM golang:latest AS go-builder\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-invalid-format.md) -->\n---\ntitle: ExposeInvalidFormat\ndescription: >-\n  IP address and host-port mapping should not be used in EXPOSE instruction. This will become an error in a future release\naliases:\n  - /go/dockerfile/rule/expose-invalid-format/\n---\n\n## Output\n\n```text\nEXPOSE instruction should not define an IP address or host-port mapping, found '127.0.0.1:80:80'\n```\n\n## Description\n\nThe [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose) instruction\nin a Dockerfile is used to indicate which ports the container listens on at\nruntime. It should not include an IP address or host-port mapping, as this is\nnot the intended use of the `EXPOSE` instruction. Instead, it should only\nspecify the port number and optionally the protocol (TCP or UDP).\n\n> [!IMPORTANT]\n> This will become an error in a future release.\n\n## Examples\n\n❌ Bad: IP address and host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 127.0.0.1:80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n❌ Bad: Host-port mapping used.\n\n```dockerfile\nFROM alpine\nEXPOSE 80:80\n```\n\n✅ Good: only the port number is specified.\n\n```dockerfile\nFROM alpine\nEXPOSE 80\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/expose-proto-casing.md) -->\n---\ntitle: ExposeProtoCasing\ndescription: >-\n  Protocol in EXPOSE instruction should be lowercase\naliases:\n  - /go/dockerfile/rule/expose-proto-casing/\n---\n\n## Output\n\n```text\nDefined protocol '80/TcP' in EXPOSE instruction should be lowercase\n```\n\n## Description\n\nProtocol names in the [`EXPOSE`](https://docs.docker.com/reference/dockerfile/#expose)\ninstruction should be specified in lowercase to maintain consistency and\nreadability. This rule checks for protocols that are not in lowercase and\nreports them.\n\n## Examples\n\n❌ Bad: protocol is not in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/TcP\n```\n\n✅ Good: protocol is in lowercase.\n\n```dockerfile\nFROM alpine\nEXPOSE 80/tcp\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-as-casing.md) -->\n---\ntitle: FromAsCasing\ndescription: >-\n  The 'as' keyword should match the case of the 'from' keyword\naliases:\n  - /go/dockerfile/rule/from-as-casing/\n---\n\n## Output\n\n```text\n'as' and 'FROM' keywords' casing do not match\n```\n\n## Description\n\nWhile Dockerfile keywords can be either uppercase or lowercase, mixing case\nstyles is not recommended for readability. This rule reports violations where\nmixed case style occurs for a `FROM` instruction with an `AS` keyword declaring\na stage name.\n\n## Examples\n\n❌ Bad: `FROM` is uppercase, `AS` is lowercase.\n\n```dockerfile\nFROM debian:latest as builder\n```\n\n✅ Good: `FROM` and `AS` are both uppercase\n\n```dockerfile\nFROM debian:latest AS deb-builder\n```\n\n✅ Good: `FROM` and `AS` are both lowercase.\n\n```dockerfile\nfrom debian:latest as deb-builder\n```\n\n## Related errors\n\n- [`FileConsistentCommandCasing`](./consistent-instruction-casing.md)\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/from-platform-flag-const-disallowed.md) -->\n---\ntitle: FromPlatformFlagConstDisallowed\ndescription: >-\n  FROM --platform flag should not use a constant value\naliases:\n  - /go/dockerfile/rule/from-platform-flag-const-disallowed/\n---\n\n## Output\n\n```text\nFROM --platform flag should not use constant value \"linux/amd64\"\n```\n\n## Description\n\nSpecifying `--platform` in the Dockerfile `FROM` instruction forces the image to build on only one target platform. This prevents building a multi-platform image from this Dockerfile and you must build on the same platform as specified in `--platform`.\n\nThe recommended approach is to:\n\n* Omit `FROM --platform` in the Dockerfile and use the `--platform` argument on the command line.\n* Use `$BUILDPLATFORM` or some other combination of variables for the `--platform` argument.\n* Stage name should include the platform, OS, or architecture name to indicate that it only contains platform-specific instructions.\n\n## Examples\n\n❌ Bad: using a constant argument for `--platform`\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using the default platform\n\n```dockerfile\nFROM alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: using a meta variable\n\n```dockerfile\nFROM --platform=${BUILDPLATFORM} alpine AS base\nRUN apk add --no-cache git\n```\n\n✅ Good: used in a multi-stage build with a target architecture\n\n```dockerfile\nFROM --platform=linux/amd64 alpine AS build_amd64\n...\n\nFROM --platform=linux/arm64 alpine AS build_arm64\n...\n\nFROM build_${TARGETARCH} AS build\n...\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-default-arg-in-from.md) -->\n---\ntitle: InvalidDefaultArgInFrom\ndescription: >-\n  Default value for global ARG results in an empty or invalid base image name\naliases:\n  - /go/dockerfile/rule/invalid-default-arg-in-from/\n---\n\n## Output\n\n```text\nUsing the global ARGs with default values should produce a valid build.\n```\n\n## Description\n\nAn `ARG` used in an image reference should be valid when no build arguments are used. An image build should not require `--build-arg` to be used to produce a valid build.\n\n## Examples\n\n❌ Bad: don't rely on an ARG being set for an image reference to be valid\n\n```dockerfile\nARG TAG\nFROM busybox:${TAG}\n```\n\n✅ Good: include a default for the ARG\n\n```dockerfile\nARG TAG=latest\nFROM busybox:${TAG}\n```\n\n✅ Good: ARG can be empty if the image would be valid with it empty\n\n```dockerfile\nARG VARIANT\nFROM busybox:stable${VARIANT}\n```\n\n✅ Good: Use a default value if the build arg is not present\n\n```dockerfile\nARG TAG\nFROM alpine:${TAG:-3.14}\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/invalid-definition-description.md) -->\n---\ntitle: InvalidDefinitionDescription\ndescription: >-\n  Comment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\naliases:\n  - /go/dockerfile/rule/invalid-definition-description/\n---\n\n> [!NOTE]\n> This check is experimental and is not enabled by default. To enable it, see\n> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).\n\n## Output\n\n```text\nComment for build stage or argument should follow the format: `# <arg/stage name> <description>`. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment.\n```\n\n## Description\n\nThe [`--call=outline`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nand [`--call=targets`](https://docs.docker.com/reference/cli/docker/buildx/build/#call-outline)\nflags for the `docker build` command print descriptions for build targets and arguments.\nThe descriptions are generated from [Dockerfile comments](https://docs.docker.com/reference/cli/docker/buildx/build/#descriptions)\nthat immediately precede the `FROM` or `ARG` instruction\nand that begin with the name of the build stage or argument.\nFor example:\n\n```dockerfile\n# build-cli builds the CLI binary\nFROM alpine AS build-cli\n# VERSION controls the version of the program\nARG VERSION=1\n```\n\nIn cases where preceding comments are not meant to be descriptions,\nadd an empty line or comment between the instruction and the preceding comment.\n\n## Examples\n\n❌ Bad: A non-descriptive comment on the line preceding the `FROM` command.\n\n```dockerfile\n# a non-descriptive comment\nFROM scratch AS base\n\n# another non-descriptive comment\nARG VERSION=1\n```\n\n✅ Good: An empty line separating non-descriptive comments.\n\n```dockerfile\n# a non-descriptive comment\n\nFROM scratch AS base\n\n# another non-descriptive comment\n\nARG VERSION=1\n```\n\n✅ Good: Comments describing `ARG` keys and stages immediately proceeding the command.\n\n```dockerfile\n# base is a stage for compiling source\nFROM scratch AS base\n# VERSION This is the version number.\nARG VERSION=1\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/json-args-recommended.md) -->\n---\ntitle: JSONArgsRecommended\ndescription: >-\n  JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\naliases:\n  - /go/dockerfile/rule/json-args-recommended/\n---\n\n## Output\n\n```text\nJSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals\n```\n\n## Description\n\n`ENTRYPOINT` and `CMD` instructions both support two different syntaxes for\narguments:\n\n- Shell form: `CMD my-cmd start`\n- Exec form: `CMD [\"my-cmd\", \"start\"]`\n\nWhen you use shell form, the executable runs as a child process to a shell,\nwhich doesn't pass signals. This means that the program running in the\ncontainer can't detect OS signals like `SIGTERM` and `SIGKILL` and respond to\nthem correctly.\n\n## Examples\n\n❌ Bad: the `ENTRYPOINT` command doesn't receive OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT my-program start\n# entrypoint becomes: /bin/sh -c my-program start\n```\n\nTo make sure the executable can receive OS signals, use the exec form for `CMD`\nand `ENTRYPOINT`, which lets you run the executable as the main process (`PID\n1`) in the container, avoiding a shell parent process.\n\n✅ Good: the `ENTRYPOINT` receives OS signals.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"my-program\", \"start\"]\n# entrypoint becomes: my-program start\n```\n\nNote that running programs as PID 1 means the program now has the special\nresponsibilities and behaviors associated with PID 1 in Linux, such as reaping\nchild processes.\n\n### Workarounds\n\nThere might still be cases when you want to run your containers under a shell.\nWhen using exec form, shell features such as variable expansion, piping (`|`)\nand command chaining (`&&`, `||`, `;`), are not available. To use such\nfeatures, you need to use shell form.\n\nHere are some ways you can achieve that. Note that this still means that\nexecutables run as child-processes of a shell.\n\n#### Create a wrapper script\n\nYou can create an entrypoint script that wraps your startup commands, and\nexecute that script with a JSON-formatted `ENTRYPOINT` command.\n\n✅ Good: the `ENTRYPOINT` uses JSON format.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nCOPY --chmod=755 <<EOT /entrypoint.sh\n#!/usr/bin/env bash\nset -e\nmy-background-process &\nmy-program start\nEOT\nENTRYPOINT [\"/entrypoint.sh\"]\n```\n\n#### Explicitly specify the shell\n\nYou can use the [`SHELL`](https://docs.docker.com/reference/dockerfile/#shell)\nDockerfile instruction to explicitly specify a shell to use. This will suppress\nthe warning since setting the `SHELL` instruction indicates that using shell\nform is a conscious decision.\n\n✅ Good: shell is explicitly defined.\n\n```dockerfile\nFROM alpine\nRUN apk add bash\nSHELL [\"/bin/bash\", \"-c\"]\nENTRYPOINT echo \"hello world\"\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/legacy-key-value-format.md) -->\n---\ntitle: LegacyKeyValueFormat\ndescription: >-\n  Legacy key/value format with whitespace separator should not be used\naliases:\n  - /go/dockerfile/rule/legacy-key-value-format/\n---\n\n## Output\n\n```text\n\"ENV key=value\" should be used instead of legacy \"ENV key value\" format\n```\n\n## Description\n\nThe correct format for declaring environment variables and build arguments in a\nDockerfile is `ENV key=value` and `ARG key=value`, where the variable name\n(`key`) and value (`value`) are separated by an equals sign (`=`).\nHistorically, Dockerfiles have also supported a space separator between the key\nand the value (for example, `ARG key value`). This legacy format is deprecated,\nand you should only use the format with the equals sign.\n\n## Examples\n\n❌ Bad: using a space separator for variable key and value.\n\n```dockerfile\nFROM alpine\nARG foo bar\n```\n\n✅ Good: use an equals sign to separate key and value.\n\n```dockerfile\nFROM alpine\nARG foo=bar\n```\n\n❌ Bad: multi-line variable declaration with a space separator.\n\n```dockerfile\nENV DEPS \\\n    curl \\\n    git \\\n    make\n```\n\n✅ Good: use an equals sign and wrap the value in quotes.\n\n```dockerfile\nENV DEPS=\"\\\n    curl \\\n    git \\\n    make\"\n```\n\n> [!NOTE]\n> Be aware of leading whitespace when converting multi-line legacy syntax to\n> the modern `key=value` format. In the legacy format, leading whitespace on\n> continuation lines is included in the value. In the modern format with\n> quoted values, leading whitespace inside the quotes is also preserved. If\n> you don't want leading whitespace in the value, make sure to remove it when\n> rewriting to the new format:\n>\n> ```dockerfile\n> ENV DEPS=\"\\\n> curl \\\n> git \\\n> make\"\n> ```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/maintainer-deprecated.md) -->\n---\ntitle: MaintainerDeprecated\ndescription: >-\n  The MAINTAINER instruction is deprecated, use a label instead to define an image author\naliases:\n  - /go/dockerfile/rule/maintainer-deprecated/\n---\n\n## Output\n\n```text\nMAINTAINER instruction is deprecated in favor of using label\n```\n\n## Description\n\nThe `MAINTAINER` instruction, used historically for specifying the author of\nthe Dockerfile, is deprecated. To set author metadata for an image, use the\n`org.opencontainers.image.authors` [OCI label](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys).\n\n## Examples\n\n❌ Bad: don't use the `MAINTAINER` instruction\n\n```dockerfile\nMAINTAINER moby@example.com\n```\n\n✅ Good: specify the author using the `org.opencontainers.image.authors` label\n\n```dockerfile\nLABEL org.opencontainers.image.authors=\"moby@example.com\"\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/multiple-instructions-disallowed.md) -->\n---\ntitle: MultipleInstructionsDisallowed\ndescription: >-\n  Multiple instructions of the same type should not be used in the same stage\naliases:\n  - /go/dockerfile/rule/multiple-instructions-disallowed/\n---\n\n## Output\n\n```text\nMultiple CMD instructions should not be used in the same stage because only the last one will be used\n```\n\n## Description\n\nIf you have multiple `CMD`, `HEALTHCHECK`, or `ENTRYPOINT` instructions in your\nDockerfile, only the last occurrence is used. An image can only ever have one\n`CMD`, `HEALTHCHECK`, and `ENTRYPOINT`.\n\n## Examples\n\n❌ Bad: Duplicate instructions.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\"]\nENTRYPOINT [\"echo\", \"Hello, Sweden!\"]\n# Only \"Hello, Sweden!\" will be printed\n```\n\n✅ Good: only one `ENTRYPOINT` instruction.\n\n```dockerfile\nFROM alpine\nENTRYPOINT [\"echo\", \"Hello, Norway!\\nHello, Sweden!\"]\n```\n\nYou can have both a regular, top-level `CMD`\nand a separate `CMD` for a `HEALTHCHECK` instruction.\n\n✅ Good: only one top-level `CMD` instruction.\n\n```dockerfile\nFROM python:alpine\nRUN apk add curl\nHEALTHCHECK --interval=1s --timeout=3s \\\n  CMD [\"curl\", \"-f\", \"http://localhost:8080\"]\nCMD [\"python\", \"-m\", \"http.server\", \"8080\"]\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/no-empty-continuation.md) -->\n---\ntitle: NoEmptyContinuation\ndescription: >-\n  Empty continuation lines will become errors in a future release\naliases:\n  - /go/dockerfile/rule/no-empty-continuation/\n---\n\n## Output\n\n```text\nEmpty continuation line found in: RUN apk add     gnupg     curl\n```\n\n## Description\n\nSupport for empty continuation (`/`) lines have been deprecated and will\ngenerate errors in future versions of the Dockerfile syntax.\n\nEmpty continuation lines are empty lines following a newline escape:\n\n```dockerfile\nFROM alpine\nRUN apk add \\\n\n    gnupg \\\n\n    curl\n```\n\nSupport for such empty lines is deprecated, and a future BuildKit release will\nremove support for this syntax entirely, causing builds to break. To avoid\nfuture errors, remove the empty lines, or add comments, since lines with\ncomments aren't considered empty.\n\n## Examples\n\n❌ Bad: empty continuation line between `EXPOSE` and 80.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n\n80\n```\n\n✅ Good: comments do not count as empty lines.\n\n```dockerfile\nFROM alpine\nEXPOSE \\\n# Port\n80\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/redundant-target-platform.md) -->\n---\ntitle: RedundantTargetPlatform\ndescription: >-\n  Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\naliases:\n  - /go/dockerfile/rule/redundant-target-platform/\n---\n\n## Output\n\n```text\nSetting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior\n```\n\n## Description\n\nA custom platform can be used for a base image. The default platform is the\nsame platform as the target output so setting the platform to `$TARGETPLATFORM`\nis redundant and unnecessary.\n\n## Examples\n\n❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.\n\n```dockerfile\nFROM --platform=$TARGETPLATFORM alpine AS builder\nRUN apk add --no-cache git\n```\n\n✅ Good: omit the `--platform` argument.\n\n```dockerfile\nFROM alpine AS builder\nRUN apk add --no-cache git\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/reserved-stage-name.md) -->\n---\ntitle: ReservedStageName\ndescription: >-\n  Reserved words should not be used as stage names\naliases:\n  - /go/dockerfile/rule/reserved-stage-name/\n---\n\n## Output\n\n```text\n'scratch' is reserved and should not be used as a stage name\n```\n\n## Description\n\nReserved words should not be used as names for stages in multi-stage builds.\nThe reserved words are:\n\n- `context`\n- `scratch`\n\n## Examples\n\n❌ Bad: `scratch` and `context` are reserved names.\n\n```dockerfile\nFROM alpine AS scratch\nFROM alpine AS context\n```\n\n✅ Good: the stage name `builder` is not reserved.\n\n```dockerfile\nFROM alpine AS builder\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/secrets-used-in-arg-or-env.md) -->\n---\ntitle: SecretsUsedInArgOrEnv\ndescription: >-\n  Sensitive data should not be used in the ARG or ENV commands\naliases:\n  - /go/dockerfile/rule/secrets-used-in-arg-or-env/\n---\n\n## Output\n\n```text\nPotentially sensitive data should not be used in the ARG or ENV commands\n```\n\n## Description\n\nWhile it is common to pass secrets to running processes\nthrough environment variables during local development,\nsetting secrets in a Dockerfile using `ENV` or `ARG`\nis insecure because they persist in the final image.\nThis rule reports violations where `ENV` and `ARG` keys\nindicate that they contain sensitive data.\n\nInstead of `ARG` or `ENV`, you should use secret mounts,\nwhich expose secrets to your builds in a secure manner,\nand do not persist in the final image or its metadata.\nSee [Build secrets](https://docs.docker.com/build/building/secrets/).\n\n## Examples\n\n❌ Bad: using ARG to pass AWS credentials.\n\n```dockerfile\nARG AWS_ACCESS_KEY_ID\nARG AWS_SECRET_ACCESS_KEY\nRUN aws s3 cp s3://my-bucket/file .\n```\n\n✅ Good: using secret mounts with environment variables.\n\n```dockerfile\nRUN --mount=type=secret,id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --mount=type=secret,id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY \\\n    aws s3 cp s3://my-bucket/file .\n```\n\nTo build with these secrets:\n\n```console\n$ docker buildx build \\\n    --secret id=aws_key_id,env=AWS_ACCESS_KEY_ID \\\n    --secret id=aws_secret_key,env=AWS_SECRET_ACCESS_KEY .\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/stage-name-casing.md) -->\n---\ntitle: StageNameCasing\ndescription: >-\n  Stage names should be lowercase\naliases:\n  - /go/dockerfile/rule/stage-name-casing/\n---\n\n## Output\n\n```text\nStage name 'BuilderBase' should be lowercase\n```\n\n## Description\n\nTo help distinguish Dockerfile instruction keywords from identifiers, this rule\nforces names of stages in a multi-stage Dockerfile to be all lowercase.\n\n## Examples\n\n❌ Bad: mixing uppercase and lowercase characters in the stage name.\n\n```dockerfile\nFROM alpine AS BuilderBase\n```\n\n✅ Good: stage name is all in lowercase.\n\n```dockerfile\nFROM alpine AS builder-base\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-arg-in-from.md) -->\n---\ntitle: UndefinedArgInFrom\ndescription: >-\n  FROM command must use declared ARGs\naliases:\n  - /go/dockerfile/rule/undefined-arg-in-from/\n---\n\n## Output\n\n```text\nFROM argument 'VARIANT' is not declared\n```\n\n## Description\n\nThis rule warns for cases where you're consuming an undefined build argument in\n`FROM` instructions.\n\nInterpolating build arguments in `FROM` instructions can be a good way to add\nflexibility to your build, and lets you pass arguments that overriding the base\nimage of a stage. For example, you might use a build argument to specify the\nimage tag:\n\n```dockerfile\nARG ALPINE_VERSION=3.20\n\nFROM alpine:${ALPINE_VERSION}\n```\n\nThis makes it possible to run the build with a different `alpine` version by\nspecifying a build argument:\n\n```console\n$ docker buildx build --build-arg ALPINE_VERSION=edge .\n```\n\nThis check also tries to detect and warn when a `FROM` instruction reference\nmiss-spelled built-in build arguments, like `BUILDPLATFORM`.\n\n## Examples\n\n❌ Bad: the `VARIANT` build argument is undefined.\n\n```dockerfile\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n✅ Good: the `VARIANT` build argument is defined.\n\n```dockerfile\nARG VARIANT=\"-alpine3.20\"\nFROM node:22${VARIANT} AS jsbuilder\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/undefined-var.md) -->\n---\ntitle: UndefinedVar\ndescription: >-\n  Variables should be defined before their use\naliases:\n  - /go/dockerfile/rule/undefined-var/\n---\n\n## Output\n\n```text\nUsage of undefined variable '$foo'\n```\n\n## Description\n\nThis check ensures that environment variables and build arguments are correctly\ndeclared before being used. While undeclared variables might not cause an\nimmediate build failure, they can lead to unexpected behavior or errors later\nin the build process.\n\nThis check does not evaluate undefined variables for `RUN`, `CMD`, and\n`ENTRYPOINT` instructions where you use the [shell form](https://docs.docker.com/reference/dockerfile/#shell-form).\nThat's because when you use shell form, variables are resolved by the command\nshell.\n\nIt also detects common mistakes like typos in variable names. For example, in\nthe following Dockerfile:\n\n```dockerfile\nFROM alpine\nENV PATH=$PAHT:/app/bin\n```\n\nThe check identifies that `$PAHT` is undefined and likely a typo for `$PATH`:\n\n```text\nUsage of undefined variable '$PAHT' (did you mean $PATH?)\n```\n\n## Examples\n\n❌ Bad: `$foo` is an undefined build argument.\n\n```dockerfile\nFROM alpine AS base\nCOPY $foo .\n```\n\n✅ Good: declaring `foo` as a build argument before attempting to access it.\n\n```dockerfile\nFROM alpine AS base\nARG foo\nCOPY $foo .\n```\n\n❌ Bad: `$foo` is undefined.\n\n```dockerfile\nFROM alpine AS base\nARG VERSION=$foo\n```\n\n✅ Good: the base image defines `$PYTHON_VERSION`\n\n```dockerfile\nFROM python AS base\nARG VERSION=$PYTHON_VERSION\n```\n\n\n\n<!-- Skill/Rule: Rules Skill (_vendor/github.com/moby/buildkit/frontend/dockerfile/docs/rules/workdir-relative-path.md) -->\n---\ntitle: WorkdirRelativePath\ndescription: >-\n  Relative workdir without an absolute workdir declared within the build can have unexpected results if the base image changes\naliases:\n  - /go/dockerfile/rule/workdir-relative-path/\n---\n\n## Output\n\n```text\nRelative workdir 'app/src' can have unexpected results if the base image changes\n```\n\n## Description\n\nWhen specifying `WORKDIR` in a build stage, you can use an absolute path, like\n`/build`, or a relative path, like `./build`. Using a relative path means that\nthe working directory is relative to whatever the previous working directory\nwas. So if your base image uses `/usr/local/foo` as a working directory, and\nyou specify a relative directory like `WORKDIR build`, the effective working\ndirectory becomes `/usr/local/foo/build`.\n\nThe `WorkdirRelativePath` build rule warns you if you use a `WORKDIR` with a\nrelative path without first specifying an absolute path in the same Dockerfile.\nThe rationale for this rule is that using a relative working directory for base\nimage built externally is prone to breaking, since working directory may change\nupstream without warning, resulting in a completely different directory\nhierarchy for your build.\n\n> [!NOTE]\n>\n> `WORKDIR` does not perform shell expansion. Paths beginning with `~` or\n> `~username` are treated as literal directory names and are not resolved to a\n> user's home directory.\n\n## Examples\n\n❌ Bad: this assumes that `WORKDIR` in the base image is `/`\n(if that changes upstream, the `web` stage is broken).\n\n```dockerfile\nFROM nginx AS web\nWORKDIR usr/share/nginx/html\nCOPY public .\n```\n\n✅ Good: a leading slash ensures that `WORKDIR` always ends up at the desired path.\n\n```dockerfile\nFROM nginx AS web\nWORKDIR /usr/share/nginx/html\nCOPY public .\n```\n\n\n\n<!-- Skill/Rule: References Skill (.agents/skills/agent-readiness-audit/references/report-template.md) -->\n# Agent Readiness Report Template\n\nUse this structure for final audit output.\n\n```markdown\n## Agent Readiness Audit\n\n**Site:** <base-url>\n**Date:** <YYYY-MM-DD>\n**Overall score:** <score>/100\n**Grade:** <A-F>\n**Confidence:** <High|Medium|Low>\n\n### Summary\n\n<2-4 sentence verdict focused on what an external agent can actually\ndiscover, fetch, and interpret on this site.>\n\n### Category Scores\n\n| Category | Score | Notes |\n| --- | ---: | --- |\n| Discovery and policy | <x>/<y> | <short note> |\n| Retrieval and markdown delivery | <x>/<y> | <short note> |\n| Structure and semantics | <x>/<y> | <short note> |\n| Crawlability and delivery behavior | <x>/<y> | <short note> |\n| Machine-readable surfaces | <x>/<y> | <short note or N/A> |\n| Content legibility | <x>/<y> | <short note> |\n\n### Sample\n\n- Sample strategy: <sitemap / internal links / explicit URLs>\n- Sampled pages: <count>\n- Page types covered: <landing, guide, manual, reference, ...>\n- Weakest page type: <if any>\n\n### Findings\n\n- `P0`: <highest-priority blocker with evidence>\n- `P1`: <important recurring issue with evidence>\n- `P2`: <lower-priority or optional improvement>\n\n### Remediation\n\n- `P0`: <fix>, because <why it matters to agents>\n- `P1`: <fix>, because <why it matters to agents>\n- `P2`: <fix>, because <why it matters to agents>\n\n### Evidence\n\n- Sitewide checks: <llms.txt, robots.txt, sitemap.xml, manifests>\n- Fetch-path checks: <markdown negotiation, direct markdown routes,\n  advertised alternates, parity>\n- Structural checks: <h1/main/article/canonical/json-ld/title-h1 parity>\n- Code block checks: <fence count, language-tag coverage>\n- Scanner comparison: <optional>\n```\n\n## Notes\n\n- Keep the summary short and outcome-oriented.\n- Findings should refer to concrete URLs or page types.\n- If a criterion is `N/A`, say why instead of leaving it blank.\n\n\n<!-- Skill/Rule: References Skill (.agents/skills/agent-readiness-audit/references/rubric.md) -->\n# Agent Readiness Rubric\n\nScore the site on a 100-point scale before normalization. If a criterion is\nnot applicable, remove its points from the denominator instead of treating\nit as failed.\n\n## Grade bands\n\n- `A`: 90-100\n- `B`: 80-89\n- `C`: 65-79\n- `D`: 50-64\n- `F`: below 50\n\n## Confidence levels\n\n- `High`: sitemap available and at least 12 sampled pages across at least\n  four page types\n- `Medium`: six to 11 sampled pages, or weaker coverage of page types\n- `Low`: fewer than six sampled pages, or homepage-biased sampling\n\n## Foundational caps\n\nApply these after computing the raw score:\n\n- No `sitemap.xml` and no `llms.txt`: maximum grade `C`\n- Markdown delivery fails on most sampled pages and no usable alternate\n  markdown path exists: maximum grade `D`\n- Main content is missing from initial HTML on more than 25% of sampled\n  pages: maximum grade `D`\n- `robots.txt` blocks broad crawl access to the docs site and the block is\n  not clearly intentional: maximum grade `F`\n\nOptional manifest gaps alone must not drop a docs-only host below `B`.\n\n## Categories\n\n### 1. Discovery and policy - 15 points\n\n- `5` `llms.txt` exists, is fetchable, and is useful for agent discovery\n- `4` `sitemap.xml` exists and includes the main docs corpus\n- `4` `robots.txt` is accessible and does not unintentionally block major\n  crawl agents or search agents\n- `2` curated bulk-discovery aid exists, such as `llms-full.txt` or an\n  equivalent machine-readable catalog\n\nWhen `llms.txt` exists, sample some URLs from it. Stale or misleading\ndiscovery links should reduce this category even if the file itself exists.\n\n### 2. Retrieval and markdown delivery - 25 points\n\n- `8` `Accept: text/markdown` works on sampled pages or an equivalent\n  negotiated markdown response exists\n- `5` a stable direct markdown route works on sampled pages\n- `5` page-level markdown hints, alternates, or UI actions point to a\n  working markdown URL\n- `4` markdown responses strip navigation chrome and preserve headings,\n  links, and code blocks cleanly\n- `3` HTML and markdown stay in parity across the sampled set\n\n### 3. Structure and semantics - 20 points\n\n- `6` sampled pages have one `h1` and a mostly consistent heading hierarchy\n- `5` `main` or `article` marks the primary content and the content is\n  present in the initial HTML\n- `4` canonical tags and stable final URLs are correct\n- `3` structured data such as breadcrumbs or article metadata exists where\n  appropriate\n- `2` headings expose stable anchors or deep-link targets, and the HTML title\n  or H1 stays reasonably aligned with the markdown H1\n\n### 4. Crawlability and delivery behavior - 15 points\n\n- `5` crawl directives are sane for a public docs property\n- `4` the site does not depend on client-side rendering to expose core\n  content\n- `3` cache and freshness signals are reasonable for bots, such as\n  `ETag`, `Last-Modified`, or useful cache headers\n- `3` redirect chains are short and predictable\n\n### 5. Machine-readable surfaces - 10 points\n\n- `4` API or reference sections expose OpenAPI, schema, or downloadable\n  machine-readable assets where relevant\n- `3` pages with interactive JavaScript reference UIs still provide a usable\n  non-JS fallback such as markdown, YAML, or another directly linked asset\n- `3` tool manifests such as MCP, plugin, or agent descriptors exist only\n  when the audited host is actually meant to expose tools\n\n### 6. Content legibility - 15 points\n\n- `5` markdown is clean and low-noise rather than a dump of site chrome\n- `4` headings and section intros are specific enough for retrieval and\n  chunking\n- `3` fenced code blocks are mostly language-tagged and remain copyable and\n  interpretable\n- `3` repeated banners, chat chrome, consent overlays, or other boilerplate\n  do not overwhelm the main content\n\n## Scoring guidance\n\nUse the full category only when the signal is consistently good across the\nsample. Partial credit is expected.\n\nExamples:\n\n- A sitewide `llms.txt` that exists but is stale or too shallow may earn\n  partial credit rather than full credit.\n- If markdown works only on some page types, score that criterion based on\n  observed coverage instead of failing or passing it outright.\n- If a working markdown route exists but the page advertises a dead\n  alternate URL, deduct in markdown discoverability rather than in raw\n  markdown availability.\n- If `llms.txt` exists but points to stale, broken, or inconsistent paths,\n  deduct in discovery rather than in core fetchability.\n- If tool manifests are irrelevant to the host, mark them `N/A`.\n- If a major page type is weaker than the rest of the site, note that\n  explicitly instead of letting stronger page types hide it in the average.\n\n## Reporting guidance\n\nFor every category, include one line that explains the score:\n\n- what was tested\n- what passed\n- what limited the score\n\nUse evidence from live fetches. Do not score from assumptions about the\nframework or source repository.\n\n\n<!-- Skill/Rule: agent-readiness-audit (.agents/skills/agent-readiness-audit/SKILL.md) -->\n---\nname: agent-readiness-audit\ndescription: >\n  Audit a documentation site for agent-friendliness: discovery, markdown\n  delivery, crawlability, semantic structure, machine-readable surfaces,\n  and content legibility. Use when asked to assess docs.docker.com or any\n  docs site for AI/agent readiness, produce a scored report, compare with\n  external scanners, or generate a remediation list. Triggers on:\n  \"audit docs for agent readiness\", \"how agent-friendly is docs.docker.com\",\n  \"score our docs for AI agents\", \"review llms.txt / markdown / crawlability\",\n  \"create an agent-readiness remediation plan\".\nargument-hint: \"<base-url>\"\n---\n\n# Agent Readiness Audit\n\nAudit the live site, not the source tree alone. Prefer the same fetch path\nan external agent would use in the wild: direct HTTP requests, sitemap\nsampling, and page-level inspection.\n\nDo not reduce the result to a homepage-only scan or a binary checklist.\n\n## 1. Set scope\n\nUse `$ARGUMENTS` as the base URL when provided. Otherwise infer the base\nURL from context and state the assumption.\n\nDecide whether the host being audited is:\n\n- a docs-only host\n- an app/tool host\n- a mixed host\n\nThis matters for optional checks such as MCP, plugin manifests, or other\ntool discovery files. Do not penalize a docs-only host for missing\ntooling manifests that belong on a separate service.\n\nFor `docs.docker.com`, treat the public docs host as docs-only. Docker's\nMCP server is published separately, so missing MCP files on the docs host\nshould be reported as `N/A`, not as a failure.\n\n## 2. Gather sitewide signals\n\nAlways check these resources first:\n\n- `/llms.txt`\n- `/llms-full.txt`\n- `/robots.txt`\n- `/sitemap.xml`\n\nOnly check host-level tool manifests when the host is an app/tool host,\nmixed host, or explicitly advertises them:\n\n- `/.well-known/ai-plugin.json`\n- `/.well-known/agent.json`\n- `/.well-known/agents.json`\n\nUse the bundled script for a baseline:\n\n```bash\nbash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\nThe script produces baseline evidence only. You still need to interpret\nwhat matters for a docs property and score it with the rubric.\n\nFor docs-only hosts, you may skip tool-manifest probes to reduce noise:\n\n```bash\nCHECK_TOOL_MANIFESTS=0 \\\n  bash .agents/skills/agent-readiness-audit/scripts/baseline-probes.sh \\\n  \"$ARGUMENTS\"\n```\n\n## 3. Sample representative pages\n\nUse the sitemap when available. Do not rely on the homepage alone.\n\nIf `llms.txt` exists, sample some URLs from it as well. This helps catch\nstale or misleading discovery surfaces that a sitemap-only sample would miss.\n\nSample at least 12 pages when the site is large enough, and cover multiple\npage types:\n\n- homepage or docs landing page\n- section landing pages\n- task guides\n- product manuals\n- reference or API pages\n- tutorial or learning pages\n\nIf the sitemap is missing or unusable, discover pages through internal\nlinks and note the lower confidence.\n\nIf the site has distinct delivery patterns, sample each one. For example:\n\n- normal content pages\n- generated reference pages\n- versioned docs\n- localized docs\n\n## 4. Run fetch-path checks on each sample\n\nFor each sampled page, verify:\n\n- HTML fetch status, content type, and final URL\n- `Accept: text/markdown` behavior\n- direct markdown route behavior such as `<page>.md` or another stable path\n- page-level markdown alternate links and whether they actually resolve\n- whether page actions such as \"Open Markdown\" agree with the working route\n- whether the HTML title or H1 matches the markdown H1 closely enough for\n  retrieval parity\n- whether main content is present in the initial HTML\n- redirect chain length and canonical URL consistency\n- obvious chrome/noise in the markdown response\n\nDo not assume a `.md` mirror exists just because another site uses one.\nVerify the actual markdown path the site exposes.\n\nTreat these as separate signals:\n\n- negotiated markdown works\n- a stable direct markdown URL works\n- the page advertises the correct markdown URL\n\nIf the page advertises dead markdown alternates but a working markdown route\nexists, do not fail markdown delivery outright. Score it as a discoverability\nand consistency problem instead.\n\nFor API or generated reference pages, also verify whether a machine-readable\nasset such as OpenAPI YAML is directly linked and fetchable.\n\n## 5. Judge structure and legibility\n\nMeasure structural signals:\n\n- exactly one `h1`\n- sane heading hierarchy\n- `main` and `article` presence where appropriate\n- canonical tags\n- JSON-LD or breadcrumb structured data\n- stable anchors and deep-linkable headings\n\nAlso make a qualitative judgment about agent legibility:\n\n- markdown strips site chrome cleanly\n- headings are specific and task-oriented\n- code blocks stay intelligible without client-side JS\n- the page is not dominated by banners, injected chat, or nav noise\n\nMeasure code block labeling explicitly when code samples are common. A page\ntype with many untagged fenced blocks should lose points even if the prose is\notherwise clean.\n\nFor page types that intentionally render interactive UIs with JavaScript,\njudge them separately from normal docs pages. If the HTML shell is thin,\ncheck whether the page still provides:\n\n- a fetchable markdown summary\n- a directly linked machine-readable asset\n- a usable non-JS fallback\n\n## 6. Score with the rubric\n\nUse [references/rubric.md](references/rubric.md).\n\nRules:\n\n- score only what you verified\n- mark non-applicable checks as `N/A`\n- normalize the final score against applicable points only\n- do not let optional manifest checks dominate the grade\n\nApply the foundational caps from the rubric. A site with broken discovery\nor broken markdown delivery should not earn a high grade because it has\nclean metadata.\n\nDo not average away a weak page type. If one major page type, such as API\nreference, is materially worse than the rest of the corpus, call it out as\nthe weakest segment and reflect it in the category notes.\n\n## 7. Compare with external scanners when useful\n\nIf external scanner results are available, compare them to your live\nfindings. Treat them as secondary evidence.\n\nIf a scanner and the live fetch disagree:\n\n- trust the live fetch\n- report the mismatch explicitly\n- explain whether the scanner is testing a different assumption\n\n## 8. Produce a remediation list\n\nTurn findings into a short backlog:\n\n- `P0`: fetchability or discovery blockers\n- `P1`: recurring structural or parity issues\n- `P2`: polish, optional manifests, or low-impact enhancements\n\nFor each remediation, include:\n\n- the failing signal\n- why it matters to agents\n- a concrete fix\n- whether it is sitewide or page-type-specific\n\n## 9. Report in a stable format\n\nUse [references/report-template.md](references/report-template.md).\n\nAlways include:\n\n- overall score and grade\n- confidence level\n- sampled URLs or sample strategy\n- category scores\n- highest-priority findings\n- remediation backlog\n\n## Notes\n\n- Favor docs-delivery checks over marketing-site heuristics.\n- Do not fail a docs host for lacking MCP or plugin manifests unless the\n  host itself is meant to expose tools.\n- Treat raw byte size as supporting evidence, not as a primary scoring input.\n- Prefer short evidence excerpts and commands over long copied page text.\n\n\n<!-- Skill/Rule: create-lab-guide (.agents/skills/create-lab-guide/SKILL.md) -->\n---\nname: create-lab-guide\ndescription: \"Clone a dockersamples Labspace repo, extract learning objectives and module structure from labspace.yaml, and produce a Hugo guide page under content/guides/ with correct frontmatter, labspace-launch shortcode, and Docker docs style compliance. Use when asked to create a lab guide, write a Labspace page, add a Docker lab tutorial, migrate a lab to docs, or document a hands-on lab.\"\n---\n\n# Create Lab Guide\n\nCreate a guide page for a Docker Labspace: clone the source repo, extract\nstructure from `labspace.yaml`, write the Hugo markdown page, and validate.\n\n## Inputs\n\n- **REPO_NAME**: GitHub repo in the `dockersamples` org (e.g. `labspace-ai-fundamentals`)\n\n## Step 1: Clone the labspace repo\n\n```bash\nTMPDIR=$(mktemp -d)\ngit clone --depth 1 https://github.com/dockersamples/{REPO_NAME}.git \"$TMPDIR/{REPO_NAME}\"\n```\n\n## Step 2: Extract key information\n\nRead these files from the cloned repo:\n\n| File | Purpose |\n|------|---------|\n| `README.md` | Lab purpose and overview |\n| `labspace/labspace.yaml` | Module structure and content paths |\n| `labspace/*.md` | Module content (only files listed in `labspace.yaml`) |\n| `.github/workflows/*.yml` | Published Compose file URL for the launch command |\n| `compose.override.yaml` | Check for top-level `model` specs (triggers `model-download` param) |\n\nExtract:\n1. A short description for the `description` and `summary` frontmatter fields.\n2. Learning objectives from the module content.\n3. Whether a model download is required (`compose.override.yaml` → top-level `model` key).\n\n## Step 3: Write the guide markdown\n\nPlace the file at `content/guides/lab-{GUIDE_ID}.md`.\n\n```markdown\n---\ntitle: \"Lab: { Short title }\"\nlinkTitle: \"Lab: { Short title }\"\ndescription: |\n  A short description of the lab for SEO and social sharing.\nsummary: |\n  A short summary of the lab for the guides listing page. 2-3 lines.\nkeywords: AI, Docker, Model Runner, agentic apps, lab, labspace\naliases: # Include only for AI-related labs\n  - /labs/docker-for-ai/{REPO_NAME_WITHOUT_LABSPACE_PREFIX}/\nparams:\n  tags: [ai, labs]\n  time: 20 minutes\n  resource_links:\n    - title: A resource link pointing to relevant documentation or code\n      url: /ai/model-runner/\n    - title: Labspace repository\n      url: https://github.com/dockersamples/{REPO_NAME}\n---\n\nShort explanation of the lab and what it covers.\n\n## Launch the lab\n\n{{< labspace-launch image=\"dockersamples/{REPO_NAME}\" >}}\n\n## What you'll learn\n\nBy the end of this Labspace, you will have completed the following:\n\n- Objective #1\n- Objective #2\n- Objective #3\n\n## Modules\n\n| # | Module | Description |\n|---|--------|-------------|\n| 1 | Module #1 | Description of module #1 |\n| 2 | Module #2 | Description of module #2 |\n| 3 | Module #3 | Description of module #3 |\n```\n\nConditional rules:\n- All lab guides **must** include `labs` in `params.tags`.\n- AI-related labs: also add `ai` tag and an alias under `/labs/docker-for-ai/`.\n- If a model download is required: add `model-download: true` to the `labspace-launch` shortcode.\n\n## Step 4: Apply Docker docs style rules\n\nFollow STYLE.md and COMPONENTS.md. Key rules:\n\n| Avoid | Use instead |\n|-------|-------------|\n| \"we\", \"let's\" | Imperative voice or \"you\" |\n| \"simply\", \"easily\", \"just\" | Remove the hedge word |\n| \"allows you to\" / \"enables you to\" | \"lets you\" or rephrase |\n| \"click\" | \"select\" |\n| Bold for emphasis / product names | Bold only for UI elements |\n| \"currently\", \"new\", \"recently\" | Remove time-relative language |\n\nUse `console` as the language hint for shell blocks with `$` prompts.\nUse contractions (\"it's\", \"you're\", \"don't\").\n\n## Step 5: Validate\n\n1. Confirm frontmatter has `title`, `description`, `keywords`, and `params.tags` including `labs`.\n2. Run `npx --no-install rumdl fmt <file>` to format.\n3. Run `docker buildx bake lint vale` and fix any errors.\n4. Re-read the file and verify: correct shortcode syntax, objectives match source content, modules match `labspace.yaml`, no vendored paths edited.\n\nDo not proceed to commit until validation passes.\n\n\n<!-- Skill/Rule: create-pr (.agents/skills/create-pr/SKILL.md) -->\n---\nname: create-pr\ndescription: >\n  Push the current branch and create a pull request against docker/docs.\n  Use after changes are committed and reviewed. \"create a PR\", \"submit the\n  fix\", \"open a pull request for this\".\n---\n\n# Create PR\n\nPush the branch and create a properly structured pull request.\n\n## 1. Verify the branch\n\nConfirm you're on a dedicated branch, not the default branch:\n\n```bash\ngit branch --show-current   # must not be main or master\n```\n\nIf this returns `main` or `master`, stop. Create a branch and move your\ncommits onto it before continuing.\n\nConfirm commits exist and the working tree is clean:\n\n```bash\ngit log --oneline main..HEAD   # confirm commits exist\ngit status --porcelain         # must print nothing\n```\n\nIf `git status --porcelain` prints anything, there are uncommitted or\nunstaged changes. Stop and commit them — or unstage stray files like\n`package-lock.json` — before opening a PR. Don't open a PR mid-edit.\n\n## 2. Push the branch\n\nIdentify the remote that points at your fork. Inspect the remotes:\n\n```bash\ngit remote -v\n```\n\nIf `origin` is your fork, use it. If `origin` points at canonical\n`docker/docs` (the upstream), push to your separate fork remote instead —\nnever push the branch to `docker/docs` directly:\n\n```bash\nFORK_REMOTE=origin   # or the name of your fork remote if origin is upstream\ngit push -u \"$FORK_REMOTE\" <branch-name>\n```\n\n## 3. Create the PR\n\nBefore creating a PR for an issue, check whether that issue already has an open\nlinked PR:\n\n```bash\ngh api repos/docker/docs/issues/<issue-number>/timeline --paginate \\\n  --jq '.[] | select((.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") and .source.issue.pull_request and .source.issue.state==\"open\") | {url: .source.issue.html_url, title: .source.issue.title}'\n```\n\nIf this returns an open PR that addresses the same issue, stop. Don't open a\nduplicate PR; report the existing PR instead. Only proceed if there is no open\nlinked PR, or if the existing PR clearly does not address the issue and you\nexplain why in the new PR body.\n\nDerive the fork owner dynamically from the same fork remote you pushed to:\n\n```bash\nFORK_OWNER=$(git remote get-url \"$FORK_REMOTE\" | sed -E 's|.*[:/]([^/]+)/[^/]+(\\.git)?$|\\1|')\n```\n\n```bash\ngh pr create --repo docker/docs \\\n  --head \"${FORK_OWNER}:<branch-name>\" \\\n  --title \"<concise summary under 70 chars>\" \\\n  --body \"$(cat <<'EOF'\n## Summary\n\n<1-2 sentences: what was wrong and what was changed>\n\nCloses #NNNN\n\nGenerated by <active coding agent name>\nEOF\n)\"\n```\n\nPrefix the title with the change type to match repo convention — `docs:` for\ndocumentation changes (or another scope like `hub:` when appropriate), for\nexample `docs: fix broken link on install page`.\n\nKeep the body short. Reviewers need to know what changed and why — nothing\nelse. Do **not** add a \"Test plan\" section — documentation PRs don't need one.\n\nUse an accurate disclosure footer that names the active coding agent, for\nexample `Generated by Codex` or `Generated by Claude Code`.\n\n### Optional: Netlify preview entry path\n\nIf the PR primarily edits a single page or a focused section of pages, add a\n`@netlify` stanza to the PR body (for example, just below the Summary). This\nsets the entry path for the Netlify deploy preview so reviewers land on the\nedited page instead of the site root:\n\n```markdown\n@netlify /desktop/setup/install/\n```\n\nThe stanza takes a single published URL path. Derive it from the source file\npath: drop the `content/` prefix and `.md` suffix, strip the `/manuals`\nsegment, and add a trailing slash. For example,\n`content/manuals/desktop/setup/install/mac-install.md` becomes\n`/desktop/setup/install/mac-install/`.\n\nOnly add this when the change is focused on one page or section. Skip it for\nPRs that touch many unrelated pages — there is no useful single entry path.\n\n### Optional: Preview links\n\nWhen the change is focused, also add direct links to the deploy preview in the\nPR body so reviewers can jump straight to the affected pages. The preview URL\nembeds the PR number:\n\n```\nhttps://deploy-preview-<pr-number>--docsdocker.netlify.app/path/to/page/\n```\n\nThe PR number isn't known until `gh pr create` returns, so add these links\nafter creating the PR by updating the body:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --body \"...\"\n```\n\nUse the same source-path-to-URL mapping as the `@netlify` stanza above.\n\n## 4. Apply labels and request review\n\nUse the Issues API for labels — `gh pr edit --add-label` silently fails:\n\n```bash\ngh api repos/docker/docs/issues/<pr-number>/labels \\\n  --method POST \\\n  --field 'labels[]=status/review'\n```\n\nRequest review:\n\n```bash\ngh pr edit <pr-number> --repo docker/docs --add-reviewer docker/docs-team\n```\n\nVerify the reviewer was assigned:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json reviewRequests \\\n  --jq '.reviewRequests[].slug'\n```\n\nIf the team doesn't appear, use the API directly:\n\n```bash\ngh api repos/docker/docs/pulls/<pr-number>/requested_reviewers \\\n  --method POST --field 'team_reviewers[]=docs-team'\n```\n\n## 5. Report\n\nPrint the PR URL and current CI state:\n\n```bash\ngh pr view <pr-number> --repo docker/docs --json url,state\ngh pr checks <pr-number> --repo docker/docs --json name,state\n```\n\n## Notes\n\n- Always use `Closes #NNNN` (not \"Fixes\") for GitHub auto-close linkage\n- One issue, one branch, one PR — never combine\n\n\n<!-- Skill/Rule: Agents Skill (.agents/skills/curate-whats-new/agents/openai.yaml) -->\ninterface:\n  display_name: \"Curate What's New\"\n  short_description: \"Curate noteworthy Docker launches from merged docs\"\n  default_prompt: \"Use $curate-whats-new to curate Docker launches published during the requested date range.\"\n\n\n<!-- Skill/Rule: curate-whats-new (.agents/skills/curate-whats-new/SKILL.md) -->\n---\nname: curate-whats-new\ndescription: Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights.\n---\n\n# Curate What's New\n\nTreat merged documentation as evidence that a capability shipped, but do not\ntreat a documentation change as news by itself.\n\nInclude an item only when the merged documentation directly shows all of the\nfollowing:\n\n- A released user-facing feature, material enhancement, or broader availability\n  milestone that was not available before the period\n- A substantial capability or workflow, not new syntax or a small control\n  within an existing workflow\n- Enough Docker-wide editorial significance to merit proactively telling users\n  about it outside product release notes\n- A useful published page and a factual title and description\n\nApply a high bar. The result is a curated launch archive, not a complete\nchangelog. A specialized feature can qualify when its user impact is\nsubstantial. A quiet period can produce few or no items.\n\n## Exclusions\n\nExclude documentation maintenance; fixes; rewrites; guidance for old behavior;\nroutine release or generated-content syncs; limitations, prerequisites, and\nworkarounds; narrow flags, settings, command variants, protocols, and\ncompatibility changes; incremental UI, safety, permissions, or observability\nimprovements; and lower-level Engine, Build, networking, or storage changes.\nThese qualify only when they are part of an independently newsworthy\nproduct-level launch.\n\nJudge the user outcome, not PR size, product popularity, labels, changed lines,\na dedicated page, or the existence of a new API or command.\n\n## Select highlights\n\nInclude every qualifying launch; do not impose a quota. Mark the five most\nimportant as `featured: true`, or all items when fewer than five qualify. Rank\nby the magnitude and distinctness of the user outcome and the value of helping\nits audience discover it. Breadth can matter, but a major capability for a\nspecialized audience can outrank a smaller change for a broad audience. Recency\nand product variety are not ranking goals.\n\nCreate one item per launch and combine PRs that document the same launch.\nPreserve existing copy while it remains accurate and qualifies. Change featured\nstatus only when the relative importance of the candidate set changes.\n\n## Procedure\n\n1. Read `data/whats-new.json`.\n2. Determine the review mode from the request:\n   - For an incremental review, list PRs merged from the day after the supplied\n     checkpoint through the end of the publication window. Retain existing\n     items inside the publication window without re-reviewing their source PRs.\n   - For a full review, inspect every PR merged in the supplied publication\n     window.\n3. List PRs in the range that applies to the review mode:\n\n   ```console\n   $ gh pr list --repo docker/docs --state merged --search 'merged:START..END' --limit 200\n   ```\n\n   If an incremental search returns no PRs, skip candidate inspection and only\n   remove expired items.\n4. Inspect the diff and resulting pages for every plausible new candidate.\n5. Decide what qualifies using only evidence in the merged documentation.\n6. Remove existing items published before the requested publication window.\n   Add newly qualifying launches, combine related PRs, and reconsider featured\n   status across the resulting list. Do not replace or rewrite retained items\n   merely because they were not part of the incremental candidate range.\n7. Replace `period_start`, `period_end`, and `items` in\n   `data/whats-new.json`. Sort items by `published` date, newest first.\n8. Write `.pr-body.md` with the publication period, selected highlights and\n   source PRs, plus concise reasons for plausible exclusions.\n\nEach item must contain `product`, `title`, `description`, `url`, `published`,\n`source_prs`, and `featured`. Use the canonical product name, a published\ninternal URL, the merge date in `YYYY-MM-DD` format, and source PR numbers.\n\nWrite factual, restrained copy. Avoid superlatives, promotional language, and\nclaims about ease or importance. Do not modify tracked files other than\n`data/whats-new.json`.\n\n\n<!-- Skill/Rule: fix-issue (.agents/skills/fix-issue/SKILL.md) -->\n---\nname: fix-issue\ndescription: >\n  Fix a single GitHub issue end-to-end: triage, research, write the fix,\n  review, and create a PR. Use when asked to fix an issue: \"fix issue 1234\",\n  \"resolve #500\", \"create a PR for issue 200\".\nargument-hint: \"<issue-number>\"\n---\n\n# Fix Issue\n\nGiven GitHub issue **$ARGUMENTS**, decide what to do with it and either\nclose it or fix it. This skill orchestrates the composable skills — it owns\nthe decision tree, not the individual steps.\n\n## 1. Triage\n\nInvoke `/triage-issue $ARGUMENTS` to understand the issue and decide what\nto do. This runs in a forked subagent and returns a verdict.\n\n## 2. Act on the triage result\n\nIf triage says **close it** — comment with the reason and close:\n```bash\ngh issue close $ARGUMENTS --repo docker/docs \\\n  --comment \"<one sentence explaining why>\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **escalate upstream** — comment noting the repo and stop:\n```bash\ngh issue comment $ARGUMENTS --repo docker/docs \\\n  --body \"This needs to be fixed in <upstream-repo>.\n\nGenerated by <active coding agent name>\"\n```\nDone.\n\nIf triage says **leave it open** — comment explaining what was checked and\nwhat's unclear. Do not close.\nDone.\n\nEnd every issue comment with an accurate agent-disclosure footer that names\nthe active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n\nIf triage says **fix it** — proceed to step 3.\n\n## 3. Research\n\nInvoke `/research` to locate affected files, verify facts, and identify\nthe fix. The issue context carries over from triage. This runs inline —\nfindings stay in conversation context for the write step.\n\nIf research reveals the issue is upstream or cannot be fixed (e.g.\nunverifiable URLs), comment on the issue and stop.\n\n## 4. Write\n\nInvoke `/write` to create a branch, make the change, format, self-review,\nand commit.\n\n## 5. Review\n\nInvoke `/review-changes` to check the diff for correctness, coherence, and\nmechanical compliance. This runs in a forked subagent with fresh context.\n\nIf issues are found, fix them and re-review until clean.\n\n## 6. Create PR\n\nInvoke `/create-pr` to push the branch and open a pull request.\n\n## 7. Return to main\n\n```bash\ngit checkout main\n```\n\n## 8. Report\n\nSummarize what happened: the issue number, what was done (closed, escalated,\nfixed with a PR link), and why — in a sentence or two.\n\n\n<!-- Skill/Rule: Agents Skill (.agents/skills/maintain-pr/agents/openai.yaml) -->\ninterface:\n  display_name: \"Maintain PR\"\n  short_description: \"Maintain an authored PR through review and CI\"\n  default_prompt: \"Use $maintain-pr to maintain this pull request through CI and review follow-up.\"\n\n\n<!-- Skill/Rule: maintain-pr (.agents/skills/maintain-pr/SKILL.md) -->\n---\nname: maintain-pr\ndescription: >\n  Maintain and follow up on a single Docker documentation pull request that\n  you own or are responsible for updating. Check CI and review feedback, fix\n  actionable failures, push changes, reply to comments, and report status.\n  Use for requests such as \"babysit this PR\", \"check the status of my PR\",\n  \"fix CI on my PR\", or \"address review comments on #500\". Do not use for\n  maintainer review of an incoming contribution; use review-pr for that.\n---\n\n# Maintain PR\n\nDo one maintenance pass over the specified author-owned PR: inspect its\nstate, fix actionable failures or feedback, reply to reviewers, and report\nthe result. This workflow may modify the branch and GitHub because the user\nis asking to maintain the PR. Do not apply it to an incoming PR merely\nbecause the user asks to review or assess it.\n\n## 1. Gather PR state\n\n```bash\ngh pr view <PR> --repo docker/docs --json state,title,url,headRefName,headRepositoryOwner,comments,reviews,reviewDecision\ngh pr checks <PR> --repo docker/docs --json name,state,detailsUrl\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, in_reply_to_id}]'\n```\n\nAlways check both top-level reviews and inline comments. A review with an\nempty body may still contain line-level feedback. Confirm that the PR is one\nthe user owns or is authorized to update before checking out or pushing its\nbranch. If not, stop and use `review-pr`.\n\n## 2. Handle terminal states\n\nIf merged, report the final state and identify unanswered review comments.\nReply only when the user remains responsible for follow-up.\n\nIf closed without merge, read the closing context and report the reason.\nCommon causes include maintainer rejection, supersession, or automation.\n\n## 3. Diagnose CI failures\n\n- Read the failure details.\n- Determine whether the failure comes from the PR or predates it.\n- Fix actionable failures in the PR's changed files.\n- Report pre-existing or upstream failures without changing unrelated files.\n\nFollow repository instructions for formatting, targeted linting, explicit\nstaging, commits, and pushes. Preserve unrelated working-tree changes.\n\n## 4. Address review feedback\n\nTreat every review comment as a claim to verify. Implement it only when the\nevidence supports it; explain any evidence-based disagreement.\n\nAfter each fix:\n\n1. Format and validate the changed files.\n2. Commit and push the focused change.\n3. Reply to every addressed thread with what changed or why no change was\n   made.\n4. End replies with an accurate agent-disclosure footer, such as\n   `Generated by Codex`.\n5. Resolve threads only after replying.\n6. Re-request review when appropriate.\n\nUse the inline comment endpoint to reply:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --method POST \\\n  --field in_reply_to=<COMMENT_ID> \\\n  --field body='<RESPONSE>'\n```\n\nUse GraphQL to retrieve unresolved review-thread IDs and resolve only the\nthreads that were addressed. Do not silently fix feedback without replying.\n\n## 5. Report\n\n```markdown\n## PR #<number>: <title>\n\n**State:** <open, merged, or closed>\n**CI:** <passing, failing, or pending>\n**Review:** <approved, changes requested, or pending>\n**Action taken:** <changes, replies, and thread resolution, or none needed>\n```\n\n\n<!-- Skill/Rule: migrate-content-ia (.agents/skills/migrate-content-ia/SKILL.md) -->\n---\nname: migrate-content-ia\ndescription: >\n  Handle Hugo docs information-architecture moves: discover old vs new URLs,\n  add front matter aliases (Phase 1), update in-repo links (Phase 2), interactive\n  List 2 resolution and fragment validation (Phase 3; no guessing). Supports\n  PR-scoped mapping plus whole-content sweeps for inbound links to that mapping,\n  or a full-site follow-up. Triggers on: \"IA migration\", \"redirects for moved\n  pages\", \"fix links after content move\", \"PR-scoped link/anchor pass\",\n  \"aliases for old URLs\". After branch work, chain the review-changes skill\n  (main...HEAD) before a PR. Agents must run the in-file required procedure\n  and definition of done, not the phases alone in isolation.\n---\n\n# Migrate content IA (redirects + links + anchors)\n\nUse this skill when pages **move or rename** under `content/` and you must\npreserve old public URLs and/or fix cross-references. Work in **phases**;\nchoose **PR-scoped** vs **full-site** mode per run.\n\n**Read first:** **CLAUDE.md** / **AGENTS.md** (URL rules, vendored areas, external\nlinks, special cases) and **hugo.yaml** (`permalinks`, `refLinksErrorLevel`,\n`disablePathToLower`). For **prose and link text**, follow **STYLE.md**; for\n**components, front matter, and link examples**, follow **COMPONENTS.md**.\n\n**Related skills:** **research** helps map moves and find inbound links; **write**\ncommits minimal edits. Run this skill’s phases after the move is identified (or\nin parallel with research for large IA work).\n\n## Agent: required procedure (do not skip)\n\n**Common mistake (wrong):** use **`git diff main...HEAD` (or the PR’s file\nlist) as the full set of places to fix links** for a migration. That set shows\n**what *moved***; it is **not** the list of every page that **points *to*** a\nmoved page. Inbound stragglers are often in files the PR **never** touched. You\nmust still **sweep the repo** for every string in the **old path and published-URL set**\nfor this run, not only for “files in the diff.”\n\n**Definition of done (when the migration is *finished*):** **Both** of the\nfollowing (unless the user or **AGENTS.md** **explicitly defers** a **List 2**\nitem in **Phase 3**; document the deferral):\n\n1. **`docker buildx bake validate`** passes for the branch, with no new\n   build/link errors from this work.\n2. A **sweep of the old path and published-URL set for this run** (see\n   [Sweep commands](#sweep-commands) below) finds **no** remaining\n   migration-relevant **inbound** reference—**including**:\n   - links to an old **source** path (plain `.md` and equivalent `ref` forms),\n   - links that use the old path **and** a `#fragment`,\n   - and, where your mapping includes them, old **published-style** `link:` /\n   `url:` / full-site URL strings,  \n   **except** intentional entries to keep: for example `aliases` on the **new**\n   canonical page, or **redirects.yml** *sources* you must not edit per policy.\n   (A hit on a **source** that is only an `alias` line on the new page is\n   **expected**—do not “fix” that away; distinguish alias rows from straggler\n   links in body or nav config.)\n\n**Chaining (policy):** when this branch’s content work is ready for handoff,\n**run the [review-changes](../review-changes/SKILL.md) skill** on\n**`main...HEAD`** (or **`merge-base`…`HEAD`** for a different target branch) so\nthe **whole branch** is re-read for cross-page issues before opening a PR. Do\nnot treat phases 0–3 alone as the final check.\n\n**Run in order (mandatory for agents):**\n\n1. **Scope the moves (mapping input):** set the Git range like **review-changes**\n   (for a PR to `main`: `git diff --name-only main...HEAD`; for another target:\n   `BASE=$(git merge-base <target-branch> HEAD)` then\n   `git diff --name-only $BASE...HEAD`, as in **Phase 0.5**). Include\n   renames; build the **old → new** table (source and published) per **Phase\n   0**.\n2. **Sweep and list:** for every **old** path/URL in that table, run\n   [Sweep commands](#sweep-commands) on the **allowed** trees. Record\n   every hit as **List 1** (no `#`) or **List 2** (old path with `#...`) per\n   **Phase 0.5**.\n3. **Phased edits:** **Phase 1** (`aliases`), then **Phase 2** (List 1), then\n   **Phase 3** (List 2) with **no guessing**—as in the sections below.\n4. **Re-sweep** the same old-path set, then run **`docker buildx bake\n   validate`**. The **Definition of done** above is met or you have **explicit\n   defers** for the remainder.\n5. **review-changes:** run **[review-changes](../review-changes/SKILL.md)**\n   on the branch vs **`main`…`HEAD`** (or the correct base) before a PR.\n\n### Sweep commands\n\nUse a **repository** search (e.g. `rg` / your IDE) so **nothing** in the\nallowed scope is only eyeballed.\n\n**Trees to include** (at minimum): all of `content/`, plus **`data/`** and\n**`layouts/`** when a migration can appear in config, `link:`-like fields,\nshortcodes, or hardcoded path strings. Follow **Vendored / generated** rules in\n**AGENTS.md**; do not edit disallowed files.\n\n**What to search for (repeat per row in the old side of the mapping):**\n\n- **Hugo / source form:** path segments that identify the *old* file, e.g.\n  `manuals/.../old-segment/...` or `../old-segment/.../page.md` as your tree\n  uses; include variants that still appear in the repo.\n- **Published / site form:** e.g. `/admin/.../old-slug/` in front matter, nav\n  `url:`, or `https://docs.docker.com/...` in allowed files—**match the\n  file’s** established pattern, per **Conventions** below.\n- **Anchors:** search for the **old path string**; matches that also include\n  `#...` belong on **List 2** for **Phase 3** unless the whole link is\n  a pure path-only case.\n\n[scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) (if present) prints\n**`PR_SCOPE_FILES` only**—it does **not** replace this sweep. Use it to build\nthe **old → new** table, **not** to list where inbound links were fixed.\n\n## Progressive disclosure (optional)\n\nThe procedure below stays in this file. If a run produces a very large\n**old → new** URL table, store that table in **`reference.md`** in this skill\ndirectory and link it from the task summary, so the agent reads the long\nmapping only when needed.\n\n## Modes\n\n- **PR-scoped (typical for a single PR)**  \n  - **What the PR “owns” (focus):** use `git diff` / `base...HEAD` to know which\n    pages and renames the branch actually moves (`PR_SCOPE_FILES`). The **old →\n    new** mapping and **List 1 / List 2** for this migration are defined from\n    **that** work, not from unrelated areas.\n  - **Where to look for stale references (sweep):** search broadly—typically all\n    of `content/` (and config, shortcodes, layouts, per Conventions)—for **inbound**\n    links and fields whose **target** is an **old** path or URL in **this** PR’s\n    mapping. Inbound stragglers are often in files the PR never touched; finding\n    them is **in scope** for this migration.  \n  - **What to edit:** update **any** file in the allowed trees that contains a\n    **migration-relevant** reference (target ∈ this PR’s old path set) according\n    to the phases below. **Do not** treat `PR_SCOPE_FILES` as a hard limit on\n    *which files you may save* for **inbound** link repairs (unless\n    project policy for a given PR says otherwise; then follow policy and\n    **defer** out-of-PR file fixes).\n  - **Out of scope (defer / ignore in this run):** link and anchor problems that\n    are **not** about this PR’s old→new map—e.g. a different area’s own slug\n    issues, rot unrelated to the remapped path set. *Example:* a PR that only\n    remaps `content/strawberry/...` should not “fix the whole site”; it **should**\n    still fix a link under `mango/…` that **points at** an old `strawberry/…` path\n    in the mapping, and **should not** chase **mango/**-only issues that do\n    not involve those old targets.\n\n- **Full-site (complete migration after the PR)**  \n  - Update stragglers **across the repo** (or all inbound links to moved\n    sections), including config-driven `link:` fields if policy allows.  \n  - Still make **minimal** edits; no drive-by rewrites to **unrelated** targets\n    outside the run’s **declared** mapping and lists.\n\n### No guessing\n\n- The agent must **not** guess **replacement paths, published URLs, or fragment\n  IDs** (including for consolidated pages, renamed headings, or\n  “semantic” remaps of `#anchor` → new `#…`). If the user has not given an\n  explicit new target, **ask**, **defer**, or **stop** per **AGENTS.md**; never\n  infer, autocomplete, or substitute a plausible fragment from the target page’s\n  heading list. That rule applies in **every** phase, including after validation\n  in Phase 3.\n\n---\n\n## Conventions (links, anchors, redirects)\n\n### Front matter `aliases` (redirects)\n\n- Per **COMPONENTS.md**, `aliases` are **URLs that redirect to this page**.\n- Add or **merge** on the **new canonical** page; do not drop unrelated\n  entries. Match local examples: **published-style paths** (leading `/`), and\n  **trailing `/`** when that matches existing pages in the same area.\n- **No** speculative redirects for URLs that were never published.\n- **Collision check** before adding: no other page or redirect may already\n  own the same old path.\n- If the site also uses **`data/redirects.yml`**, only add entries when\n  project policy requires it; avoid duplicating the same old URL in\n  `aliases` **and** `redirects.yml` unless maintainers do.\n\n### Internal links in Markdown (STYLE.md + COMPONENTS.md)\n\n- Use **relative paths to source files** (e.g. `../section/page.md`) with\n  **`.md`**, following **COMPONENTS.md** examples, unless the file already\n  uses an established pattern (e.g. some `link:` or nav fields use **published**\n  paths without `manuals` or `.md` — **match the surrounding file**).\n- Keep **CLAUDE.md** / **AGENTS.md** rules: internal ref targets under\n  `content/manuals/...` often use the full **`/manuals/...`** path; published\n  URLs omit the `manuals` segment—do not confuse the two when fixing links.\n- **Link text (STYLE.md):** descriptive, ~**5 words**; no “click here” or\n  “learn more”; **no** end punctuation **inside** the link text; **no** bold/italic\n  on link text unless normal in the sentence.\n- **Headings (STYLE):** **sentence case**; do not rename headings in passing\n  unless the migration requires it (heading changes break fragments).\n\n### Shortcodes and layouts (links not only in Markdown)\n\n- **Phase 2–3 scope includes** any **shortcode or layout partial** (under\n  **Modes**, search broadly for inbound links to the migration; **edits** follow\n  the same file-level rules as for Markdown) that emits links: e.g. `ref` /\n  `relref`, `link` fields in shortcode args, or hardcoded\n  `docs.docker.com` / path strings. Grep for old paths, slugs, and fragments\n  under `layouts/shortcodes/` (and `layouts/_default/` if partials build nav).\n- Match each file’s existing pattern; do not rewrite working shortcode style\n  just to “clean up.”\n\n### Fragments / anchors (Phase 3)\n\n- List 1 / List 2: fragment-bearing **cross-references to old paths** are tracked\n  on **List 2** in Phase 0.5; do not bulk-rewrite them in the **List 1** pass\n  (Phase 2). See Phase 0.5 and Phase 2.\n- **Valid `#fragment` values:** after the user supplies a new fragment, it should\n  match the **target** page’s **generated** heading ID (Hugo slugification; see\n  **CLAUDE.md** / **AGENTS.md**). The agent still **validates** (see Phase 3) and\n  must **not** “pick” a different id from the page to replace a bad answer—**No\n  guessing**.\n- Same-page: `[Text](#section-id)`.\n- Cross-page: when user-provided, `#fragment` must still be checked against the\n  **target** file. Validate fragments in shortcodes the same way as in body\n  Markdown.\n\n### External URLs (**AGENTS.md**)\n\n- Do not commit **guessed** replacement URLs. If a URL cannot be verified,\n  treat as blocked or drop the fragment per AGENTS guidance. See also **No\n  guessing** above; internal and external link targets are treated the same for\n  inference: **none** without user input or a verified source.\n\n### Special cases (**AGENTS.md**)\n\n- **Engine API version** pages: respect coordinated **`/latest/` `aliases`**\n  rules—never leave two version files both owning `/latest/`.\n- **Vendored / generated** trees: read-only; see CLAUDE.md. Do not “fix” links\n  there if policy forbids.\n\n---\n\n## Phase 0 — Discovery (read-only; may use whole repo)\n\n1. Read **hugo.yaml** (permalinks, `refLinksErrorLevel`, `disablePathToLower`).\n2. From the branch (diff, renames), build a **mapping table**:\n   - old source path → new source path  \n   - old published URL → new published URL (from permalink rules)\n3. **Case:** with `disablePathToLower: true`, filesystem path **case** appears in\n   URLs—**directory and link casing must match** (e.g. `setup` vs `Setup`).\n4. When planning **inbound link** fixes, treat old-path references as two\n   categories: **no fragment** vs **with `#fragment`**. That split feeds\n   **List 1** and **List 2** in Phase 0.5 and drives Phase 2 ordering (see\n   there).\n\n---\n\n## Phase 0.5 — PR-scoped evaluation (required before edits in PR mode)\n\n1. **Set `PR_SCOPE_FILES` (Git scope for PR mode)**  \n   - When the PR **targets `main`**, use the same triple-dot form as\n     **review-changes**:  \n     `git diff --name-only main...HEAD`  \n   - For a **different target branch** or a custom base, use the merge base:  \n     `BASE=$(git merge-base <target-branch> HEAD)`  \n     then:  \n     `git diff --name-only \"$BASE\"...HEAD`  \n   - Those paths define **what moved** in the branch; they are the primary input\n     to the **old → new** path/URL table. They are **not** a hard cap on *where\n     to search* for **inbound** links (see **Modes**): sweeps for links **to** old\n     paths usually cover all of `content/` (and other trees per Conventions).  \n   - If project policy **limits edits** to the diff for a given PR, follow that\n     and **defer** link fixes in files outside the diff; note the exception in\n     the task if the user relaxes that policy.\n\n2. Build checklists (see **Modes** for sweep vs area-of-work):\n   - path/URL mapping this run must honor (old source path → new; old published\n     → new, from the **PR’s** moves in PR-scoped mode, or the **declared** full\n     migration in full-site mode)\n   - **List 1 — old path, no fragment:** every **inbound** reference, found on\n     the **sweep** surface, to a moved **old** path that does **not** include a\n     `#...` fragment (e.g. `…/banana.md` in the repo’s link style for that\n     file).\n   - **List 2 — old path with fragment:** every **inbound** reference, found on\n     the same sweep, to a moved **old** path that **includes** a `#...` fragment\n     (e.g. `…/banana.md#anchor` or the published-style equivalent in context). The\n     **same** old path string may appear on **both** List 1 and List 2 for\n     different links; duplication across the two lists is OK.\n   - **Matching rules:** when recording List 1 / List 2, use **one** consistent\n     path representation for comparison (e.g. relative `../path/banana.md` vs\n     root-anchored) **per the conventions in this doc** and the **surrounding\n     file’s** established pattern. Agents compare and skip List 2 links in the\n     List 1 pass using the **same** representation rules.\n3. **Out of scope** for the lists: only include references whose **old** target\n   is in this run’s **mapping**. Do not build List 1/2 for unrelated **mango/**\n   (or other) problems unless those links also target an **old** path that this\n   migration renames. Defer those issues separately (see **Modes**).\n\n---\n\n## Phase 1 — `aliases` (old published URLs)\n\n1. On each **new** canonical page, add or merge **`aliases`** for every **real**\n   former public URL.\n2. Do not strip existing unrelated aliases.\n3. **PR-scoped:** add aliases only where the canonical file is in scope or the\n   project requires it; otherwise list missing alias targets for follow-up.\n\n---\n\n## Phase 2 — In-repo link reference updates\n\n1. **List 1 first (path only):** update references that belong to **List 1**\n   (old path, **no** fragment). Replace old source paths or old published URLs\n   with the **new** targets; preserve each file’s link pattern (relative vs\n   root-anchored `.md` paths). **Do not** apply the same bulk path replacement to\n   links that appear in **List 2** (old path **with** `#...`) during this\n   sub-step—**leave** every **List 2** link **unchanged** for now.\n2. **After List 1 is complete:** **re-scan** the **same** **sweep** surface as\n   in Phase 0.5 (e.g. all of `content/` plus config) or **print** a clear list of\n   all **remaining** **List 2** entries. Those links should still point at the\n   **old** path and **old** fragment until Phase 3.  \n3. **Full-site (extra sweep):** after steps 1–2, still use **AGENTS “Page\n   deletion checklist”**-style thoroughness for **config / front matter**\n   `link:` and similar so nav and grids are not left on old slugs. Apply the\n   **List 1 / List 2** rules there too: path-only old references first; defer\n   fragment-bearing rewrites in line with **List 2** until Phase 3.\n4. **PR-scoped (which files to change):** apply List 1 and later Phase 3 updates\n   to **every** file the **sweep** finds with a **migration-relevant** reference\n   (inbound to an **old** path in the mapping), including files **not** in\n   `PR_SCOPE_FILES`, per **Modes**. **Log** and **defer** (do not “fix”)\n   unrelated stragglers. If policy forbids out-of-PR file edits, defer per step 1\n   of Phase 0.5.  \n5. Include **shortcodes and layout partials** (see Conventions and **Modes** for\n   sweep vs focus).\n\n---\n\n## Phase 3 — List 2: interactive path and fragment resolution\n\n**Prerequisites:** Phase 2 has updated **List 1**; **List 2** still lists **old\npath + `#...`** (unchanged) for this migration. See **Modes** for which files\nmay be edited; **No guessing** applies.\n\n1. **Print List 2** to the user: every remaining **old path** + `#anchor` (in the\n   agreed representation), so nothing is hidden before the loop.\n2. **For each distinct** `old-path#oldAnchor` (or process in the order the user\n   prefers, one at a time):  \n   - Ask: **What is the new path (and fragment, if any) for this content?** The\n     user may give a new source path, published URL, and/or `#newAnchor` per\n     project conventions.  \n   - **Validate** the user’s answer: open the **target** page (or resolve the\n     target) and check that `#newAnchor` (if any) **exists** as a real heading\n     / generated id on that page, per **CLAUDE.md** / **AGENTS.md** (same rules\n     as the rest of the site). **Do not** replace the user’s fragment with a\n     “better” one from the file.  \n   - If validation **fails** (unknown target file, or `#newAnchor` not found on\n     the page): **warn** clearly (what failed: path vs missing fragment), then\n     **ask again** for a corrected path and/or fragment. **Repeat** until\n     validation passes or the user **defers** / **drops** the fragment (per\n     **AGENTS.md**). **Never** guess a new fragment to fix the problem.  \n   - When validation **passes:** update **all** in-repo references that match\n     that **same** `old-path#oldAnchor` to the user-approved `new-path#newAnchor`\n     (respect each file’s link style; include shortcodes/layouts on the same\n     **sweep** surface as Phase 2).  \n3. **Repeat** from step 1: **re-print** or **re-scan** for **List 2** until it is\n   **empty** or the user defers the remainder.  \n4. **PR-scoped / full-site:** the **loop** is the same. **Edits** follow **Modes**:\n   migration-relevant **inbound** links may live in any file on the sweep; do\n   not expand into **unrelated** link debt from other areas. Defer as in **Modes**\n   and Phase 0.5.\n\n---\n\n## Optional: scripts helper\n\nThis skill includes a small **scope helper** so agents do not re-derive Git\nrecipes. See [scripts/scope-pr-files.sh](scripts/scope-pr-files.sh) — it prints\npaths in PR scope for a given target branch (default `main`).\n\n---\n\n## Verification\n\n```bash\ndocker buildx bake validate\n```\n\nUse the **Definition of done** in **Agent: required procedure (do not skip)**\nas the final bar: **validate** must pass, and the **sweep** must be clean for\n**plain** and **`#fragment`** old-path references, **or** the remainder must be\n**explicitly deferred** in **Phase 3** per **AGENTS.md** / the user. Mid-run,\n**Phase 2** may still leave **List 2** links unchanged **until** Phase 3; that\nintermediate state is **not** the finished migration.\n\n\n<!-- Skill/Rule: research (.agents/skills/research/SKILL.md) -->\n---\nname: research\ndescription: >\n  Research a documentation topic — locate affected files, understand the\n  problem, identify what to change. Use when investigating an issue, a\n  question, or a topic before writing a fix. Triggers on: \"research issue\n  1234\", \"investigate what needs changing for #500\", \"what files are\n  affected by #200\", \"where is X documented\", \"is our docs page about Y\n  accurate\", \"look into how we document Z\".\n---\n\n# Research\n\nThoroughly investigate the topic at hand and produce a clear plan for\nthe fix. The goal is to identify exact files, named targets within those\nfiles, and the verified content needed for the fix.\n\n## 1. Gather context\n\nIf the input is a GitHub issue number, fetch it:\n\n```bash\ngh issue view <number> --repo docker/docs \\\n  --json number,title,body,labels,comments\n```\n\nOtherwise, work from what was provided — a description, a URL, a question,\nor prior conversation context. Identify the topic, affected feature, or\npage to investigate.\n\n## 2. Locate affected files\n\nSearch `content/` using the URL or topic from the issue. Remember the\n`/manuals` prefix mapping when converting URLs to file paths.\n\nFor each candidate file, read the relevant section to confirm it contains\nthe reported problem.\n\n## 3. Check vendored ownership\n\nBefore planning any edit, verify the file is editable locally:\n\n- `_vendor/` — read-only, vendored via Hugo modules\n- `data/cli/` — read-only, generated from upstream YAML\n- `content/reference/cli/` — read-only, generated from `data/cli/`\n- Everything else in `content/` — editable\n\nIf the fix requires upstream changes, identify the upstream repo and note\nit as out of scope. See the vendored content table in CLAUDE.md.\n\n## 4. Find related content\n\nLook for pages that may need updating alongside the primary fix:\n\n- Pages that link to the affected content\n- Include files (`content/includes/`) referenced by the page\n- Related pages in the same section describing the same feature\n\n## 5. Verify facts\n\nIf the issue makes a factual claim about how a feature behaves, verify it.\nFollow external links, read upstream source, check release notes. Do not\nplan a fix based on an unverified claim.\n\nIf the fix requires a replacement URL and that URL cannot be verified (e.g.\nnetwork restrictions), report it as a blocker rather than guessing.\n\n## 6. Check the live site (if needed)\n\nFor URL or rendering issues, fetch the live page:\n\n```\nhttps://docs.docker.com/<path>/\n```\n\n## 7. Report findings\n\nSummarize what you found — files to change, the specific problem in each,\nwhat the fix should be, and any constraints. This context feeds directly\ninto the write step.\n\nBe specific: name the file, the section or element within it, and the\nverified content needed. \"Fix the broken link in networking.md\" is not\nspecific enough. \"In `compose/networking.md`, the 'Custom networks' section,\nremove the note about `driver_opts` being ignored — this was fixed in\nCompose 2.24\" is.\n\n## Notes\n\n- Research quality bounds write quality. Vague research produces broad\n  changes; precise research produces minimal ones.\n- Do not create standalone research files — findings stay in conversation\n  context for the write step.\n\n\n<!-- Skill/Rule: review-changes (.agents/skills/review-changes/SKILL.md) -->\n---\nname: review-changes\ndescription: >\n  Review uncommitted or recently committed documentation changes for\n  correctness, coherence, and style compliance. Use before creating a PR\n  to catch issues. \"review my changes\", \"review the diff\", \"check the fix\n  before submitting\", \"does this look right\".\ncontext: fork\nmodel: opus\n---\n\n# Review Changes\n\nEvaluate whether the changes correctly and completely solve the stated\nproblem, without introducing new issues. Start with no assumptions — the\nchange may contain mistakes. Your job is to catch what the writer missed,\nnot to rubber-stamp the diff.\n\n## 1. Identify what changed\n\nDetermine the scope of changes to review:\n\n```bash\n# Uncommitted changes\ngit diff --name-only\n\n# Last commit\ngit diff --name-only HEAD~1\n\n# Entire branch vs main\ngit diff --name-only main...HEAD\n```\n\nPick the right comparison for what's being reviewed. If reviewing a branch,\nuse `main...HEAD` to see all changes since the branch diverged.\n\n## 2. Read each changed file in full\n\nDo not just read the diff. For every changed file, read the entire file to\nunderstand the full context the change lives in. A diff can look correct in\nisolation but contradict something earlier on the same page.\n\nThen read the diff for the detailed changes:\n\n```bash\n# Adjust the comparison to match step 1\ngit diff --unified=10              # uncommitted\ngit diff --unified=10 HEAD~1       # last commit\ngit diff --unified=10 main...HEAD  # branch\n```\n\n## 3. Follow cross-references\n\nFor each changed file, check what links to it and what it links to:\n\n- Search for other pages that reference the changed content (grep for the\n  filename, heading anchors, or key phrases)\n- Read linked pages to verify the change doesn't create contradictions\n  across pages\n- Check that anchor links in cross-references still match heading IDs\n\nA change that's correct on its own page can break the story told by a\nrelated page.\n\n## 4. Verify factual accuracy\n\nDon't assume the change is factually correct just because it reads well.\n\n- If the change describes how a feature behaves, verify against upstream\n  docs or source code\n- If the change includes a URL, check that it resolves\n- If the change references a CLI flag, option, or API field, confirm it\n  exists\n\n## 5. Evaluate as a reader\n\nConsider someone landing on this page from a search result, with no prior\ncontext:\n\n- Does the page make sense on its own?\n- Is the changed section clear without having read the issue or diff?\n- Would a reader be confused by anything the change introduces or leaves\n  out?\n\n## 6. Review code and template changes\n\nFor non-Markdown changes (JS, HTML, CSS, Hugo templates):\n\n- Trace through the common execution path\n- Trace through at least one edge case (no stored preference, Alpine fails\n  to load, first visit vs returning visitor)\n- Ask whether the change could produce unexpected browser or runtime\n  behavior that no automated tool would catch\n\n## 7. Decision\n\n**Approve** if the change is correct, coherent, complete, and factually\naccurate.\n\n**Request changes** if:\n- The change does not correctly solve the stated problem\n- There is a factual error or contradiction (on-page or cross-page)\n- A cross-reference is broken or misleading\n- A reader would be confused\n\nWhen requesting changes, be specific: quote the exact text that is wrong,\nexplain why, and suggest the correct fix.\n\n\n<!-- Skill/Rule: Agents Skill (.agents/skills/review-pr/agents/openai.yaml) -->\ninterface:\n  display_name: \"Review PR\"\n  short_description: \"Validate incoming documentation pull requests\"\n  default_prompt: \"Use $review-pr to validate this incoming documentation PR and draft maintainer feedback.\"\n\n\n<!-- Skill/Rule: review-pr (.agents/skills/review-pr/SKILL.md) -->\n---\nname: review-pr\ndescription: >\n  Review one or more incoming Docker documentation pull requests as a\n  maintainer. Independently validate technical claims, assess editorial fit\n  and information architecture, choose a verdict, and draft exact inline or\n  PR-wide feedback behind a confirmation gate. Use for requests such as\n  \"review PR 123\", \"is this PR correct?\", \"does this information belong\n  here?\", \"validate this PR\", or \"help review backlog PRs\". Do not use to\n  maintain or fix a PR you own; use maintain-pr for that.\n---\n\n# Review PR\n\nReview incoming contributions for factual correctness and whether they make\nthe documentation better as a whole. Treat a technically true addition as\ninsufficient when it is misplaced, overemphasized, redundant, or unhelpful\nto the page's intended reader.\n\n## Preserve the write boundary\n\nPerform the review in two phases:\n\n1. Research the PR, decide a verdict, and present the exact proposed\n   comment or review text.\n2. Wait for explicit user confirmation, then post only the confirmed text.\n\nBefore confirmation, do not post comments, submit a GitHub review, approve or\nrequest changes, resolve threads, push commits, edit labels, or otherwise\nmutate GitHub. A request to review or draft feedback is not confirmation to\npost it. Ask `Post these comments?` and stop. Treat revisions to a draft as\nunconfirmed until the user explicitly asks to post them.\n\n## 1. Gather the full context\n\nFor each PR, inspect its metadata, body, commits, changed files, checks,\nconversation, reviews, and linked issues. Always fetch inline comments\nseparately because `gh pr view --json reviews` omits them.\n\n```bash\ngh pr view <PR> --repo docker/docs \\\n  --json number,title,url,state,author,body,baseRefName,headRefName,headRefOid,commits,files,comments,reviews,reviewDecision,statusCheckRollup\ngh api repos/docker/docs/pulls/<PR>/comments \\\n  --jq '[.[] | {id, author: .user.login, body, path, line, side, commit_id}]'\ngh pr diff <PR> --repo docker/docs\n```\n\nRead linked issues and relevant discussion. An issue is evidence that a\nreader was confused, but it does not establish the reporter's diagnosis or\njustify a new highlighted note by itself. Green CI establishes only that automated\nchecks passed, not that the content is correct.\n\nFetch the PR head when local inspection is useful. Compare it with the\ncanonical upstream base rather than assuming the local branch is fresh.\nRead each changed file in full, not only its diff.\n\n## 2. Research independently\n\nVerify every material claim against authoritative sources such as product\nsource code, upstream documentation, specifications, release notes, or safe\nlocal reproduction. Do not accept the PR description, issue diagnosis, or\nexisting review feedback as fact.\n\nSearch the documentation for related explanations and canonical pages. Read\n`STYLE.md`, `COMPONENTS.md`, and applicable repository instructions. Check\nwhether a changed file is generated or maintained upstream and identify the correct\nupstream repository instead of proposing a local edit.\n\nDistinguish among:\n\n- a wrong fact\n- a correct fact expressed inaccurately\n- a correct fact placed on the wrong page\n- content already explained elsewhere\n- a real discovery problem better addressed with a short signpost and link\n- a request that needs no documentation change.\n\nIf an external claim or replacement URL cannot be verified, report that\nlimitation instead of guessing.\n\n## 3. Assess editorial fit\n\nApply these questions to each addition:\n\n- Does it change a reader's decision or next action on this page?\n- Is this the canonical page for the concept?\n- Is the fact general, or specific to this page, feature, or component?\n- Is the information already documented elsewhere?\n- Would a concise local signpost to canonical coverage solve the discovery\n  problem better than duplicating the explanation?\n- Is the visual and textual weight proportional to the information's value?\n- Does it preserve the page's scope, flow, and character?\n\nPrefer one coherent explanation in the canonical location. Add local context\nonly when it helps the reader complete the task at hand. Avoid stray notes,\ncallouts, and exhaustive edge cases whose prominence exceeds their value.\n\n## 4. Choose a decisive verdict\n\nLead with one of these outcomes:\n\n- **Approve**: correct, useful, well placed, and ready to merge.\n- **Approve with optional polish**: ready to merge; suggestions are genuinely\n  non-blocking.\n- **Focused rewrite**: the underlying need is valid, but wording, scope,\n  placement, or structure should change before merge.\n- **Close / no docs change**: incorrect, redundant, out of scope, or not a\n  documentation problem.\n\nExplain the verdict with evidence. When wording is the issue, provide exact\nreplacement text rather than a vague request to improve it.\n\n## 5. Place feedback deliberately\n\nUse an inline comment when the finding is anchored to a narrow changed line\nor range and acting on it is local. Examples include an inaccurate sentence,\nan ambiguous option description, a broken link, or a precise wording\nreplacement.\n\nUse a PR-wide comment for scope, information architecture, overall approach,\nmultiple intertwined edits, or a proposed replacement section. Do not attach\nholistic feedback to an arbitrary line.\n\nUse both when appropriate: put the overall direction in the PR-wide comment\nand line-specific corrections inline. Do not repeat the same point in both.\nConsolidate related feedback so the author receives the fewest comments that\nremain clear and actionable.\n\nFor every proposed inline comment, resolve and display the current changed\nfile path and right-side diff line. If the target line is not part of the\ncurrent diff or cannot be identified reliably, use a PR-wide comment that\nquotes the target text instead. Never guess a line number.\n\nEnd comments posted on the user's behalf with an accurate agent-disclosure\nfooter, such as `Generated by Codex`.\n\n## 6. Present drafts and stop\n\nBefore any GitHub write, show the review in this form, omitting empty\nsections:\n\n```markdown\n## Verdict\n\nFocused rewrite\n\n## Findings\n\n- <finding and evidence>\n\n## Proposed inline comments\n\n1. `path/to/file.md:42`\n   > Exact comment text\n\n## Proposed PR-wide comment\n\n> Exact comment text\n\nPost these comments?\n```\n\nFor multiple PRs, give each PR its own verdict and comment set. Make the\nconfirmation scope unambiguous. Do not interpret approval of one PR's drafts\nas approval to post comments on the others.\n\n## 7. Post only confirmed feedback\n\nImmediately before posting, re-fetch the PR head SHA and diff. If either the\nhead or an inline target changed, stop and show the updated draft or\nplacement for confirmation.\n\nPost confirmed inline comments as a single comment-only review when\npractical. Use the current head SHA and right-side diff lines:\n\n```bash\ngh api repos/docker/docs/pulls/<PR>/reviews --method POST --input <payload>\n```\n\nThe JSON payload contains `commit_id`, `event: \"COMMENT\"`, and a `comments`\narray whose entries contain `path`, `line`, `side: \"RIGHT\"`, and `body`.\nSubmitting a review with `APPROVE` or `REQUEST_CHANGES` requires separate,\nexplicit user authorization; a verdict alone does not grant it.\n\nPost confirmed holistic feedback separately:\n\n```bash\ngh pr comment <PR> --repo docker/docs --body-file <file>\n```\n\nUse a safely created temporary file or API input so Markdown, backticks, and\nshell substitutions are preserved literally. Post exactly the confirmed\ntext. Verify the resulting review/comments and report their URLs and\nplacements. If GitHub rejects an inline location, do not silently fall back\nto a PR-wide comment; report the failure and prepare a revised placement for\nconfirmation.\n\n## Definition of done\n\n- Verify technical claims with authoritative evidence.\n- Evaluate usefulness, placement, duplication, and proportionality.\n- Give a decisive verdict and exact actionable wording.\n- Choose inline and PR-wide placement based on the feedback's scope.\n- Show every exact draft and target before any GitHub mutation.\n- Post only after explicit confirmation and verify what was posted.\n\n\n<!-- Skill/Rule: testcontainers-guide-migrator (.agents/skills/testcontainers-guides-migrator/SKILL.md) -->\n---\nname: testcontainers-guide-migrator\ndescription: >\n  Migrate a Testcontainers guide from testcontainers.com into the Docker docs site (docs.docker.com).\n  Converts AsciiDoc to Hugo Markdown, updates code to the latest Testcontainers API, splits into\n  chapters with stepper navigation, verifies code compiles and tests pass, and validates against\n  Docker docs style rules. Use when asked to migrate a testcontainers guide, add a TC guide, or\n  port content from testcontainers.com to Docker docs.\n---\n\n# Migrate a Testcontainers Guide\n\nYou are migrating guides from https://testcontainers.com/guides/ into the Docker docs Hugo site.\nEach guide lives in its own GitHub repo under `testcontainers/tc-guide-*`, written in AsciiDoc.\nThe source repos are listed in the testcontainers-site build.sh:\nhttps://github.com/testcontainers/testcontainers-site/blob/main/build.sh#L23-L45\n\n## Inputs\n\nThe user provides one or more guides to migrate. Resolve these from the inventory below:\n\n- **REPO_NAME**: GitHub repo (e.g. `tc-guide-getting-started-with-testcontainers-for-java`)\n- **SLUG**: guide slug inside `guide/` dir (e.g. `getting-started-with-testcontainers-for-java`)\n- **LANG**: language identifier (go, java, dotnet, nodejs, python)\n- **GUIDE_ID**: short kebab-case name (e.g. `getting-started`)\n\n## Guide inventory\n\nThese are the 21 guides from testcontainers.com/guides/ and their source repos:\n\n| # | Title | Repo | Lang | GUIDE_ID |\n|---|-------|------|------|----------|\n| 1 | Introduction to Testcontainers | tc-guide-introducing-testcontainers | (none) | introducing |\n| 2 | Getting started for Java | tc-guide-getting-started-with-testcontainers-for-java | java | getting-started |\n| 3 | Testing Spring Boot REST API | tc-guide-testing-spring-boot-rest-api | java | spring-boot-rest-api |\n| 4 | Testcontainers lifecycle (JUnit 5) | tc-guide-testcontainers-lifecycle | java | lifecycle |\n| 5 | Configuration of services in container | tc-guide-configuration-of-services-running-in-container | java | service-configuration |\n| 6 | Replace H2 with real database | tc-guide-replace-h2-with-real-database-for-testing | java | replace-h2 |\n| 7 | Testing ASP.NET Core web app | tc-guide-testing-aspnet-core | dotnet | aspnet-core |\n| 8 | Testing Spring Boot Kafka Listener | tc-guide-testing-spring-boot-kafka-listener | java | spring-boot-kafka |\n| 9 | REST API integrations with MockServer | tc-guide-testing-rest-api-integrations-using-mockserver | java | mockserver |\n| 10 | Getting started for .NET | tc-guide-getting-started-with-testcontainers-for-dotnet | dotnet | getting-started |\n| 11 | AWS integrations with LocalStack | tc-guide-testing-aws-service-integrations-using-localstack | java | aws-localstack |\n| 12 | Testcontainers in Quarkus apps | tc-guide-testcontainers-in-quarkus-applications | java | quarkus |\n| 13 | Getting started for Go | tc-guide-getting-started-with-testcontainers-for-go | go | getting-started |\n| 14 | jOOQ and Flyway with Testcontainers | tc-guide-working-with-jooq-flyway-using-testcontainers | java | jooq-flyway |\n| 15 | Getting started for Node.js | tc-guide-getting-started-with-testcontainers-for-nodejs | nodejs | getting-started |\n| 16 | REST API integrations with WireMock | tc-guide-testing-rest-api-integrations-using-wiremock | java | wiremock |\n| 17 | Local dev with Testcontainers Desktop | tc-guide-simple-local-development-with-testcontainers-desktop | java | local-dev-desktop |\n| 18 | Micronaut REST API with WireMock | tc-guide-testing-rest-api-integrations-in-micronaut-apps-using-wiremock | java | micronaut-wiremock |\n| 19 | Micronaut Kafka Listener | tc-guide-testing-micronaut-kafka-listener | java | micronaut-kafka |\n| 20 | Getting started for Python | tc-guide-getting-started-with-testcontainers-for-python | python | getting-started |\n| 21 | Keycloak with Spring Boot | tc-guide-securing-spring-boot-microservice-using-keycloak-and-testcontainers | java | keycloak-spring-boot |\n\nAlready migrated: **#2 (Java getting-started)**, **#13 (Go getting-started)**, **#20 (Python getting-started)**\n\n## Step 0: Pre-flight\n\n1. Confirm `testing-with-docker` tag exists in `data/tags.yaml`. If not, add:\n   ```yaml\n   testing-with-docker:\n     title: Testing with Docker\n   ```\n2. Check if new terms need adding to `_vale/config/vocabularies/Docker/accept.txt`.\n3. Read `STYLE.md` and `COMPONENTS.md` to refresh on Docker docs conventions.\n\n## Step 1: Clone the guide repo\n\nClone the guide repo to a temporary directory. This gives you all source files locally — no HTTP calls needed.\n\n```bash\ngit clone --depth 1 https://github.com/testcontainers/{REPO_NAME}.git <tmpdir>/{REPO_NAME}\n```\n\nWhere `<tmpdir>` is a temporary directory on your system (e.g. the output of `mktemp -d`).\n\nThe repo structure is:\n- `<tmpdir>/{REPO_NAME}/guide/{SLUG}/index.adoc` — the AsciiDoc guide source\n- `<tmpdir>/{REPO_NAME}/src/` — application source code (referenced by `include::` directives)\n- `<tmpdir>/{REPO_NAME}/testdata/` — test data files (SQL scripts, configs, etc.)\n- `<tmpdir>/{REPO_NAME}/pom.xml` or `go.mod` — build config\n\n1. Read `guide/{SLUG}/index.adoc` to get the guide content.\n2. Find all `include::{codebase}/path/to/file[]` directives. The `{codebase}` attribute points to a remote URL, but since you have the repo cloned, read the files directly from disk instead (e.g. `include::{codebase}/src/main/java/Foo.java[]` → read `<tmpdir>/{REPO_NAME}/src/main/java/Foo.java`).\n3. If includes have `[lines=\"X..Y\"]`, extract only those lines from the local file.\n4. Note the `[source,lang]` block preceding each include — that determines the code fence language.\n\nThis cloned repo also serves as the base for Step 6 (code verification) — you can run the tests directly in it to confirm they pass before updating the code to the latest API.\n\n## Step 2: Convert AsciiDoc to Markdown\n\n| AsciiDoc | Markdown |\n|---|---|\n| `== Heading` | `## Heading` |\n| `=== Heading` | `### Heading` |\n| `*bold*` (AsciiDoc bold) | `**bold**` |\n| `https://url[Link text]` | `[Link text](url)` |\n| `[source,lang]\\n----\\ncode\\n----` | `` ```lang\\ncode\\n``` `` |\n| `[source,shell]` with `$` prompts | `` ```console `` |\n| `[NOTE]\\ntext` or `====\\n[NOTE]\\n...\\n====` | `> [!NOTE]\\n> text` |\n| `[TIP]\\ntext` | `> [!TIP]\\n> text` |\n| `:toc:`, `:toclevels:`, `:codebase:` | Remove entirely |\n| `include::{codebase}/path[]` | Replace with fetched code in a code fence |\n| YAML front matter (date, draft, repo) | Remove; transform to Docker docs format |\n\n## Step 3: Apply Docker docs style rules\n\nThese are mandatory (from STYLE.md and AGENTS.md):\n\n- **No \"we\"**: \"We are going to create\" → \"Create\" or \"Start by creating\"\n- **No \"let us\" / \"let's\"**: → imperative voice or \"You can...\"\n- **No hedge words**: remove \"simply\", \"easily\", \"just\", \"seamlessly\"\n- **No meta-commentary**: remove \"it's worth noting\", \"it's important to understand\"\n- **No \"allows you to\" / \"enables you to\"**: → \"lets you\" or rephrase\n- **No \"click\"**: → \"select\"\n- **No bold for emphasis or product names**: only bold UI elements\n- **No time-relative language**: remove \"currently\", \"new\", \"recently\", \"now\"\n- **No exclamations**: remove \"Voila!!!\" etc.\n- Use `console` language hint for interactive shell blocks with `$` prompts\n- Use contractions: \"it's\", \"you're\", \"don't\"\n\n## Step 4: Update code to latest Testcontainers API\n\nResearch the latest API version for the target language before writing code.\n\n**Best practices reference**: The Testcontainers team maintains Claude skills with up-to-date API patterns and best practices for each language at https://github.com/testcontainers/claude-skills/ — check the relevant language skill (testcontainers-go, testcontainers-node, testcontainers-dotnet) for current API signatures, cleanup patterns, wait strategies, and anti-patterns to avoid.\n\nFor each language, check the cloned repo's existing code, then update to the latest API. Key patterns per language:\n\n**Go** (testcontainers-go v0.41.0):\n- `postgres.RunContainer(ctx, opts...)` → `postgres.Run(ctx, \"image\", opts...)`\n- `testcontainers.WithImage(...)` → image is now the 2nd positional param to `Run()`\n- Manual `WithWaitStrategy(wait.ForLog(...))` → `postgres.BasicWaitStrategies()`\n- `t.Cleanup(func() { ctr.Terminate(ctx) })` → `testcontainers.CleanupContainer(t, ctr)`\n- `if err != nil { log.Fatal(err) }` → `require.NoError(t, err)` (use testify require/assert)\n- Helper functions should accept `t *testing.T` as first param, call `t.Helper()`\n- No `TearDownSuite()` needed if `CleanupContainer` is registered in the helper\n- Go version prerequisite: 1.25+\n\n**Java** (testcontainers-java 2.0.4):\n- Artifacts renamed in 2.x: `org.testcontainers:postgresql` → `org.testcontainers:testcontainers-postgresql`\n- Check the latest version at https://java.testcontainers.org/\n- Use `@Testcontainers` and `@Container` annotations for JUnit 5 lifecycle\n- Prefer module-specific containers (e.g. `PostgreSQLContainer`) over `GenericContainer`\n- Use `@DynamicPropertySource` for Spring Boot integration\n\n**.NET** (testcontainers-dotnet):\n- Check the latest NuGet package version\n- Use `IAsyncLifetime` for container lifecycle in xUnit\n- Use builder pattern: `new PostgreSqlBuilder().Build()`\n\n**Node.js** (testcontainers-node):\n- Check the latest npm version\n- Use module-specific packages (e.g. `@testcontainers/postgresql`)\n- Use `GenericContainer` for services without a dedicated module\n\n**Python** (testcontainers-python):\n- Check the latest PyPI version\n- Use context managers (`with PostgresContainer() as postgres:`)\n- Use module-specific containers when available\n\nFor all languages: consult the corresponding Testcontainers skill at https://github.com/testcontainers/claude-skills/ for current best practices and anti-patterns.\n\n## Step 5: Create guide directory structure\n\nDirectory: `content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n\nEach guide is its own top-level entry under `/guides/`. Do NOT nest guides inside a shared parent section — otherwise they won't appear individually in the tag/language filters on the guides listing page.\n\n### _index.md (landing page)\n\n```yaml\n---\ntitle: {Full guide title}\nlinkTitle: {Short title for guides listing}\ndescription: {One-line description}\nkeywords: testcontainers, {lang}, testing, {technologies used}\nsummary: |\n  {2-3 line summary for the guides listing card}\ntoc_min: 1\ntoc_max: 2\ntags: [testing-with-docker]\nlanguages: [{lang}]\nparams:\n  time: {estimated} minutes\n---\n\n<!-- Source: https://github.com/testcontainers/{REPO_NAME} -->\n```\n\nContent: what you'll learn (bulleted list), prerequisites, and a NOTE linking to `https://testcontainers.com/getting-started/` for newcomers.\n\n### Sub-pages (chapters)\n\nSplit the guide into logical chapters. Each sub-page:\n\n```yaml\n---\ntitle: {Chapter title}\nlinkTitle: {Short title for stepper}\ndescription: {One-line description}\nweight: {10, 20, 30, ...}\n---\n```\n\n**No `tags`, `languages`, or `params` on sub-pages** — only on `_index.md`.\n\nTypical chapter breakdown:\n| Weight | File | Content |\n|--------|------|---------|\n| 10 | `create-project.md` | Project setup, dependencies, business logic |\n| 20 | `write-tests.md` | First test using testcontainers |\n| 30 | `test-suites.md` | Reusing containers, test helpers, suites |\n| 40 | `run-tests.md` | Running tests, summary, further reading |\n\nAdapt the split to the guide's content — some guides may need fewer or more chapters.\n\n## Step 6: Verify code compiles and tests pass\n\nThis is CRITICAL. The code in the guide MUST compile and all tests MUST pass. Do not skip this step.\n\n### 6a: Use the cloned repo as the verification project\n\nThe repo you cloned in Step 1 (`<tmpdir>/{REPO_NAME}`) already contains a working project with all source files, build config, and tests. Use it as the starting point:\n\n```bash\ncd <tmpdir>/{REPO_NAME}\n```\n\nFirst, verify the **original** code compiles and tests pass before you change anything. This confirms a good baseline.\n\n### 6b: Update the code in the cloned repo\n\nAfter confirming the original works, apply the API updates (from Step 4) directly in the cloned repo's source files. This is the same code you're putting in the guide — keep them in sync.\n\n### 6c: Update dependencies and compile\n\nRun compilation inside a container for reproducibility — no need to install the language toolchain on the host. Use the appropriate language Docker image, mounting the cloned repo:\n\n```bash\ndocker run --rm -v \"<tmpdir>/{REPO_NAME}\":/app -w /app <language-image> sh -c \"<compile command>\"\n```\n\nPick the right image for the language (e.g. `golang:1.25-alpine`, `maven:3-eclipse-temurin-21`, `gradle:jdk21`, `mcr.microsoft.com/dotnet/sdk:9.0`, `node:22-alpine`, `python:3.13-alpine`). Update dependencies to the latest Testcontainers version and compile.\n\nIf compilation fails, fix the code and update the guide markdown to match.\n\n### 6d: Run tests in a container with Docker socket mounted\n\nRun tests in the same kind of container, but **mount the Docker socket** so Testcontainers can create sibling containers.\n\n#### macOS Docker Desktop workarounds\n\nWhen running on macOS with Docker Desktop, these environment variables and flags are **required**:\n\n- **`TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`** — On macOS, containers can't reach sibling containers via the Docker bridge IP (`172.17.0.x`). This tells Testcontainers (including Ryuk) to connect via `host.docker.internal` instead. **Do NOT disable Ryuk** — it is a core Testcontainers feature and the guides must demonstrate proper usage.\n- **`docker-java.properties`** with `api.version=1.47` — Docker Desktop's minimum API version is 1.44, but docker-java defaults to 1.24. Create this file in the project root and mount it to `/root/.docker-java.properties` inside Java containers.\n- **`-Dspotless.check.skip=true`** — The Spotless Maven plugin in the source repos is incompatible with JDK 21. Skip it since it's a code formatter, not part of the test.\n- **`-Dmicronaut.test.resources.enabled=false`** — Micronaut's Test Resources service starts a separate process that can't connect to Docker from inside a container. The guide tests use Testcontainers directly, not Test Resources. Only needed for Micronaut guides.\n#### Java guide test command\n\n```bash\n# Create docker-java.properties in the project root\necho \"api.version=1.47\" > <tmpdir>/{REPO_NAME}/docker-java.properties\n\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v \"<tmpdir>/{REPO_NAME}/docker-java.properties\":/root/.docker-java.properties \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  maven:3.9-eclipse-temurin-21 \\\n  mvn -B test -Dspotless.check.skip=true -Dspotless.apply.skip=true\n```\n\nFor Quarkus guides, use `maven:3.9-eclipse-temurin-17` instead (Quarkus 3.22.3 compiles for Java 17).\n\n#### Go guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  golang:1.25-alpine \\\n  sh -c \"apk add --no-cache gcc musl-dev && go test -v -count=1 ./...\"\n```\n\n#### Python guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  python:3.13-slim \\\n  sh -c \"pip install -r requirements.txt && python -m pytest\"\n```\n\n#### .NET guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  mcr.microsoft.com/dotnet/sdk:9.0 \\\n  dotnet test\n```\n\n#### Node.js guide test command\n\n```bash\ndocker run --rm \\\n  -v \"<tmpdir>/{REPO_NAME}\":/app \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -e DOCKER_HOST=unix:///var/run/docker.sock \\\n  -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \\\n  -w /app \\\n  node:22-alpine \\\n  sh -c \"npm install && npm test\"\n```\n\n#### Important: run tests sequentially\n\nRun guide tests **one at a time**. Running multiple concurrent DinD or sibling-container tests can overwhelm Docker Desktop's containerd store and cause `meta.db: input/output error` corruption, requiring a Docker Desktop restart.\n\n### 6e: Fix until green\n\nIf any test fails, debug and fix the code in both the temporary project AND the guide markdown. Re-run until all tests pass. Do not proceed until verified.\n\n## Step 7: Update cross-references\n\n1. **`content/manuals/testcontainers.md`**: Add a bullet under the `## Guides` section:\n   ```markdown\n   - [Guide title](/guides/testcontainers-{LANG}-{GUIDE_ID}/)\n   ```\n2. **Do NOT update** `content/guides/testcontainers-cloud/_index.md` — keep its external links.\n3. Link to `https://testcontainers.com/getting-started/` for the Testcontainers overview.\n4. Use internal paths for already-migrated guides; keep `testcontainers.com` links for unmigrated ones.\n\n## Step 8: Validate\n\n**IMPORTANT**: Run ALL validation locally before committing. Vale checks run on CI and will block the PR if they fail — fixing after push wastes CI cycles and review time.\n\n1. `npx --no-install rumdl fmt content/guides/testcontainers-{LANG}-{GUIDE_ID}/`\n2. `npx --no-install rumdl fmt content/manuals/testcontainers.md`\n3. `docker buildx bake lint` — must pass with no errors\n4. `docker buildx bake vale` — then check for errors in the new files:\n   ```bash\n   grep -A2 \"testcontainers-{LANG}-{GUIDE_ID}\" tmp/vale.out\n   ```\n   Fix ALL errors before proceeding. Common issues:\n   - **Vale.Spelling**: tech terms (library names, tools) not in the dictionary → add to `_vale/config/vocabularies/Docker/accept.txt` (alphabetical order)\n   - **Vale.Terms**: wrong casing (e.g. \"python\" → \"Python\") → fix in the markdown. Watch for package names like `testcontainers-python` triggering false positives — rephrase to \"Testcontainers for Python\" in prose.\n   - **Docker.Avoid**: hedge words like \"very\", \"simply\" → reword\n   - **Docker.We**: first-person plural → rewrite to \"you\" or imperative\n   - Info-level suggestions (e.g. \"VS Code\" → \"versus\") are not blocking but review them\n\n   Re-run `docker buildx bake vale` after fixes until no errors remain in the new files.\n5. Verify in local dev server (`HUGO_PORT=1314 docker compose watch`):\n   - Guide appears when filtering by its language\n   - Guide appears when filtering by `Testing with Docker` tag\n   - Stepper navigation works across chapters\n   - All links resolve (no 404s)\n6. Verify all external URLs return 200:\n   ```bash\n   curl -s -o /dev/null -w \"%{http_code}\" -L \"{url}\"\n   ```\n\n## Step 9: Commit\n\nOne commit per guide. Message format:\n```\nfeat(guides): add testcontainers {lang} {guide-id} guide\n\nMigrated from https://github.com/testcontainers/{REPO_NAME}\nUpdated to testcontainers-{lang} v{version} API.\n```\n\n## Special cases\n\n- **introducing-testcontainers**: Language-agnostic, conceptual. May overlap with `content/manuals/testcontainers.md`. Review for deduplication before migrating.\n- **local-dev-testcontainers-desktop**: About Testcontainers Desktop (now part of Docker Desktop). May need significant rewriting rather than mechanical migration.\n- **Java guides**: Many share the same language. Each still gets its own `testcontainers-java-{GUIDE_ID}` directory.\n\n## Reference: completed migration (Go getting-started)\n\nUse `content/guides/testcontainers-go-getting-started/` as the reference implementation:\n- `_index.md` — landing page with frontmatter, prerequisites, learning objectives\n- `create-project.md` (weight: 10) — project setup and business logic\n- `write-tests.md` (weight: 20) — first test with testcontainers-go\n- `test-suites.md` (weight: 30) — container reuse with testify suites\n- `run-tests.md` (weight: 40) — running tests, summary, further reading\n\n\n<!-- Skill/Rule: triage-issue (.agents/skills/triage-issue/SKILL.md) -->\n---\nname: triage-issue\ndescription: >\n  Analyze a single GitHub issue for docker/docs — check whether the problem\n  still exists, determine a verdict, and report findings. Use when asked to\n  triage, assess, or review an issue, even if the user doesn't say \"triage\"\n  explicitly: \"triage issue 1234\", \"is issue 500 still valid\", \"should we\n  close #200\", \"look at this issue\", \"what's going on with #200\".\nargument-hint: \"<issue-number>\"\ncontext: fork\n---\n\n# Triage Issue\n\nGiven GitHub issue **$ARGUMENTS** from docker/docs, figure out whether\nit's still a real problem and say what should happen next.\n\n## 1. Fetch the issue\n\n```bash\ngh issue view $ARGUMENTS --repo docker/docs \\\n  --json number,title,body,state,labels,createdAt,updatedAt,closedAt,assignees,author,comments\n```\n\n## 2. Understand the problem\n\nRead the issue body and all comments. Identify:\n\n- What is the reported problem?\n- What content, URL, or file does it reference?\n- Has anyone already proposed a fix or workaround in the comments?\n\nCheck for linked PRs in the issue timeline, not only in the issue body or\ncomments:\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/timeline --paginate \\\n  --jq '.[] | select(.event==\"cross-referenced\" or .event==\"connected\" or .event==\"referenced\") | {event, created_at, source: .source.issue.html_url, title: .source.issue.title, state: .source.issue.state}'\n```\n\nIf an open PR already addresses the issue, don't open another PR. Review the\nexisting PR instead, and report that the issue already has an associated PR. A\nmerged PR is strong evidence the issue is fixed. A closed-without-merge PR means\nthe issue is likely still open.\n\n## 3. Follow URLs\n\nFind all `docs.docker.com` URLs in the issue body and comments. For each:\n\n- Fetch the URL to check if it still exists (404 = content removed or moved)\n- Check whether the content still contains the problem described\n- Note when the page was last updated relative to when the issue was filed\n\nFor non-docs URLs (GitHub links, external references), fetch them too if\nthey are central to understanding the issue.\n\n## 4. Check the repository\n\nIf the issue references specific files, content sections, or code:\n\n- Find and read the current version of that content\n- Check whether the problem has been fixed, content moved, or file removed\n- Remember the `/manuals` prefix mapping when looking up files\n\n## 5. Check for upstream ownership\n\nIf the issue is about content in `_vendor/` or `data/cli/`, it cannot be\nfixed here. Identify which upstream repo owns it (see the vendored content\ntable in CLAUDE.md).\n\n## 6. Decide and act\n\nAfter investigating, pick one of these verdicts and take the corresponding\naction on the issue:\n\n- **Close it** — the problem is already fixed, the content no longer exists,\n  or the issue is too outdated to be useful. Close the issue with a comment\n  explaining why:\n\n  ```bash\n  gh issue close $ARGUMENTS --repo docker/docs \\\n    --comment \"Closing: <one-sentence reason>\"\n  ```\n\n- **Fix it** — the problem is real and fixable in this repo. Name the\n  file(s) and what needs to change. Label the issue `status/confirmed` and\n  remove `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/confirmed'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Escalate upstream** — the problem is real but lives in vendored content.\n  Name the upstream repo. Label the issue `status/upstream` and remove\n  `status/triage` if present:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/upstream'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\n- **Leave it open** — you can't determine the current state, or the issue\n  needs human judgment. Label the issue `status/needs-analysis`:\n\n  ```bash\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n    --method POST --field 'labels[]=status/needs-analysis'\n  gh api repos/docker/docs/issues/$ARGUMENTS/labels/status%2Ftriage \\\n    --method DELETE || true\n  ```\n\nDon't overthink the classification. An old issue isn't stale if the problem\nstill exists. An upstream issue is still valid — it's just not fixable here.\n\nAlso apply the most relevant `area/` label based on the content affected.\nAvailable area labels: `area/accounts`, `area/admin`, `area/ai`,\n`area/api`, `area/billing`, `area/build`, `area/build-cloud`, `area/cli`,\n`area/compose`, `area/compose-spec`, `area/config`, `area/contrib`,\n`area/copilot`, `area/desktop`, `area/dhi`, `area/engine`,\n`area/enterprise`, `area/extensions`, `area/get-started`, `area/guides`,\n`area/hub`, `area/install`, `area/networking`, `area/offload`,\n`area/release-notes`, `area/samples`, `area/scout`, `area/security`,\n`area/storage`, `area/subscription`, `area/swarm`, `area/ux`. Pick one\n(or at most two if the issue clearly spans areas). Skip if none fit.\n\n```bash\ngh api repos/docker/docs/issues/$ARGUMENTS/labels \\\n  --method POST --field 'labels[]=area/<name>'\n```\n\n## 7. Report\n\nWrite a short summary: what the issue reports, what you found, and what\nshould happen next. Reference the specific files, URLs, or PRs that support\nyour conclusion. Skip metadata fields — the issue itself has the dates and\nlabels. Mention the action you took (closed, labeled, etc.).\n\n## Notes\n\n- Always check timeline cross-references before deciding to fix an issue\n- Do not narrate your process — produce the final report\n- End every issue comment with an accurate agent-disclosure footer that names\n  the active coding agent, for example `Generated by Codex` or `Generated by\nClaude Code`.\n.\n\n\n<!-- Skill/Rule: write (.agents/skills/write/SKILL.md) -->\n---\nname: write\ndescription: >\n  Write a documentation fix on a branch. Makes the minimal change, formats,\n  self-reviews, and commits. Use after research has identified what to change.\n  \"write the fix\", \"make the changes\", \"implement the fix for #1234\".\nhooks:\n  PostToolUse:\n    - matcher: \"Edit|Write\"\n      hooks:\n        - type: command\n          command: \"bash ${CLAUDE_SKILL_DIR}/scripts/post-edit.sh\"\n---\n\n# Write\n\nMake the minimal change that resolves the issue. Research has already\nidentified what to change — this skill handles the edit, formatting,\nself-review, and commit.\n\n## 1. Create a branch\n\n```bash\ngit checkout -b fix/issue-<number>-<short-desc> main\n```\n\nUse a short kebab-case description derived from the issue title (3-5 words).\n\n## 2. Read then edit\n\nAlways read each file before modifying it. Make the minimal change that\nfixes the issue. Do not improve surrounding content, add comments, or\naddress adjacent problems.\n\nFollow the writing guidelines in CLAUDE.md, STYLE.md, and COMPONENTS.md.\n\n## 3. Front matter check\n\nEvery content page requires `title`, `description`, and `keywords` in its\nfront matter. If any are missing from a file you touch, add them.\n\n## 4. Validate\n\nrumdl runs automatically after each edit via the PostToolUse hook.\nRun lint manually after all edits are complete:\n\n```bash\nscripts/lint.sh <changed-files>\n```\n\nThe lint script runs rumdl and Vale on only the files you pass it,\nso the output is scoped to your changes. Fix any errors it reports.\n\n## 5. Self-review\n\nRe-read each changed file: right file, right lines, change is complete,\nfront matter is present. Run `git diff` and verify only intended changes\nare present.\n\n## 6. Commit\n\nStage only the changed files:\n\n```bash\ngit add <files>\ngit diff --cached --name-only  # verify — no package-lock.json or other noise\ngit commit -m \"$(cat <<'EOF'\ndocs: <short description under 72 chars> (fixes #NNNN)\n\n<What was wrong: one sentence citing the specific problem.>\n<What was changed: one sentence describing the exact edit.>\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\"\n```\n\nThe commit body is mandatory. A reviewer reading only the commit should\nunderstand the problem and the fix without opening the issue.\n\n## Notes\n\n- Never edit `_vendor/` or `data/cli/` — these are vendored\n- If a file doesn't exist, check for renames:\n  `git log --all --full-history -- \"**/filename.md\"`\n- If the fix requires a URL that cannot be verified, stop and report a\n  blocker rather than guessing\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/_index.md) -->\n---\ntitle: Tools\ndescription: Interfaces and tools for browsing, managing, and automating Docker Hardened Images.\nweight: 25\nparams:\n  grid_tools:\n    - title: Use Docker Hub\n      description: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\n      icon: squares-2x2\n      link: /dhi/tools/hub/\n    - title: CLI\n      description: Install and use the `docker dhi` command-line interface to browse the catalog, inspect images, and manage mirrors from your terminal.\n      icon: command-line\n      link: /dhi/tools/cli/\n    - title: MCP server\n      description: Connect an AI assistant to the DHI catalog to search repositories, inspect images, retrieve SBOMs, and check CVEs using plain language.\n      icon: cpu-chip\n      link: /dhi/tools/mcp/\n    - title: Use the DHI Terraform provider\n      description: Use the DHI Terraform provider to manage mirrors and automate DHI configuration as infrastructure as code.\n      icon: wrench-screwdriver\n      link: /dhi/tools/terraform/\n    - title: Use the DHI API\n      description: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\n      icon: code-bracket\n      link: /dhi/tools/api/\n---\n\nDocker Hardened Images can be accessed and managed through several interfaces.\nChoose the tool that fits your workflow.\n\n{{< grid items=\"grid_tools\" >}}\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/api.md) -->\n---\ntitle: Use the DHI API\nlinktitle: API\ndescription: Query Docker Hardened Images data programmatically using the DHI GraphQL API.\nweight: 50\nkeywords: dhi api, docker hardened images api, graphql api, dhi endpoint, dhi authentication\n---\n\nThe DHI API is a GraphQL API for querying Docker Hardened Images data\nprogrammatically, for use cases like building automation or dashboards on\ntop of DHI data.\n\n## Endpoint\n\nSend requests as `POST` requests to:\n\n```text\nhttps://api.dso.docker.com/v1/graphql\n```\n\n## Request format\n\nThe API accepts standard GraphQL requests: a JSON body with a `query` and,\noptionally, `variables`.\n\n```console\n$ curl https://api.dso.docker.com/v1/graphql \\\n  -H \"Authorization: Bearer <token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"...\", \"variables\": { ... }}'\n```\n\nEvery query takes a `Context` argument (conventionally named `ctx` in the\n`variables` object) alongside its query-specific arguments:\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `ctx` | `Context` | Yes | Scopes the request to an organization. |\n| `ctx.organization` | `String` | Yes | The Docker organization the token belongs to. |\n\n## Authentication\n\nAn [organization access token](/manuals/enterprise/security/access-tokens.md)\n(OAT) or personal access token (PAT) isn't used directly as the bearer\ntoken. Exchange it first for an access token:\n\n```console\n$ curl -X POST https://hub.docker.com/v2/auth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"identifier\": \"<identifier>\", \"secret\": \"<token>\"}'\n```\n\nFor `identifier`, use your Docker Hub username with a PAT, or the\norganization name with an OAT. The response contains the access token:\n\n```json\n{ \"access_token\": \"...\" }\n```\n\nPass that `access_token` as `Authorization: Bearer <access_token>`. Also set\n`ctx.organization` in `variables` to the organization the token belongs to\n(see [Request format](#request-format)).\n\n## Response format\n\nResponses follow the standard GraphQL envelope:\n\n| Key | Description |\n|---|---|\n| `data` | The requested fields. A field is `null` if it couldn't be resolved, for example due to an authorization failure. |\n| `errors` | Present when a field failed to resolve. Includes a `message` and a `path` identifying which field failed. |\n| `extensions` | Metadata such as a `correlation_id`, useful when reporting an issue. |\n\nFor example, an unauthenticated request, or a request for data your token\ncan't access, returns a `null` result under `data` alongside an authorization\nerror in `errors`, rather than an HTTP-level failure:\n\n```json\n{\n  \"errors\": [\n    {\n      \"message\": \"You are not allowed to read data for this team\",\n      \"path\": [\"someQuery\"],\n      \"extensions\": { \"code\": \"DOWNSTREAM_SERVICE_ERROR\", \"status\": 403 }\n    }\n  ],\n  \"data\": { \"someQuery\": null },\n  \"extensions\": { \"correlation_id\": \"...\" }\n}\n```\n\n## Queries\n\n### `imagePackagesForImageCoords`\n\nFetches every package in an image, every CVE reported against it, and\nwhether Docker suppresses that CVE, by digest. See [Query VEX for a Docker\nHardened Image](/manuals/dhi/how-to/vex-api.md) for a guided example.\n\n| Argument | Type | Required | Description |\n|---|---|---|---|\n| `digest` | `String` | Yes | The image's platform manifest digest, not the multi-arch index digest. |\n| `hostName` | `String` | Yes | `hub.docker.com` or `docker.io`. |\n| `repoName` | `String` | Yes | Repository name, with or without the namespace prefix. |\n| `includeExcepted` | `Boolean` | No | Include suppressed CVEs in the response alongside the reason for suppression. Without it, the response only shows the netted list, with no visibility into what was suppressed. |\n| `includeNodsa` | `Boolean` | No | Include Debian NODSA exclusions, which make up most suppressions on a Debian-based image. |\n| `includePublic` | `Boolean` | No | Also include public images when `ctx.organization` scopes the request to an organization. Not needed for a typical lookup. |\n\nKeep the requested response fields limited to what you plan to render.\nFields such as `locations`, `description`, `vulnerableRange`, and `epss`\nincrease response size substantially and aren't needed for a CVE-count or\nsuppressed-CVE view.\n\n#### Response fields\n\n`vulnerabilityExceptions` only contains records that actually suppress a\nCVE, so it always lines up with `isExcepted`: an empty array means the CVE\nis live. Use `isExcepted` as your filter for \"is this CVE suppressed.\"\n\n| Field | Meaning |\n|---|---|\n| `isExcepted` | Docker suppresses this CVE for this image. Use this to filter. |\n| `sourceType` | `EXTERNAL` (Debian NODSA), `MANUAL_EXCEPTION` (Docker analyst exception), or `VEX_STATEMENT` (an ingested VEX document). |\n| `type` | `FALSE_POSITIVE` and `ACCEPTED_RISK` suppress the CVE. `UNDER_INVESTIGATION` and `AFFECTED` don't. |\n| `justification` | The OpenVEX justification value. Always `null` for NODSA exclusions. |\n| `additionalDetails` | Free-text rationale for the suppression. |\n| `isDhiStatement` | Whether the statement is inherited from the DHI base image. |\n| `id` | Stable identifier for the statement. |\n\n#### Mapping to OpenVEX\n\nIf your pipeline consumes OpenVEX documents (for example, Trivy's `--vex`\nflag), each suppressed record maps as follows:\n\n| OpenVEX field | Source |\n|---|---|\n| `vulnerability.name` | `sourceId` |\n| `products[].@id` | The parent package's `purl` |\n| `status` | `not_affected` (from `type: FALSE_POSITIVE`) |\n| `justification` | `justification`, defaulting to `vulnerable_code_cannot_be_controlled_by_adversary` for NODSA exclusions |\n| `status_notes` | `additionalDetails` |\n| `@id` | `id` |\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/cli.md) -->\n---\ntitle: Use the DHI CLI\nlinkTitle: CLI\nweight: 20\nkeywords: docker dhi, CLI, command line, docker hardened images\ndescription: Learn how to install and use docker dhi, the command-line interface for managing Docker Hardened Images.\naliases:\n  - /dhi/how-to/cli/\n---\n\nThe `docker dhi` command-line interface (CLI) is a tool for managing Docker Hardened Images:\n- Browse the catalog of available DHI images and their metadata\n- View attestations for DHI images, including SBOMs and provenance\n- Mirror DHI images to your Docker Hub organization\n- Create and manage customizations of DHI images\n- Generate authentication for enterprise package repositories\n- Monitor customization builds\n\n## Installation\n\nThe `docker dhi` CLI is available in [Docker Desktop](https://docs.docker.com/desktop/) version 4.65 and later.\nYou can also install the standalone `dhictl` binary.\n\n### Docker Desktop\n\nThe `docker dhi` command is included in Docker Desktop 4.65 and later. No additional installation is required.\n\n### Standalone binary\n\n1. Download the `dhictl` binary for your platform from the\n   [releases](https://github.com/docker-hardened-images/dhictl/releases) page.\n2. Move it to a directory in your `PATH`:\n    - `mv dhictl /usr/local/bin/` on _Linux_ and _macOS_\n    - Move `dhictl.exe` to a directory in your `PATH` on _Windows_\n\n## Usage\n\nEvery command has built-in help accessible with the `--help` flag:\n\n```console\n$ docker dhi --help\n$ docker dhi catalog list --help\n```\n\n### Browse the DHI catalog\n\nList all available DHI images:\n\n```console\n$ docker dhi catalog list\n```\n\nFilter by type, name, or compliance:\n\n```console\n$ docker dhi catalog list --type image\n$ docker dhi catalog list --filter golang\n$ docker dhi catalog list --fips\n$ docker dhi catalog list --stig\n```\n\nGet details of a specific image, including available tags and CVE counts:\n\n```console\n$ docker dhi catalog get <image-name>\n```\n\n### View attestations\n\nList all attestations attached to a DHI image:\n\n```console\n$ docker dhi attestation list dhi/nginx:1.27\n$ docker dhi attestation list dhi/nginx:1.27 --platform linux/amd64\n$ docker dhi attestation list dhi/nginx:1.27 --predicate-type https://slsa.dev/provenance/v1\n$ docker dhi attestation list dhi/nginx:1.27 --json\n```\n\nGet a specific attestation by its referrer digest:\n\n```console\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest>\n$ docker dhi attestation get dhi/nginx:1.27 sha256:<digest> -o provenance.json\n```\n\nDisplay the SPDX SBOM for an image:\n\n```console\n$ docker dhi attestation sbom dhi/nginx:1.27\n$ docker dhi attestation sbom dhi/nginx:1.27 --platform linux/amd64\n```\n\n### Mirror DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nStart mirroring one or more DHI images to your Docker Hub organization:\n\n```console\n$ docker dhi mirror start --org my-org \\\n  dhi/golang,my-org/dhi-golang \\\n  dhi/nginx,my-org/dhi-nginx \\\n  dhi/prometheus-chart,my-org/dhi-prometheus-chart\n```\n\nMirror with dependencies:\n\n```console\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --dependencies\n```\n\nList mirrored images in your organization:\n\n```console\n$ docker dhi mirror list --org my-org\n```\n\nFilter mirrored images by name or type:\n\n```console\n$ docker dhi mirror list --org my-org --filter python\n$ docker dhi mirror list --org my-org --type image\n$ docker dhi mirror list --org my-org --type helm-chart\n```\n\nStop mirroring one or more images:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org\n$ docker dhi mirror stop dhi-python dhi-golang --org my-org\n```\n\nStop mirroring and delete the repositories:\n\n```console\n$ docker dhi mirror stop dhi-golang --org my-org --delete\n$ docker dhi mirror stop dhi-golang --org my-org --delete --force\n```\n\n### Customize DHI images\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nThe CLI can be used to create and manage DHI image customizations. For detailed\ninstructions on creating customizations using the GUI, see [Customize a Docker\nHardened Image](../how-to/customize.md).\n\nThe following is a quick reference for CLI commands. For complete details on all\noptions and flags, see the\n[CLI reference](/reference/cli/docker/dhi/).\n\n```console\n# Prepare a single customization scaffold\n$ docker dhi customization prepare golang 1.25 \\\n  --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\" \\\n  > my-customization.yaml\n\n# Prepare a bulk customization scaffold (pipe JSON array via stdin)\n$ echo '[{\"destination\":\"my-org/dhi-golang\",\"tag-definition-id\":\"golang/alpine-3.23/1.24-dev\"}]' \\\n  | docker dhi customization prepare --name \"golang with git\" --org my-org \\\n  > my-customization.yaml\n\n# Create a customization\n$ docker dhi customization create my-customization.yaml --org my-org\n\n# Create with flag overrides (flags take precedence over the YAML file)\n$ docker dhi customization create my-customization.yaml --org my-org \\\n  --destination my-org/dhi-golang \\\n  --name \"golang with git\"\n\n# List customizations\n$ docker dhi customization list --org my-org\n\n# Filter customizations by name, repository, or source\n$ docker dhi customization list --org my-org --filter git\n$ docker dhi customization list --org my-org --repo dhi-golang\n$ docker dhi customization list --org my-org --source golang\n\n# Get a customization by ID\n$ docker dhi customization get <id> --org my-org\n\n# Update a customization\n# The YAML file must include the 'id' field to identify the customization to update\n$ docker dhi customization edit my-customization.yaml --org my-org\n\n# Delete a customization by ID\n$ docker dhi customization delete <id> --org my-org\n\n# Delete multiple customizations\n$ docker dhi customization delete <id1> <id2> --org my-org\n\n# Delete without confirmation prompt\n$ docker dhi customization delete <id> --org my-org --force\n```\n\nFor a complete reference of all YAML fields, see\n[Image customization YAML file](/dhi/how-to/customize/#image-customization-yaml-file).\n\n### Enterprise package authentication\n\n{{< summary-bar feature_name=\"Docker Hardened Images Enterprise\" >}}\n\nGenerate authentication credentials for accessing the enterprise hardened\npackage repository. These credentials are used when configuring your package\nmanager to install compliance and security-patched packages in your own images. For detailed\ninstructions, see [Enterprise\nrepository](../how-to/hardened-packages.md#enterprise-repository).\n\nFor Alpine-based images:\n\n```console\n$ docker dhi auth apk\n```\n\nFor Debian-based images:\n\n```console\n$ docker dhi auth deb\n```\n\n### Monitor customization builds\n\n{{< summary-bar feature_name=\"Docker Hardened Images\" >}}\n\nList builds for a customization:\n\n```console\n$ docker dhi customization build list <customization-id> --org my-org\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\nGet details of a specific build:\n\n```console\n$ docker dhi customization build get <customization-id> <build-id> --org my-org\n$ docker dhi customization build get <customization-id> <build-id> --org my-org --json\n```\n\nView build logs:\n\n```console\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org\n$ docker dhi customization build logs <customization-id> <build-id> --org my-org --json\n```\n\n### JSON output\n\nMost list and get commands support a `--json` flag for machine-readable output:\n\n```console\n$ docker dhi catalog list --json\n$ docker dhi catalog get golang --json\n$ docker dhi attestation list dhi/nginx:1.27 --json\n$ docker dhi mirror list --org my-org --json\n$ docker dhi mirror start --org my-org dhi/golang,my-org/dhi-golang --json\n$ docker dhi customization list --org my-org --json\n$ docker dhi customization build list <customization-id> --org my-org --json\n```\n\n## Configuration\n\nThe `docker dhi` CLI can be configured with a YAML file located at:\n- `$HOME/.config/dhictl/config.yaml` on _Linux_ and _macOS_\n- `%USERPROFILE%\\.config\\dhictl\\config.yaml` on _Windows_\n\nIf `$XDG_CONFIG_HOME` is set, the configuration file is located at `$XDG_CONFIG_HOME/dhictl/config.yaml`.\n\nAvailable configuration options:\n\n| Option      | Environment Variable | Description                                                                                                               |\n|-------------|----------------------|---------------------------------------------------------------------------------------------------------------------------|\n| `org`       | `DHI_ORG`            | Default Docker Hub organization for mirror and customization commands.                                                    |\n| `api_token` | `DHI_API_TOKEN`      | Docker token for authentication. You can generate a token in your [Docker Hub account settings](https://hub.docker.com/). |\n\nEnvironment variables take precedence over configuration file values.\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/hub.md) -->\n---\ntitle: Use Docker Hub\nlinktitle: Docker Hub\ndescription: Browse the DHI catalog on Docker Hub to search repositories, inspect image metadata, and view SBOMs, CVEs, and attestations.\nweight: 10\nkeywords: docker hub dhi catalog, hardened images hub, dhi repository details, image variants hub\n---\n\nThe [Docker Hardened Images catalog](https://hub.docker.com/hardened-images/catalog)\non Docker Hub is the primary web interface for browsing, searching, and inspecting\nDHI repositories and their metadata.\n\n## Catalog page\n\nThe catalog lists all available DHI repositories. You can filter by name,\nimage type, or compliance requirements (FIPS, STIG) to find the image you need.\n\n## Repository details page\n\nWhen you select a repository from the catalog, the repository details page\nprovides the following:\n\n- Overview: A brief explanation of the image.\n- Guides: Several guides on how to use the image and migrate your existing application.\n- Images: Select this option to [view image variants](#images-page).\n- Security summary: Select a tag name to view a quick security summary,\n  including package count and total known vulnerabilities.\n- Recently pushed tags: A list of recently updated image variants and when they\n  were last updated.\n- Use this image: After selecting an image variant, you can select this option to\n  view instructions on how to pull and use the image variant, or select **Mirror\n  repository** to mirror it to your organization.\n\n## Images page\n\nFrom the repository details page, select **Images** to see all available image\nvariants for that repository. The table includes:\n\n- Image version: The image name with its base distribution (for example, `debian\n  13`) and associated tags.\n- Type: The support lifecycle status of the variant.\n- Compliance: Relevant compliance designations, for example `CIS`, `FIPS`, or\n  `STIG (100%)`.\n- Package manager: Whether a package manager is available. A checkmark indicates\n  a package manager is present (for example, `apt` or `apk`), a dash indicates\n  none.\n- Shell: Whether a shell is available. A checkmark indicates a shell is present\n  (for example, `bash` or `busybox`), a dash indicates none.\n- User: The user that the container runs as, for example `root` or `nonroot\n  (65532)`.\n- Last pushed: When the image variant was last updated.\n- Vulnerabilities: Vulnerability counts by severity level.\n\n## Image variant details page\n\nSelect an image version from the Images table to view detailed information about\nthat specific variant:\n\n- Packages: A list of all packages included in the image variant, with each\n  package's name, version, distribution, and licensing information.\n- Specifications:\n  - Source and build information: The Dockerfile and Git commit used to build the image.\n  - Build parameters, entrypoint, CMD, user, working directory, environment\n    variables, labels, and platform.\n- Vulnerabilities: A list of known CVEs for the image variant, including CVE ID,\n  severity, affected package, fix version, last detected date, status, and\n  suppressed CVEs.\n- Attestations: Signed security attestations covering the image's build process,\n  contents, and security posture. For the full list, see\n  [Attestations](/dhi/explore/security-concepts/attestations/).\n\n## Manage page\n\nThe Manage page (**My Hub** > **Hardened Images** > **Manage**) is the central\nplace for administering your organization's mirrored DHI repositories. It has\ntwo tabs:\n\n- Mirrored Images: Lists all image repositories currently mirrored to your\n  organization, with their source DHI repository, destination repository name,\n  and mirroring status. From here you can stop mirroring or open a repository's\n  settings.\n- Mirrored Helm charts: The same view for Helm chart repositories.\n\nSelecting a mirrored repository opens its settings, where you can enable or\ndisable Extended Lifecycle Support (ELS) and access customizations.\n\nFor step-by-step instructions, see [Mirror a Docker Hardened Image\nrepository](/dhi/how-to/mirror/).\n\n## Customizations\n\nCustomizations are accessible from **My Hub** > **Hardened Images** > **Manage** > **Mirrored Images**.\nSelect the menu icon next to a mirrored repository and\nthen **Customize**. Each customization defines\nadditional packages, OCI artifacts, environment variables, or labels to layer\nonto the base DHI during a rebuild.\n\nThe customizations view shows each customization's name, status, and last build\ntime. Selecting a customization opens its configuration, where you can edit the\ndefinition, trigger a rebuild, or delete it.\n\nFor step-by-step instructions, see [Customize a Docker Hardened\nImage](/dhi/how-to/customize/).\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/mcp.md) -->\n---\ntitle: Use the DHI MCP server\nlinktitle: MCP server\ndescription: Connect an AI assistant to the Docker Hardened Images catalog using the DHI MCP server to search repositories, inspect images, view SBOMs, and check CVEs.\nweight: 30\nkeywords: docker hardened images mcp, ai assistant dhi, mcp server docker, dhi catalog ai, claude cursor docker images, sbom mcp, cve mcp\naliases:\n  - /dhi/how-to/mcp/\n---\n\nThe Docker Hardened Images (DHI) MCP server exposes the DHI catalog through the\nModel Context Protocol (MCP), letting you query repositories, inspect image\nmetadata, retrieve SBOMs, and check CVEs directly from your AI assistant in\nplain language.\n\nThe MCP server is:\n\n- Remote. No local binary to install. Your AI assistant connects directly to\n  `https://dhi.io/mcp`.\n- Compatible with any MCP-capable AI assistant, including Claude,\n  Cursor, and others.\n\nMost tools are public and require no credentials. The mirror management tools\n(`dhi_list_mirrors`, `dhi_create_mirror`, `dhi_remove_mirror`) require a Docker\nHub username and personal access token (PAT) with owner access to the target\norganization. Credentials are passed as an HTTP Basic auth header in the MCP\nclient configuration — they are never passed as tool arguments.\n\n## Connect your AI assistant\n\nConfiguration varies by client. Select the tab for your AI assistant.\n\n{{< tabs >}}\n{{< tab name=\"Claude Desktop\" >}}\n\nAdd the following to your Claude Desktop configuration file:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\nThe configuration file is located at:\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n{{< /tab >}}\n{{< tab name=\"Cursor\" >}}\n\nAdd the following to `.cursor/mcp.json` in your project, or\n`~/.cursor/mcp.json` globally:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Claude Code\" >}}\n\nRun the following command to add the DHI MCP server:\n\n```console\n$ claude mcp add dhi --url https://dhi.io/mcp\n```\n\nOr add it manually to `.claude/mcp.json` in your project:\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\"\n    }\n  }\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Docker Agent\" >}}\n\nIn your [Docker Agent](/manuals/ai/docker-agent/_index.md) YAML configuration, add the\nDHI MCP server as a remote toolset:\n\n```yaml\ntoolsets:\n  - type: mcp\n    remote:\n      url: \"https://dhi.io/mcp\"\n      transport_type: streamable\n```\n\nFor example, to create an agent that can answer questions about the DHI catalog:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: DHI catalog assistant\n    instruction: |\n      Help me find and evaluate Docker Hardened Images.\n      Search the DHI catalog, inspect image details, check CVEs,\n      and retrieve SBOMs and attestations as needed.\n    toolsets:\n      - type: mcp\n        remote:\n          url: \"https://dhi.io/mcp\"\n          transport_type: streamable\n```\n\nRun the agent with:\n\n```console\n$ docker agent run dhi-agent.yaml\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Available tools\n\nThe DHI MCP server provides ten tools that your AI assistant calls automatically\nbased on what you ask:\n\n| Tool | What it does |\n|------|-------------|\n| `dhi_list_repositories` | Search and filter the DHI catalog by name, type, category, FIPS, or STIG compliance |\n| `dhi_get_repository` | Get full details for a repository: tag definitions, build config, platforms, and per-manifest vulnerability counts |\n| `dhi_get_tag_definition` | Get the deep view of a single tag definition |\n| `dhi_get_image_details` | Get per-digest details: tags, platform, size, layer and package counts, vulnerability severity counts, and attestation types |\n| `dhi_get_image_packages` | Retrieve the full software bill of materials (SBOM): package name, version, type, purl, licenses, and file locations |\n| `dhi_get_image_cves` | List CVEs with severity, CVSS score, fix version, EPSS score, and CISA-exploited flag; filter by minimum severity or fixable-only |\n| `dhi_get_image_attestations` | List SBOM, provenance, signature, and other attestations for a specific image digest |\n| `dhi_list_mirrors` | List mirrored DHI repositories for a Docker Hub organization — requires authentication |\n| `dhi_create_mirror` | Start mirroring a DHI repository into a Docker Hub organization — requires authentication |\n| `dhi_remove_mirror` | Stop mirroring a repository by its mirror ID — requires authentication |\n\n## Authenticate for mirror tools\n\nThe mirror tools require a Docker Hub username and [personal access token\n(PAT)](/security/access-tokens/) with owner access to the target organization,\npassed as an HTTP Basic auth header. Generate the value with:\n\n```console\n$ printf 'USERNAME:dckr_pat_...' | base64 | tr -d '\\n'\n```\n\nThen add it to your MCP client configuration:\n\n> [!WARNING]\n> Base64 encoding is not encryption. The value in your configuration file\n> is effectively a plaintext password. Do not commit this file to version\n> control or share it.\n\n```json\n{\n  \"mcpServers\": {\n    \"dhi\": {\n      \"url\": \"https://dhi.io/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Basic <base64-value>\"\n      }\n    }\n  }\n}\n```\n\nWithout credentials, the read-only catalog tools work normally and the mirror\ntools return an authentication error.\n\n## What the tools return\n\nEach tool returns structured data that your AI assistant can summarize,\ncompare, or act on:\n\n- `dhi_list_repositories` returns a list of repositories with display\n  name, distributions, platforms, FIPS/STIG flags, included tools, and category.\n- `dhi_get_repository` returns the full repository record, including all tag\n  definitions with their tags, build configuration, image indexes, and\n  per-platform manifest digests with vulnerability counts.\n- `dhi_get_tag_definition` returns tags, build parameters, entrypoint,\n  environment variables, run-as user, and per-platform manifests for a single\n  tag definition.\n- `dhi_get_image_details` returns the image platform, compressed size, layer\n  count, package count, vulnerability severity counts by level, labels, and\n  a list of attestation predicate types.\n- `dhi_get_image_packages` returns each package in the image with its name,\n  version, type (`deb`, `rpm`, `apk`, etc.), purl, licenses, and the file paths where\n  it was found.\n- `dhi_get_image_cves` returns each CVE affecting the image with its\n  severity, CVSS score and vector, affected package, fix version (if any), EPSS\n  probability score, and a flag indicating whether CISA lists it as\n  actively exploited.\n- `dhi_get_image_attestations` returns the predicate type and OCI reference\n  for each attestation attached to the image digest.\n- `dhi_list_mirrors` returns each mirror's ID, source DHI repository,\n  destination repository, and mirroring status for the given organization.\n- `dhi_create_mirror` starts mirroring a DHI source repository into the\n  specified organization and destination repository name.\n- `dhi_remove_mirror` stops mirroring for the given mirror ID. It does not\n  delete the destination repository — only stops new images from being synced.\n\n\n<!-- Skill/Rule: Tools Skill (content/manuals/dhi/tools/terraform.md) -->\n---\ntitle: Use the DHI Terraform provider\nlinktitle: Terraform\ndescription: Use the DHI Terraform provider to manage mirrors and customizations as infrastructure as code.\nweight: 40\nkeywords: dhi terraform, docker hardened images terraform, infrastructure as code, dhi mirror terraform, dhi provider\n---\n\nThe [DHI Terraform provider](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs)\nlets you manage Docker Hardened Image mirrors and customizations as\ninfrastructure as code.\n\n## Install and configure the provider\n\nAdd the provider to your Terraform configuration:\n\n```hcl\nterraform {\n  required_providers {\n    dhi = {\n      source = \"docker-hardened-images/dhi\"\n    }\n  }\n}\n\nprovider \"dhi\" {\n  docker_hub_username = var.docker_username\n  docker_hub_password = var.docker_password\n  organization        = var.org_name\n}\n```\n\nInstead of specifying credentials in the provider block, you can set environment\nvariables:\n\n| Variable | Description |\n|----------|-------------|\n| `DOCKER_USERNAME` | Docker Hub username or organization namespace |\n| `DOCKER_PASSWORD` | Docker Hub password or personal/organization access token |\n| `DHI_ORG` | Target organization namespace |\n\nYou can authenticate using a personal access token (PAT) or an organization\naccess token (OAT) in place of a password. When using an OAT, permission scopes\napply:\n\n- Read (pull) access is required to list mirrors.\n- Push access is required to create or delete mirrors.\n\n## Resources\n\n### `dhi_mirror`\n\nManages a mirrored DHI repository in your organization. See [Mirror a Docker\nHardened Image repository](/dhi/how-to/mirror/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/mirror).\n\n### `dhi_customization`\n\nManages image customizations applied to a mirrored repository. See [Customize a\nDocker Hardened Image](/dhi/how-to/customize/) for task-based examples.\n\nFor the full list of resource attributes, see the [Terraform Registry\ndocumentation](https://registry.terraform.io/providers/docker-hardened-images/dhi/latest/docs/resources/customization).\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/_index.md) -->\n---\ntitle: Docker Extensions\nweight: 60\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows\nparams:\n  sidebar:\n    group: Application development\naliases:\n - /desktop/extensions/\n---\n\nDocker Extensions let you use third-party tools within Docker Desktop to extend its functionality.\n\nYou can seamlessly connect your favorite development tools to your application development and deployment workflows. Augment Docker Desktop with debugging, testing, security, and networking functionalities, and create custom add-ons using the Extensions [SDK](extensions-sdk/_index.md).\n\nAnyone can use Docker Extensions and there is no limit to the number of extensions you can install.\n\n![Extensions Marketplace](/assets/images/extensions.webp)\n\n## What extensions are available?\n\nThere is a mix of partner and community-built extensions and Docker-built extensions.\nYou can explore the list of available extensions in [Docker Hub](https://hub.docker.com/search?q=&type=extension) or in the Extensions Marketplace within Docker Desktop.\n\n## Security and trust\n\nDocker Extensions run with elevated privileges on your host machine. They have direct access to the Docker Engine, can read and write files on your filesystem, and can install and run native binaries. \n\nDocker reviews extensions submitted to the Marketplace, but does not guarantee the security of any extension. Extensions installed outside the Marketplace have not been reviewed at all. Only install extensions from publishers you trust. \n\nIf you're an organization admin, see [Configure a private marketplace](private-marketplace.md) to control which extensions your team can install.\n\n<!-- Skill/Rule: Extensions-sdk Skill (content/manuals/extensions/extensions-sdk/_index.md) -->\n---\ntitle: Overview of the Extensions SDK\nlinkTitle: Extensions SDK\ndescription: Overall index for Docker Extensions SDK documentation\nkeywords: Docker, Extensions, sdk\naliases:\n - /desktop/extensions-sdk/dev/overview/\n - /desktop/extensions-sdk/\ngrid:\n  - title: \"The build and publish process\"\n    description: Understand the process for building and publishing an extension.\n    icon: clipboard-document-check\n    link: \"/extensions/extensions-sdk/process/\"\n  - title: \"Quickstart guide\"\n    description: Follow the quickstart guide to create a basic Docker extension quickly.\n    icon: magnifying-glass-plus\n    link: \"/extensions/extensions-sdk/quickstart/\"\n  - title: \"View the design guidelines\"\n    description: Ensure your extension aligns to Docker's design guidelines and principles.\n    icon: paint-brush\n    link: \"/extensions/extensions-sdk/design/design-guidelines/\"\n  - title: \"Publish your extension\"\n    description: Understand how to publish your extension to the Marketplace.\n    icon: arrow-up-tray\n    link: \"/extensions/extensions-sdk/extensions/\"\n  - title: \"Interacting with Kubernetes\"\n    description: Find information on how to interact indirectly with a Kubernetes cluster from your Docker extension.\n    icon: arrows-right-left\n    link: \"/extensions/extensions-sdk/guides/kubernetes/\"\n  - title: \"Multi-arch extensions\"\n    description: Build your extension for multiple architectures.\n    icon: document-duplicate\n    link: \"/extensions/extensions-sdk/extensions/multi-arch/\"\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nThe resources in this section help you create your own Docker extension.\n\nThe Docker CLI tool provides a set of commands to help you build and publish your extension, packaged as a \nspecially formatted Docker image.\n\nAt the root of the image filesystem is a `metadata.json` file which describes the content of the extension. \nIt's a fundamental element of a Docker extension.\n\nAn extension can contain a UI part and backend parts that run either on the host or in the Desktop virtual machine.\nFor further information, see [Architecture](architecture/_index.md).\n\nYou distribute extensions through Docker Hub. However, you can develop them locally without the need to push \nthe extension to Docker Hub. See [Extensions distribution](extensions/DISTRIBUTION.md) for further details.\n\n{{% include \"extensions-form.md\" %}}\n\n{{< grid >}}\n\n\n<!-- Skill/Rule: Architecture Skill (content/manuals/extensions/extensions-sdk/architecture/_index.md) -->\n---\ntitle: Extension architecture\nlinkTitle: Architecture\ndescription: Docker extension architecture\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/architecture/\nweight: 50\n---\n\nExtensions are applications that run inside the Docker Desktop. They're packaged as Docker images, distributed\nthrough Docker Hub, and installed by users either through the Marketplace within the Docker Desktop Dashboard or the\nDocker Extensions CLI.\n\nExtensions can be composed of three (optional) components:\n- A frontend (or User Interface): A web application displayed in a tab of the dashboard in Docker Desktop\n- A backend: One or many containerized services running in the Docker Desktop VM\n- Executables: Shell scripts or binaries that Docker Desktop copies on the host when installing the extension\n\n![Overview of the three components of an extension](images/extensions-architecture.png?w=600h=400)\n\nAn extension doesn't necessarily need to have all these components, but at least one of them depending on the extension features. \nTo configure and run those components, Docker Desktop uses a `metadata.json` file. See the\n[metadata](metadata) section for more details.\n\n## The frontend\n\nThe frontend is basically a web application made from HTML, Javascript, and CSS. It can be built with a simple HTML\nfile, some vanilla Javascript or any frontend framework, such as React or Vue.js.\n\nWhen Docker Desktop installs the extension, it extracts the UI folder from the extension image, as defined by the \n`ui` section in the `metadata.json`. See the [ui metadata section](metadata.md#ui-section) for more details.\n\nEvery time users click on the **Extensions** tab, Docker Desktop initializes the extension's UI as if it was the first time. When they navigate away from the tab, both the UI itself and all the sub-processes started by it (if any) are terminated.\n\nThe frontend can invoke `docker` commands, communicate with the extension backend, or invoke extension executables\ndeployed on the host, through the [Extensions SDK](https://www.npmjs.com/package/@docker/extension-api-client).\n\n> [!TIP]\n>\n> The `docker extension init` generates a React based extension. But you can still use it as a starting point for\n> your own extension and use any other frontend framework, like Vue, Angular, Svelte, etc. or event stay with\n> vanilla Javascript.\n\nLearn more about [building a frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) for your extension.\n\n## The backend\n\nAlongside a frontend application, extensions can also contain one or many backend services. In most cases, the Extension does not need a backend, and features can be implemented just by invoking docker commands through the SDK. However, there are some cases when an extension requires a backend\n\tservice, for example:\n- To run long-running processes that must outlive the frontend\n- To store data in a local database and serve them back with a REST API\n- To store the extension state, like when a button starts a long-running process, so that if you navigate away\n  from the extension and come back, the frontend can pick up where it left off\n- To access specific resources in the Docker Desktop VM, for example by mounting folders in the compose\nfile\n\n> [!TIP]\n>\n> The `docker extension init` generates a Go backend. But you can still use it as a starting point for\n> your own extension and use any other language like Node.js, Python, Java, .Net, or any other language and framework.\n\nUsually, the backend is made of one container that runs within the Docker Desktop VM. Internally, Docker Desktop creates\na Docker Compose project, creates the container from the `image` option of the `vm` section of the `metadata.json`, and\nattaches it to the Compose project. See the [`vm` metadata section](metadata.md#vm-section) for more details.\n\nIn some cases, a `compose.yaml` file can be used instead of an `image`. This is useful when the backend container\nneeds more specific options, such as mounting volumes or requesting [capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)\nthat can't be expressed just with a Docker image. The `compose.yaml` file can also be used to add multiple containers\nneeded by the extension, like a database or a message broker. \nNote that, if the Compose file defines many services, the SDK can only contact the first of them.\n\n> [!NOTE]\n>\n> In some cases, it is useful to also interact with the Docker engine from the backend.\n> See [How to use the Docker socket](../guides/use-docker-socket-from-backend.md) from the backend.\n\nTo communicate with the backend, the Extension SDK provides [functions](../dev/api/backend.md#get) to make `GET`,\n`POST`, `PUT`, `HEAD`, and `DELETE` requests from the frontend. Under the hood, the communication is done through a socket\nor named pipe, depending on the operating system. If the backend was listening to a port, it would be difficult to\nprevent collision with other applications running on the host or in a container already. Also, some users are\nrunning Docker Desktop in constrained environments where they can't open ports on their machines.\n\n![Backend and frontend communication](images/extensions-arch-2.png?w=500h=300)\n\nFinally, the backend can be built with any technology, as long as it can run in a container and listen on a socket.\n\nLearn more about [adding a backend](/manuals/extensions/extensions-sdk/build/backend-extension-tutorial.md) to your extension.\n\n## Executables\n\nIn addition to the frontend and the backend, extensions can also contain executables. Executables are binaries or shell scripts\nthat are installed on the host when the extension is installed. The frontend can invoke them with [the extension SDK](../dev/api/backend.md#invoke-an-extension-binary-on-the-host).\n\nThese executables are useful when the extension needs to interact with a third-party CLI tool, like AWS, `kubectl`, etc.\nShipping those executables with the extension ensure that the CLI tool is always available, at the right version, on\nthe users' machine.\n\nWhen Docker Desktop installs the extension, it copies the executables on the host as defined by the `host` section in\nthe `metadata.json`. See the [`host` metadata section](metadata.md#host-section) for more details.\n\n![Executable and frontend communication](images/extensions-arch-3.png?w=250h=300)\n\nHowever, since they're executed on the users' machine, they have to be available to the platform they're running on.\nFor example, if you want to ship the `kubectl` executable, you need to provide a different version for Windows, Mac,\nand Linux. Multi arch images will also need to include binaries built for the right arch (AMD / ARM)\n\n\nSee the [host metadata section](metadata.md#host-section) for more details.\n\nLearn how to [invoke host binaries](../guides/invoke-host-binaries.md).\n\n\n<!-- Skill/Rule: Architecture Skill (content/manuals/extensions/extensions-sdk/architecture/metadata.md) -->\n---\ntitle: Extension metadata\nlinkTitle: Metadata\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases:\n - /desktop/extensions-sdk/extensions/METADATA\n - /desktop/extensions-sdk/architecture/metadata/\n---\n\n## The metadata.json file\n\nThe `metadata.json` file is the entry point for your extension. It contains the metadata for your extension, such as the\nname, version, and description. It also contains the information needed to build and run your extension. The image for\na Docker extension must include a `metadata.json` file at the root of its filesystem.\n\nThe format of the `metadata.json` file must be:\n\n```json\n{\n    \"icon\": \"extension-icon.svg\",\n    \"ui\": ...\n    \"vm\": ...\n    \"host\": ...\n}\n```\n\nThe `ui`, `vm`, and `host` sections are optional and depend on what a given extension provides. They describe the extension content to be installed.\n\n### UI section\n\nThe `ui` section defines a new tab that's added to the dashboard in Docker Desktop. It follows the form:\n\n```json\n\"ui\":{\n    \"dashboard-tab\":\n    {\n        \"title\":\"MyTitle\",\n        \"root\":\"/ui\",\n        \"src\":\"index.html\"\n    }\n}\n```\n\n`root` specifies the folder where the UI code is within the extension image filesystem.\n`src` specifies the entrypoint that should be loaded in the extension tab.\n\nOther UI extension points will be available in the future.\n\n### VM section\n\nThe `vm` section defines a backend service that runs inside the Desktop VM. It must define either an `image` or a\n`compose.yaml` file that specifies what service to run in the Desktop VM.\n\n```json\n\"vm\": {\n    \"image\":\"${DESKTOP_PLUGIN_IMAGE}\"\n},\n```\n\nWhen you use `image`, a default compose file is generated for the extension.\n\n> `${DESKTOP_PLUGIN_IMAGE}` is a specific keyword that allows an easy way to refer to the image packaging the extension.\n> It is also possible to specify any other full image name here. However, in many cases using the same image makes\n> things easier for extension development.\n\n```json\n\"vm\": {\n    \"composefile\": \"compose.yaml\"\n},\n```\n\nThe Compose file, with a volume definition for example, would look like:\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /host/path:/container/path\n```\n\n### Host section\n\nThe `host` section defines executables that Docker Desktop copies on the host.\n\n```json\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/myBinary\"\n          },\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/myBinary.exe\"\n          },\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/myBinary\"\n          },\n        ]\n      }\n    ]\n  }\n```\n\n`binaries` defines a list of binaries Docker Desktop copies from the extension image to the host.\n\n`path` specifies the binary path in the image filesystem. Docker Desktop is responsible for copying these files in its own location, and the JavaScript API allows invokes these binaries.\n\nLearn how to [invoke executables](../guides/invoke-host-binaries.md).\n\n\n<!-- Skill/Rule: Architecture Skill (content/manuals/extensions/extensions-sdk/architecture/security.md) -->\n---\ntitle: Extension security\nlinkTitle: Security\ndescription: Aspects of the security model of extensions\nkeywords: Docker, extensions, sdk, security\naliases:\n - /desktop/extensions-sdk/guides/security/\n - /desktop/extensions-sdk/architecture/security/\n---\n\n## Extension capabilities\n\nAn extension can have the following optional parts: \n* A user interface in HTML or JavaScript, displayed in Docker Desktop Dashboard\n* A backend part that runs as a container\n* Executables deployed on the host machine.\n\nExtensions are executed with the same permissions as the Docker Desktop user. Extension capabilities include running any Docker commands (including running containers and mounting folders), running extension binaries, and accessing files on your machine that are accessible by the user running Docker Desktop.\nNote that extensions are not restricted to execute binaries that they list in the [host section](../architecture/metadata.md#host-section) of the extension metadata: since these binaries can contain any code running as user, they can in turn execute any other commands as long as the user has rights to execute them.\n\nThe Extensions SDK provides a set of JavaScript APIs to invoke commands or invoke these binaries from the extension UI code. Extensions can also provide a backend part that starts a long-lived running container in the background.\n\n> [!IMPORTANT]\n>\n> Make sure you trust the publisher or author of the extension when you install it, as the extension has the same access rights as the user running Docker Desktop.\n\n\n<!-- Skill/Rule: Design Skill (content/manuals/extensions/extensions-sdk/design/_index.md) -->\n---\ntitle: UI styling overview for Docker extensions\nlinkTitle: Design and UI styling\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases:\n - /desktop/extensions-sdk/design/design-overview/\n - /desktop/extensions-sdk/design/overview/\n - /desktop/extensions-sdk/design/\nweight: 60\n---\n\nOur Design System is a constantly evolving set of specifications that aim to ensure visual consistency across Docker products, and meet [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance). We've opened parts of it to extension authors, documenting basic styles (color, typography) and components. See: [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771).\n\nWe require extensions to match the wider Docker Desktop UI to a certain degree, and reserve the right to make this stricter in the future.\n\nTo get started on your UI, follow the steps below.\n\n## Step one: Choose your framework\n\n### Recommended: React+MUI, using our theme\n\nDocker Desktop's UI is written in React and [MUI](https://mui.com/) (using Material UI specifically). This is the only officially supported framework for building extensions, and the one that the `init` command automatically configures for you. Using it brings significant benefits to authors:\n\n- You can use our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme) to automatically replicate Docker Desktop's look and feel.\n- In future, we'll release utilities and components specifically targeting this combination (e.g. custom MUI components, or React hooks for interacting with Docker).\n\nRead our [MUI best practices](mui-best-practices.md) guide to learn future-proof ways to use MUI with Docker Desktop.\n\n### Not recommended: Some other framework\n\nYou may prefer to use another framework, perhaps because you or your team are more familiar with it or because you have existing assets you want to reuse. This is possible, but highly discouraged. It means that:\n\n- You'll need to manually replicate the look and feel of Docker Desktop. This takes a lot of effort, and if you don't match our theme closely enough, users will find your extension jarring and we may ask you to make changes during a review process.\n- You'll have a higher maintenance burden. Whenever Docker Desktop's theme changes (which could happen in any release), you'll need to manually change your extension to match it.\n- If your extension is open-source, deliberately avoiding common conventions will make it harder for the community to contribute to it.\n\n## Step two: Follow the below recommendations\n\n### Follow our MUI best practices (if applicable)\n\nSee our [MUI best practices](mui-best-practices.md) article.\n\n### Only use colors from our palette\n\nWith minor exceptions, displaying your logo for example, you should only use colors from our palette. These can be found in our [style guide document](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771), and will also soon be available in our MUI theme and via CSS variables.\n\n### Use counterpart colors in light/dark mode\n\nOur colors have been chosen so that the counterpart colors in each variant of the palette should have the same essential characteristics. Anywhere you use `red-300` in light mode, you should use `red-300` in dark mode too.\n\n## What's next?\n\n- Take a look at our [MUI best practices](mui-best-practices.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n\n\n<!-- Skill/Rule: Design Skill (content/manuals/extensions/extensions-sdk/design/design-guidelines.md) -->\n---\ntitle: Design guidelines for Docker extensions\nlinkTitle: Guidelines\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-guidelines/\nweight: 10\n---\n\nAt Docker, we aim to build tools that integrate into a user's existing workflows rather than requiring them to adopt new ones. We strongly recommend that you follow these guidelines when creating extensions. We review and approve your Marketplace publication based on these requirements.\n\nHere is a simple checklist to go through when creating your extension:\n- Is it easy to get started?\n- Is it easy to use?\n- Is it easy to get help when needed?\n\n\n## Create a consistent experience with Docker Desktop\n\nUse the [Docker Material UI Theme](https://www.npmjs.com/package/@docker/docker-mui-theme) and the [Docker Extensions Styleguide](https://www.figma.com/file/U7pLWfEf6IQKUHLhdateBI/Docker-Design-Guidelines?node-id=1%3A28771) to ensure that your extension feels like it is part of Docker Desktop to create a seamless experience for users.\n\n- Ensure the extension has both a light and dark theme. Using the components and styles as per the Docker style guide ensures that your extension meets the [level AA accessibility standard.](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n  ![Light and dark mode](images/light_dark_mode.webp)\n\n- Ensure that your extension icon is visible both in light and dark mode.\n\n  ![Icon colors in light and dark mode](images/icon_colors.webp)\n\n- Ensure that the navigational behavior is consistent with the rest of Docker Desktop. Add a header to set the context for the extension.\n\n  ![Header that sets the context](images/header.webp)\n\n- Avoid embedding terminal windows. The advantage we have with Docker Desktop over the CLI is that we have the opportunity to provide rich information to users. Make use of this interface as much as possible. \n\n  ![Terminal window used incorrectly](images/terminal_window_dont.webp)\n\n  ![Terminal window used correctly](images/terminal_window_do.webp)\n\n## Build features natively\n\n- In order not to disrupt the flow of users, avoid scenarios where the user has to navigate outside Docker Desktop, to the CLI or a webpage for example, in order to carry out certain functionalities. Instead, build features that are native to Docker Desktop.\n\n  ![Incorrect way to switch context](images/switch_context_dont.webp)\n\n  ![Correct way to switch context](images/switch_context_do.webp)\n\n## Break down complicated user flows\n\n- If a flow is too complicated or the concept is abstract, break down the flow into multiple steps with one simple call-to-action in each step. This helps when onboarding novice users to your extension\n\n  ![A complicated flow](images/complicated_flows.webp)\n\n- Where there are multiple call-to-actions, ensure you use the primary (filled button style) and secondary buttons (outline button style) to convey the importance of each action.\n\n  ![Call to action](images/cta.webp)\n\n## Onboarding new users\n\nWhen creating your extension, ensure that first time users of the extension and your product can understand its value-add and adopt it easily. Ensure you include contextual help within the extension.\n\n- Ensure that all necessary information is added to the extensions Marketplace as well as the extensions detail page. This should include:\n  - Screenshots of the extension. Note that the recommended size for screenshots is 2400x1600 pixels. \n  - A detailed description that covers what the purpose of the extension is, who would find it useful and how it works.\n  - Link to necessary resources such as documentation.\n- If your extension has particularly complex functionality, add a demo or video to the start page. This helps onboard a first time user quickly.\n\n  ![start page](images/start_page.webp)\n\n## What's next?\n\n- Explore our [design principles](design-principles.md).\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n\n\n<!-- Skill/Rule: Design Skill (content/manuals/extensions/extensions-sdk/design/design-principles.md) -->\n---\ntitle: Docker design principles\ndescription: Docker extension design\nkeywords: Docker, extensions, design\naliases: \n - /desktop/extensions-sdk/design/design-principles/\nweight: 20\n---\n\n## Provide actionable guidance\n\nWe anticipate needs and provide simple explanations with clear actions so people are never lost and always know what to do next. Recommendations lead users to functionality that enhances the experience and extends their knowledge.\n\n## Create value through confidence\n\nPeople from all levels of experience should feel they know how to use our product. Experiences are familiar, unified, and easy to use so all users feel like experts.\n\n## Infuse productivity with delight\n\nWe seek out moments of purposeful delight that elevate rather than distract, making work easier and more gratifying. Simple tasks are automated and users are left with more time for innovation.\n\n## Build trust through transparency\n\nWe always provide clarity on what is happening and why. No amount of detail is withheld; the right information is shown at the right time and is always accessible.\n\n## Scale with intention\n\nOur products focus on inclusive growth and are continuously useful and adapt to match changing individual needs. We support all levels of expertise by meeting users where they are with conscious personalization.\n\n## What's next?\n\n- Take a look at our [UI styling guidelines](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n\n\n<!-- Skill/Rule: Design Skill (content/manuals/extensions/extensions-sdk/design/mui-best-practices.md) -->\n---\ntitle: MUI best practices\ndescription: Guidelines for using MUI to maximize compatibility with Docker Desktop\nkeywords: Docker, extensions, mui, theme, theming, material-ui, material\naliases: \n - /desktop/extensions-sdk/design/mui-best-practices/\n---\n\nThis article assumes you're following our recommended practice by using our [Material UI theme](https://www.npmjs.com/package/@docker/docker-mui-theme).\nFollowing the steps below maximizes compatibility with Docker Desktop and minimizes the work you need to do as an\nextension author. They should be considered supplementary to the non-MUI-specific guidelines found in the\n[UI Styling overview](index.md).\n\n## Assume the theme can change at any time\n\nResist the temptation to fine-tune your UI with precise colors, offsets and font sizings to make it look as attractive as possible. Any specializations you make today will be relative to the current MUI theme, and may look worse when the theme changes. Any part of the theme might change without warning, including (but not limited to):\n\n-  The font, or font sizes\n-  Border thicknesses or styles\n-  Colors:\n   -  Our palette members (e.g. `red-100`) could change their RGB values\n   -  The semantic colors (e.g. `error`, `primary`, `textPrimary`, etc) could be changed to use a different member of our palette\n   -  Background colors (e.g. those of the page, or of dialogs) could change\n-  Spacings:\n   -  The size of the basic unit of spacing,(exposed via `theme.spacing`. For instance, we may allow users to customize the density of the UI\n   -  The default spacing between paragraphs or grid items\n\nThe best way to build your UI, so that it’s robust against future theming changes, is to:\n\n-  Override the default styling as little as possible.\n-  Use semantic typography. e.g. use `Typography`s or `Link`s with appropriate `variant`s instead of using typographical HTML elements (`<a>`, `<p>`, `<h1>`, etc) directly.\n-  Use canned sizes. e.g. use `size=\"small\"` on buttons, or `fontSize=\"small\"` on icons, instead of specifying sizes in pixels.\n-  Prefer semantic colors. e.g. use `error` or `primary` over explicit color codes.\n-  Write as little CSS as possible. Write semantic markup instead. For example, if you want to space out paragraphs of text, use the `paragraph` prop on your `Typography` instances. If you want to space out something else, use a `Stack` or `Grid` with the default spacing.\n-  Use visual idioms you’ve seen in the Docker Desktop UI, since these are the main ones we’ll test any theme changes against.\n\n## When you go custom, centralize it\n\nSometimes you’ll need a piece of UI that doesn’t exist in our design system. If so, we recommend that you first reach out to us. We may already have something in our internal design system, or we may be able to expand our design system to accommodate your use case.\n\nIf you still decide to build it yourself after contacting us, try and define the new UI in a reusable fashion. If you define your custom UI in just one place, it’ll make it easier to change in the future if our core theme changes. You could use:\n\n-  A new `variant` of an existing component - see [MUI docs](https://mui.com/material-ui/customization/theme-components/#creating-new-component-variants)\n-  A MUI mixin (a freeform bundle of reusable styling rules defined inside a theme)\n-  A new [reusable component](https://mui.com/material-ui/customization/how-to-customize/#2-reusable-component)\n\nSome of the above options require you to extend our MUI theme. See the MUI documentation on [theme composition](https://mui.com/material-ui/customization/theming/#nesting-the-theme).\n\n## What's next?\n\n- Take a look at our [UI styling guide](index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n\n\n<!-- Skill/Rule: Dev Skill (content/manuals/extensions/extensions-sdk/dev/_index.md) -->\n---\nbuild:\n  render: never\ntitle: Developer SDK tools\n---\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/_index.md) -->\n---\nbuild:\n  render: never\ntitle: Extension APIs\n---\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/backend.md) -->\n---\ntitle: Extension Backend\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/backend/\n---\n\nThe `ddClient.extension.vm` object can be used to communicate with the backend defined in the [vm section](../../architecture/metadata.md#vm-section) of the extension metadata.\n\n## get\n\n▸ **get**(`url`): `Promise`<`unknown`\\>\n\nPerforms an HTTP GET request to a backend service.\n\n```typescript\nddClient.extension.vm.service\n .get(\"/some/service\")\n .then((value: any) => console.log(value)\n```\n\nSee [Service API Reference](/reference/api/extensions-sdk/HttpService.md) for other HTTP methods.\n\n> Deprecated extension backend communication\n>\n> The methods below that use `window.ddClient.backend` are deprecated and will be removed in a future version. Use the methods specified above.\n\nThe `window.ddClient.backend` object can be used to communicate with the backend\ndefined in the [vm section](../../architecture/metadata.md#vm-section) of the\nextension metadata. The client is already connected to the backend.\n\nExample usages:\n\n```typescript\nwindow.ddClient.backend\n  .get(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .post(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .put(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .patch(\"/some/service\", { ... })\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .delete(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .head(\"/some/service\")\n  .then((value: any) => console.log(value));\n\nwindow.ddClient.backend\n  .request({ url: \"/url\", method: \"GET\", headers: { 'header-key': 'header-value' }, data: { ... }})\n  .then((value: any) => console.log(value));\n```\n\n## Run a command in the extension backend container\n\nFor example, execute the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"]);\n```\n\nStream the output of the command executed in the backend container. For example, spawn the command `ls -l` inside the backend container:\n\n```typescript\nawait ddClient.extension.vm.cli.exec(\"ls\", [\"-l\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nFor more details, refer to the [Extension VM API Reference](/reference/api/extensions-sdk/ExtensionVM.md)\n\n> Deprecated extension backend command execution\n>\n> This method is deprecated and will be removed in a future version. Use the specified method above.\n\nIf your extension ships with additional binaries that should be run inside the\nbackend container, you can use the `execInVMExtension` function:\n\n```typescript\nconst output = await window.ddClient.backend.execInVMExtension(\n  `cliShippedInTheVm xxx`\n);\nconsole.log(output);\n```\n\n## Invoke an extension binary on the host\n\nInvoke a binary on the host. The binary is typically shipped with your extension using the [host section](../../architecture/metadata.md#host-section) in the extension metadata. Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nFor example, execute the shipped binary `kubectl -h` command in the host:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"]);\n```\n\nAs long as the `kubectl` binary is shipped as part of your extension, you can spawn the `kubectl -h` command in the host and get the output stream:\n\n```typescript\nawait ddClient.extension.host.cli.exec(\"kubectl\", [\"-h\"], {\n  stream: {\n    onOutput(data: { stdout: string } | { stderr: string }): void {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error: any): void {\n      console.error(error);\n    },\n    onClose(exitCode: number): void {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n  },\n});\n```\n\nYou can stream the output of the command executed in the backend container or in the host.\n\nFor more details, refer to the [Extension Host API Reference](/reference/api/extensions-sdk/ExtensionHost.md)\n\n> Deprecated invocation of extension binary\n>\n> This method is deprecated and will be removed in a future version. Use the method specified above.\n\nTo execute a command in the host:\n\n```typescript\nwindow.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((cmdResult: any) => {\n  console.log(cmdResult);\n});\n```\n\nTo stream the output of the command executed in the backend container or in the host:\n\n```typescript\nwindow.ddClient.spawnHostCmd(\n  `cliShippedOnHost`,\n  [`arg1`, `arg2`],\n  (data: any, err: any) => {\n    console.log(data.stdout, data.stderr);\n    // Once the command exits we get the status code\n    if (data.code) {\n      console.log(data.code);\n    }\n  }\n);\n```\n\n> [!NOTE]\n> \n>You cannot use this to chain commands in a single `exec()` invocation (like `cmd1 $(cmd2)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/dashboard-routes-navigation.md) -->\n---\ntitle: Navigation\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard-routes-navigation/\n---\n\n`ddClient.desktopUI.navigate` enables navigation to specific screens of Docker Desktop such as the containers tab, the images tab, or a specific container's logs.\n\nFor example, navigate to a given container logs:\n\n```typescript\nconst id = '8c7881e6a107';\ntry {\n  await ddClient.desktopUI.navigate.viewContainerLogs(id);\n} catch (e) {\n  console.error(e);\n  ddClient.desktopUI.toast.error(\n    `Failed to navigate to logs for container \"${id}\".`\n  );\n}\n```\n\n#### Parameters\n\n| Name | Type     | Description                                                                                                                                                                                            |\n| :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id` | `string` | The full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`. You can use the `--no-trunc` flag as part of the `docker ps` command to display the full container id. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\nA promise that fails if the container doesn't exist.\n\nFor more details about all navigation methods, see the [Navigation API reference](/reference/api/extensions-sdk/NavigationIntents.md).\n\n> Deprecated navigation methods\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.navigateToContainers();\n// id - the full container id, e.g. `46b57e400d801762e9e115734bf902a2450d89669d85881058a46136520aca28`\nwindow.ddClient.navigateToContainer(id);\nwindow.ddClient.navigateToContainerLogs(id);\nwindow.ddClient.navigateToContainerInspect(id);\nwindow.ddClient.navigateToContainerStats(id);\n\nwindow.ddClient.navigateToImages();\nwindow.ddClient.navigateToImage(id, tag);\n\nwindow.ddClient.navigateToVolumes();\nwindow.ddClient.navigateToVolume(volume);\n\nwindow.ddClient.navigateToDevEnvironments();\n```\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/dashboard.md) -->\n---\ntitle: Dashboard\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases:\n - /desktop/extensions-sdk/dev/api/dashboard/\n---\n\n## User notifications\n\nToasts provide a brief notification to the user. They appear temporarily and\nshouldn't interrupt the user experience. They also don't require user input to disappear.\n\n### success\n\n▸ **success**(`msg`): `void`\n\nUse to display a toast message of type success.\n\n```typescript\nddClient.desktopUI.toast.success(\"message\");\n```\n\n### warning\n\n▸ **warning**(`msg`): `void`\n\nUse to display a toast message of type warning.\n\n```typescript\nddClient.desktopUI.toast.warning(\"message\");\n```\n\n### error\n\n▸ **error**(`msg`): `void`\n\nUse to display a toast message of type error.\n\n```typescript\nddClient.desktopUI.toast.error(\"message\");\n```\n\nFor more details about method parameters and the return types available, see [Toast API reference](/reference/api/extensions-sdk/Toast.md).\n\n> Deprecated user notifications\n>\n> These methods are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.toastSuccess(\"message\");\nwindow.ddClient.toastWarning(\"message\");\nwindow.ddClient.toastError(\"message\");\n```\n\n## Open a file selection dialog\n\nThis function opens a file selector dialog that asks the user to select a file or folder.\n\n▸ **showOpenDialog**(`dialogProperties`): `Promise`<[`OpenDialogResult`](/reference/api/extensions-sdk/OpenDialogResult.md)\\>:\n\nThe `dialogProperties` parameter is a list of flags passed to Electron to customize the dialog's behaviour. For example, you can pass `multiSelections` to allow a user to select multiple files. See [Electron's documentation](https://www.electronjs.org/docs/latest/api/dialog) for a full list.\n\n```typescript\nconst result = await ddClient.desktopUI.dialog.showOpenDialog({\n  properties: [\"openDirectory\"],\n});\nif (!result.canceled) {\n  console.log(result.paths);\n}\n```\n\n## Open a URL\n\nThis function opens an external URL with the system default browser.\n\n▸ **openExternal**(`url`): `void`\n\n```typescript\nddClient.host.openExternal(\"https://docker.com\");\n```\n\n> The URL must have the protocol `http` or `https`.\n\nFor more details about method parameters and the return types available, see [Desktop host API reference](/reference/api/extensions-sdk/Host.md).\n\n> Deprecated external URL opening\n>\n> This method is deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nwindow.ddClient.openExternal(\"https://docker.com\");\n```\n\n## Navigation to Dashboard routes\n\nFrom your extension, you can also [navigate](dashboard-routes-navigation.md) to other parts of the Docker Desktop Dashboard.\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/docker.md) -->\n---\ntitle: Docker\ndescription: Docker extension API\nkeywords: Docker, extensions, sdk, API\naliases: \n - /desktop/extensions-sdk/dev/api/docker/\n---\n\n## Docker objects\n\n▸ **listContainers**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of containers:\n\n```typescript\nconst containers = await ddClient.docker.listContainers();\n```\n\n▸ **listImages**(`options?`): `Promise`<`unknown`\\>\n\nTo get the list of local container images:\n\n```typescript\nconst images = await ddClient.docker.listImages();\n```\n\nSee the [Docker API reference](/reference/api/extensions-sdk/Docker.md) for details about these methods.\n\n> Deprecated access to Docker objects\n>\n> The methods below are deprecated and will be removed in a future version. Use the methods specified above.\n\n```typescript\nconst containers = await window.ddClient.listContainers();\n\nconst images = await window.ddClient.listImages();\n```\n\n## Docker commands\n\nExtensions can also directly execute the `docker` command line.\n\n▸ **exec**(`cmd`, `args`): `Promise`<[`ExecResult`](/reference/api/extensions-sdk/ExecResult.md)\\>\n\n```typescript\nconst result = await ddClient.docker.cli.exec(\"info\", [\n  \"--format\",\n  '\"{{ json . }}\"',\n]);\n```\n\nThe result contains both the standard output and the standard error of the executed command:\n\n```json\n{\n  \"stderr\": \"...\",\n  \"stdout\": \"...\"\n}\n```\n\nIn this example, the command output is JSON.\nFor convenience, the command result object also has methods to easily parse it:\n\n- `result.lines(): string[]` splits output lines.\n- `result.parseJsonObject(): any` parses a well-formed json output.\n- `result.parseJsonLines(): any[]` parses each output line as a json object.\n\n▸ **exec**(`cmd`, `args`, `options`): `void`\n\nThe command above streams the output as a result of the execution of a Docker command.\nThis is useful if you need to get the output as a stream or the output of the command is too long.\n\n```typescript\nawait ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    onOutput(data) {\n      if (data.stdout) {\n        console.error(data.stdout);\n      } else {\n        console.log(data.stderr);\n      }\n    },\n    onError(error) {\n      console.error(error);\n    },\n    onClose(exitCode) {\n      console.log(\"onClose with exit code \" + exitCode);\n    },\n    splitOutputLines: true,\n  },\n});\n```\n\nThe child process created by the extension is killed (`SIGTERM`) automatically when you close the dashboard in Docker Desktop or when you exit the extension UI.\nIf needed, you can also use the result of the `exec(streamOptions)` call in order to kill (`SIGTERM`) the process.\n\n```typescript\nconst logListener = await ddClient.docker.cli.exec(\"logs\", [\"-f\", \"...\"], {\n  stream: {\n    // ...\n  },\n});\n\n// when done listening to logs or before starting a new one, kill the process\nlogListener.close();\n```\n\nThis `exec(streamOptions)` API can also be used to listen to docker events:\n\n```typescript\nawait ddClient.docker.cli.exec(\n  \"events\",\n  [\"--format\", \"{{ json . }}\", \"--filter\", \"container=my-container\"],\n  {\n    stream: {\n      onOutput(data) {\n        if (data.stdout) {\n          const event = JSON.parse(data.stdout);\n          console.log(event);\n        } else {\n          console.log(data.stderr);\n        }\n      },\n      onClose(exitCode) {\n        console.log(\"onClose with exit code \" + exitCode);\n      },\n      splitOutputLines: true,\n    },\n  }\n);\n```\n\n> [!NOTE]\n>\n>You cannot use this to chain commands in a single `exec()` invocation (like `docker kill $(docker ps -q)` or using pipe between commands).\n>\n> You need to invoke `exec()` for each command and parse results to pass parameters to the next command if needed.\n\nSee the [Exec API reference](/reference/api/extensions-sdk/Exec.md) for details about these methods.\n\n> Deprecated execution of Docker commands\n>\n> This method is deprecated and will be removed in a future version. Use the one specified just below.\n\n```typescript\nconst output = await window.ddClient.execDockerCmd(\n  \"info\",\n  \"--format\",\n  '\"{{ json . }}\"'\n);\n\nwindow.ddClient.spawnDockerCmd(\"logs\", [\"-f\", \"...\"], (data, error) => {\n  console.log(data.stdout);\n});\n```\n\n\n<!-- Skill/Rule: Api Skill (content/manuals/extensions/extensions-sdk/dev/api/overview.md) -->\n---\ntitle: Extension UI API\ndescription: Docker extension development overview\nkeywords: Docker, extensions, sdk, development\naliases:\n - /desktop/extensions-sdk/dev/api/overview/\n---\n\nThe extensions UI runs in a sandboxed environment and doesn't have access to any\nelectron or nodejs APIs.\n\nThe extension UI API provides a way for the frontend to perform different actions\nand communicate with the Docker Desktop dashboard or the underlying system.\n\nJavaScript API libraries, with Typescript support, are available in order to get all the API definitions in to your extension code.\n\n- [@docker/extension-api-client](https://www.npmjs.com/package/@docker/extension-api-client) gives access to the extension API entrypoint `DockerDesktopClient`.\n- [@docker/extension-api-client-types](https://www.npmjs.com/package/@docker/extension-api-client-types) can be added as a dev dependency in order to get types auto-completion in your IDE.\n\n```Typescript\nimport { createDockerDesktopClient } from '@docker/extension-api-client';\n\nexport function App() {\n  // obtain Docker Desktop client\n  const ddClient = createDockerDesktopClient();\n  // use ddClient to perform extension actions\n}\n```\n\nThe `ddClient` object gives access to various APIs:\n\n- [Extension Backend](backend.md)\n- [Docker](docker.md)\n- [Dashboard](dashboard.md)\n- [Navigation](dashboard-routes-navigation.md)\n\nSee also the [Extensions API reference](/reference/api/extensions-sdk/_index.md).\n\n\n<!-- Skill/Rule: Dev Skill (content/manuals/extensions/extensions-sdk/dev/continuous-integration.md) -->\n---\ntitle: Continuous Integration (CI)\ndescription: Automatically test and validate your extension.\nkeywords: Docker, Extensions, sdk, CI, test, regression\naliases: \n - /desktop/extensions-sdk/dev/continuous-integration/\nweight: 20\n---\n\nIn order to help validate your extension and ensure it's functional, the Extension SDK provides tools to help you setup continuous integration for your extension.\n\n> [!IMPORTANT]\n>\n> The [Docker Desktop Action](https://github.com/docker/desktop-action) and the [extension-test-helper library](https://www.npmjs.com/package/@docker/extension-test-helper) are both [experimental](https://docs.docker.com/release-lifecycle/#experimental).\n\n## Setup CI environment with GitHub Actions\n\nYou need Docker Desktop to be able to install and validate your extension.\nYou can start Docker Desktop in GitHub Actions using the [Docker Desktop Action](https://github.com/docker/desktop-action), by adding the following to a workflow file:\n\n```yaml\nsteps:\n  - id: start_desktop\n    uses: docker/desktop-action/start@v0.1.0\n```\n\n> [!NOTE]\n>\n> This action supports only GitHub Actions macOS runners at the moment. You need to specify `runs-on: macOS-latest` for your end to end tests.\n\nOnce the step has executed, the next steps use Docker Desktop and the Docker CLI to install and test the extension.\n\n## Validating your extension with Puppeteer\n\nOnce Docker Desktop starts in CI, you can build, install, and validate your extension with Jest and Puppeteer.\n\nFirst, build and install the extension from your test:\n\n```ts\nimport { DesktopUI } from \"@docker/extension-test-helper\";\nimport { exec as originalExec } from \"child_process\";\nimport * as util from \"util\";\n\nexport const exec = util.promisify(originalExec);\n\n// keep a handle on the app to stop it at the end of tests\nlet dashboard: DesktopUI;\n\nbeforeAll(async () => {\n  await exec(`docker build -t my/extension:latest .`, {\n    cwd: \"my-extension-src-root\",\n  });\n\n  await exec(`docker extension install -f my/extension:latest`);\n});\n```\n\nThen open the Docker Desktop Dashboard and run some tests in your extension's UI:\n\n```ts\ndescribe(\"Test my extension\", () => {\n  test(\"should be functional\", async () => {\n    dashboard = await DesktopUI.start();\n\n    const eFrame = await dashboard.navigateToExtension(\"my/extension\");\n\n    // use puppeteer APIs to manipulate the UI, click on buttons, expect visual display and validate your extension\n    await eFrame.waitForSelector(\"#someElementId\");\n  });\n});\n```\n\nFinally, close the Docker Desktop Dashboard and uninstall your extension:\n\n```ts\nafterAll(async () => {\n  dashboard?.stop();\n  await exec(`docker extension uninstall my/extension`);\n});\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Learn how to [publish your extension](../extensions/_index.md).\n\n\n<!-- Skill/Rule: Dev Skill (content/manuals/extensions/extensions-sdk/dev/test-debug.md) -->\n---\ntitle: Test and debug\ndescription: Test and debug your extension.\nkeywords: Docker, Extensions, sdk, preview, update, Chrome DevTools\naliases:\n - /desktop/extensions-sdk/build/test-debug/\n - /desktop/extensions-sdk/dev/test-debug/\nweight: 10\n---\n\nIn order to improve the developer experience, Docker Desktop provides a set of tools to help you test and debug your extension.\n\n### Open Chrome DevTools\n\nIn order to open the Chrome DevTools for your extension when you select the **Extensions** tab, run:\n\n```console\n$ docker extension dev debug <name-of-your-extensions>\n```\n\nEach subsequent click on the extension tab also opens Chrome DevTools. To stop this behaviour, run:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\nAfter an extension is deployed, it is also possible to open Chrome DevTools from the UI extension part using a variation of the [Konami Code](https://en.wikipedia.org/wiki/Konami_Code). Select the **Extensions** tab, and then hit the key sequence `up, up, down, down, left, right, left, right, p, d, t`.\n\n### Hot reloading whilst developing the UI\n\nDuring UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\nextension. To do this, you can configure Docker Desktop to load your UI from a development server, such as the one\n[Vite](https://vitejs.dev/) starts when invoked with `npm start`.\n\nAssuming your app runs on the default port, start your UI app and then run:\n\n```console\n$ cd ui\n$ npm run dev\n```\n\nThis starts a development server that listens on port 3000.\n\nYou can now tell Docker Desktop to use this as the frontend source. In another terminal run:\n\n```console\n$ docker extension dev ui-source <name-of-your-extensions> http://localhost:3000\n```\n\nClose and reopen the Docker Desktop dashboard and go to your extension. All the changes to the frontend code are immediately visible.\n\nOnce finished, you can reset the extension configuration to the original settings. This will also reset opening Chrome DevTools if you used `docker extension dev debug <name-of-your-extensions>`:\n\n```console\n$ docker extension dev reset <name-of-your-extensions>\n```\n\n## Show the extension containers\n\nIf your extension is composed of one or more services running as containers in the Docker Desktop VM, you can access them easily from the dashboard in Docker Desktop.\n\n1. In Docker Desktop, navigate to **Settings**.\n2. Under the **Extensions** tab, select the **Show Docker Desktop Extensions system containers** option. You can now view your extension containers and their logs.\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build an [advanced frontend](/manuals/extensions/extensions-sdk/build/frontend-extension-tutorial.md) extension.\n- Learn more about extensions [architecture](../architecture/_index.md).\n- Explore our [design principles](../design/design-principles.md).\n- Take a look at our [UI styling guidelines](../design/_index.md).\n- Learn how to [setup CI for your extension](continuous-integration.md).\n\n\n<!-- Skill/Rule: Dev Skill (content/manuals/extensions/extensions-sdk/dev/usage.md) -->\n---\ntitle: CLI reference\ndescription: Docker extension CLI\nkeywords: Docker, extensions, sdk, CLI\naliases:\n - /desktop/extensions-sdk/dev/cli/usage/\n - /desktop/extensions-sdk/dev/usage/\nweight: 30\n---\n\nThe Extensions CLI is an extension development tool that is used to manage Docker extensions. Actions include install, list, remove, and validate extensions.\n\n- `docker extension enable` turns on Docker extensions.\n- `docker extension dev` commands for extension development.\n- `docker extension disable` turns off Docker extensions.\n- `docker extension init` creates a new Docker extension.\n- `docker extension install` installs a Docker extension with the specified image.\n- `docker extension ls` list installed Docker extensions.\n- `docker extension rm` removes a Docker extension.\n- `docker extension update` removes and re-installs a Docker extension.\n- `docker extension validate` validates the extension metadata file against the JSON schema.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/_index.md) -->\n---\ntitle: \"Part two: Publish\"\ndescription: General steps in how to publish an extension\nkeywords: Docker, Extensions, sdk, publish\naliases: \n - /desktop/extensions-sdk/extensions/\nweight: 40\n---\n\nThis section describes how to make your extension available and more visible, so users can discover it and install it with a single click.\n\n## Release your extension\n\nAfter you have developed your extension and tested it locally, you are ready to release the extension and make it available for others to install and use (either internally with your team, or more publicly).\n\nReleasing your extension consists of:\n\n- Providing information about your extension: description, screenshots, etc. so users can decide to install your extension\n- [Validating](validate.md) that the extension is built in the right format and includes the required information\n- Making the extension image available on [Docker Hub](https://hub.docker.com/)\n\nSee [Package and release your extension](DISTRIBUTION.md) for more details about the release process.\n\n## Promote your extension\n\nOnce your extension is available on Docker Hub, users who have access to the extension image can install it using the Docker CLI.\n\n### Use a share extension link\n\nYou can also [generate a share URL](share.md) in order to share your extension within your team, or promote your extension on the internet. The share link lets users view the extension description and screenshots.\n\n### Publish your extension in the Marketplace\n\nYou can publish your extension in the Extensions Marketplace to make it more discoverable. You must [submit your extension](publish.md) if you want to have it published in the Marketplace.\n\n## What happens next\n\n### New releases\n\nOnce you have released your extension, you can push a new release just by pushing a new version of the extension image, with an incremented tag (still using `semver` conventions).\nExtensions published in the Marketplace benefit from update notifications to all Desktop users that have installed the extension. For more details, see [new releases and updates](DISTRIBUTION.md#new-releases-and-updates).\n\n### Extension support and user feedback\n\nIn addition to providing a description of your extension's features and screenshots, you should also specify additional URLs using [extension labels](labels.md). This direct users to your website for reporting bugs and feedback, and accessing documentation and support.\n\n{{% include \"extensions-form.md\" %}}\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/DISTRIBUTION.md) -->\n---\ntitle: Package and release your extension\ndescription: Docker extension distribution\nkeywords: Docker, extensions, sdk, distribution\naliases: \n - /desktop/extensions-sdk/extensions/DISTRIBUTION/\nweight: 30\n---\n\nThis page contains additional information on how to package and distribute extensions.\n\n## Package your extension\n\nDocker extensions are packaged as Docker images. The entire extension runtime including the UI, backend services (host or VM), and any necessary binary must be included in the extension image.\nEvery extension image must contain a `metadata.json` file at the root of its filesystem that defines the [contents of the extension](../architecture/metadata.md).\n\nThe Docker image must have several [image labels](labels.md), providing information about the extension. See how to use [extension labels](labels.md) to provide extension overview information.\n\nTo package and release an extension, you must build a Docker image (`docker build`), and push the image to [Docker Hub](https://hub.docker.com/) (`docker push`) with a specific tag that lets you manage versions of the extension.\n\n## Release your extension\n\nDocker image tags must follow semver conventions in order to allow fetching the latest version of the extension, and to know if there are updates available. See [semver.org](https://semver.org/) to learn more about semantic versioning.\n\nExtension images must be multi-arch images so that users can install extensions on ARM/AMD hardware. These multi-arch images can include ARM/AMD specific binaries. Mac users will automatically use the right image based on their architecture.\nExtensions that install binaries on the host must also provide Windows binaries in the same extension image. See how to [build a multi-arch image](multi-arch.md) for your extension.\n\nYou can implement extensions without any constraints on the code repository. Docker doesn't need access to the code repository in order to use the extension. Also, you can manage new releases of your extension, without any dependency on Docker Desktop releases.\n\n## New releases and updates\n\nYou can release a new version of your Docker extension by pushing a new image with a new tag to Docker Hub.\n\nAny new image pushed to an image repository corresponding to an extension defines a new version of that extension. Image tags are used to identify version numbers. Extension versions must follow semver to make it easy to understand and compare versions.\n\nDocker Desktop scans the list of extensions published in the marketplace for new versions, and provides notifications to users when they can upgrade a specific extension. Extensions that aren't part of the Marketplace don't have automatic update notifications at the moment.\n\nUsers can download and install the newer version of any extension without updating Docker Desktop itself.\n\n## Extension API dependencies\n\nExtensions must specify the Extension API version they rely on. Docker Desktop checks the extension's required version, and only proposes to install extensions that are compatible with the current Docker Desktop version installed. Users might need to update Docker Desktop in order to install the latest extensions available.\n\nExtension image labels must specify the API version that the extension relies upon. This allows Docker Desktop to inspect newer versions of extension images without downloading the full extension image upfront.\n\n## License on extensions and the extension SDK\n\nThe [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use.\n\nThere is no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/labels.md) -->\n---\ntitle: Extension image labels\nlinkTitle: Add labels\ndescription: Docker extension labels\nkeywords: Docker, extensions, sdk, labels\naliases: \n - /desktop/extensions-sdk/extensions/labels/\nweight: 10\n---\n\nExtensions use image labels to provide additional information such as a title, description, screenshots, and more.\n\nThis information is then displayed as an overview of the extension, so users can choose to install it.\n\n![An extension overview, generated from labels](images/marketplace-details.png)\n\nYou can define [image labels](/reference/dockerfile.md#label) in the extension's `Dockerfile`.\n\n> [!IMPORTANT]\n>\n> If any of the **required** labels are missing in the `Dockerfile`, Docker Desktop considers the extension invalid and doesn't list it in the Marketplace.\n\n\nHere is the list of labels you can or need to specify when building your extension:\n\n| Label                                       | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Example                                                                                                                                                                                                                                                         |\n| ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `org.opencontainers.image.title`            | Yes      | Human-readable title of the image (string). This appears in the UI for Docker Desktop.                                                                                                                                                                                                                                                                                                                                                                                                                | my-extension                                                                                                                                                                                                                                                    |\n| `org.opencontainers.image.description`      | Yes      | Human-readable description of the software packaged in the image (string)                                                                                                                                                                                                                                                                                                                                                                                                                             | This extension is cool.                                                                                                                                                                                                                                         |\n| `org.opencontainers.image.vendor`           | Yes      | Name of the distributing entity, organization, or individual.                                                                                                                                                                                                                                                                                                                                                                                                                                         | Acme, Inc.                                                                                                                                                                                                                                                      |\n| `com.docker.desktop.extension.api.version`  | Yes      | Version of the Docker Extension manager that the extension is compatible with. It must follow [semantic versioning](https://semver.org/).                                                                                                                                                                                                                                                                                                                                                             | A specific version like `0.1.0` or, a constraint expression: `>= 0.1.0`, `>= 1.4.7, < 2.0` . For your first extension, you can use `docker extension version` to know the SDK API version and specify `>= <SDK_API_VERSION>`.                                   |\n| `com.docker.desktop.extension.icon`         | Yes      | The extension icon (format: .svg .png .jpg)                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com/assets/image.svg`                                                                                                                                                                                                                          |\n| `com.docker.extension.screenshots`          | Yes      | A JSON array of image URLs and an alternative text displayed to users (in the order they appear in your metadata) in your extension's details page. **Note:** The recommended size for screenshots is 2400x1600 pixels.                                                                                                                                                                                                                                                                               | `[{\"alt\":\"alternative text for image 1\",` `\"url\":\"https://example.com/image1.png\"},` `{\"alt\":\"alternative text for image2\",` `\"url\":\"https://example.com/image2.jpg\"}]`                                                                                         |\n| `com.docker.extension.detailed-description` | Yes      | Additional information in plain text or HTML about the extension to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                    | `My detailed description` or `<h1>My detailed description</h1>`                                                                                                                                                                                                 |\n| `com.docker.extension.publisher-url`        | Yes      | The publisher website URL to display in the details dialog.                                                                                                                                                                                                                                                                                                                                                                                                                                           | `https://example.com`                                                                                                                                                                                                                                           |\n| `com.docker.extension.additional-urls`      | No       | A JSON array of titles and additional URLs displayed to users (in the order they appear in your metadata) in your extension's details page. Docker recommends you display the following links if they apply: documentation, support, terms of service, and privacy policy links.                                                                                                                                                                                                                      | `[{\"title\":\"Documentation\",\"url\":\"https://example.com/docs\"},` `{\"title\":\"Support\",\"url\":\"https://example.com/bar/support\"},` `{\"title\":\"Terms of Service\",\"url\":\"https://example.com/tos\"},` `{\"title\":\"Privacy policy\",\"url\":\"https://example.com/privacy\"}]` |\n| `com.docker.extension.changelog`            | Yes      | Changelog in plain text or HTML containing the change for the current version only.                                                                                                                                                                                                                                                                                                                                                                                                                   | `Extension changelog` or `<p>Extension changelog<ul>` `<li>New feature A</li>` `<li>Bug fix on feature B</li></ul></p>`                                                                                                                                         |\n| `com.docker.extension.account-info`         | No       | Whether the user needs to register to a SaaS platform to use some features of the extension.                                                                                                                                                                                                                                                                                                                                                                                                          | `required` in case it does, leave it empty otherwise.                                                                                                                                                                                                           |\n| `com.docker.extension.categories`           | No       | The list of Marketplace categories that your extension belongs to: `ci-cd`, `container-orchestration`, `cloud-deployment`, `cloud-development`, `database`, `kubernetes`, `networking`, `image-registry`, `security`, `testing-tools`, `utility-tools`,`volumes`. If you don't specify this label, users won't be able to find your extension in the Extensions Marketplace when filtering by a category. Extensions published to the Marketplace before the 22nd of September 2022 have been auto-categorized by Docker. | Specified as comma separated values in case of having multiple categories e.g: `kubernetes,security` or a single value e.g. `kubernetes`.                                                                                                   |\n\n> [!TIP]\n>\n> Docker Desktop applies CSS styles to the provided HTML content. You can make sure that it renders correctly \n> [within the Marketplace](#preview-the-extension-in-the-marketplace). It is recommended that you follow the \n> [styling guidelines](../design/_index.md).\n\n## Preview the extension in the Marketplace\n\nYou can validate that the image labels render as you expect.\n\nWhen you create and install your unpublished extension, you can preview the extension in the Marketplace's **Managed** tab. You can see how the extension labels render in the list and in the details page of the extension.\n\n> Preview extensions already listed in Marketplace\n>\n> When you install a local image of an extension already published in the Marketplace, for example with the tag `latest`, your local image is not detected as \"unpublished\".\n>\n> You can re-tag your image in order to have a different image name that's not listed as a published extension.\n> Use `docker tag org/published-extension unpublished-extension` and then `docker extension install unpublished-extension`.\n\n![List preview](images/list-preview.png)\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/multi-arch.md) -->\n---\ntitle: Build multi-arch extensions\ndescription: Step three in creating an extension.\nkeywords: Docker, Extensions, sdk, build, multi-arch\naliases: \n - /desktop/extensions-sdk/extensions/multi-arch/\n---\n\nIt is highly recommended that, at a minimum, your extension is supported for the following architectures:\n\n- `linux/amd64`\n- `linux/arm64`\n\nDocker Desktop retrieves the extension image according to the user’s system architecture. If the extension does not provide an image that matches the user’s system architecture, Docker Desktop is not able to install the extension. As a result, users can’t run the extension in Docker Desktop.\n\n## Build and push for multiple architectures\n\nIf you created an extension from the `docker extension init` command, the\n`Makefile` at the root of the directory includes a target with name\n`push-extension`.\n\nYou can run `make push-extension` to build your extension against both\n`linux/amd64` and `linux/arm64` platforms, and push them to Docker Hub.\n\nFor example:\n\n```console\n$ make push-extension\n```\n\nAlternatively, if you started from an empty directory, use the command below\nto build your extension for multiple architectures:\n\n```console\n$ docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=username/my-extension:0.0.1 .\n```\n\nYou can then check the image manifest to see if the image is available for both\narchitectures using the [`docker buildx imagetools` command](/reference/cli/docker/buildx/imagetools/):\n\n```console\n$ docker buildx imagetools inspect username/my-extension:0.0.1\nName:      docker.io/username/my-extension:0.0.1\nMediaType: application/vnd.docker.distribution.manifest.list.v2+json\nDigest:    sha256:f3b552e65508d9203b46db507bb121f1b644e53a22f851185d8e53d873417c48\n\nManifests:\n  Name:      docker.io/username/my-extension:0.0.1@sha256:71d7ecf3cd12d9a99e73ef448bf63ae12751fe3a436a007cb0969f0dc4184c8c\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/amd64\n\n  Name:      docker.io/username/my-extension:0.0.1@sha256:5ba4ceea65579fdd1181dfa103cc437d8e19d87239683cf5040e633211387ccf\n  MediaType: application/vnd.docker.distribution.manifest.v2+json\n  Platform:  linux/arm64\n```\n\n> [!TIP]\n>\n> If you're having trouble pushing the image, make sure you're signed in to Docker Hub. Otherwise, run `docker login` to authenticate.\n\nFor more information, see [Multi-platform images](/manuals/build/building/multi-platform.md) page.\n\n## Adding multi-arch binaries\n\nIf your extension includes some binaries that deploy to the host, it’s important that they also have the right architecture when building the extension against multiple architectures.\n\nCurrently, Docker does not provide a way to explicitly specify multiple binaries for every architecture in the `metadata.json` file. However, you can add architecture-specific binaries depending on the `TARGETARCH` in the extension’s `Dockerfile`.\n\nThe following example shows an extension that uses a binary as part of its operations. The extension needs to run both in Docker Desktop for Mac and Windows.\n\nIn the `Dockerfile`, download the binary depending on the target architecture:\n\n```Dockerfile\n#syntax=docker/dockerfile:1.3-labs\n\nFROM alpine AS dl\nWORKDIR /tmp\nRUN apk add --no-cache curl tar\nARG TARGETARCH\nRUN <<EOT ash\n    mkdir -p /out/darwin\n    curl -fSsLo /out/darwin/kubectl \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/darwin/${TARGETARCH}/kubectl\"\n    chmod a+x /out/darwin/kubectl\nEOT\nRUN <<EOT ash\n    if [ \"amd64\" = \"$TARGETARCH\" ]; then\n        mkdir -p /out/windows\n        curl -fSsLo /out/windows/kubectl.exe \"https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe\"\n    fi\nEOT\n\nFROM alpine\nLABEL org.opencontainers.image.title=\"example-extension\" \\\n    org.opencontainers.image.description=\"My Example Extension\" \\\n    org.opencontainers.image.vendor=\"Docker Inc.\" \\\n    com.docker.desktop.extension.api.version=\">= 0.3.3\"\n\nCOPY --from=dl /out /\n```\n\nIn the `metadata.json` file, specify the path for every binary on every platform:\n\n```json\n{\n  \"icon\": \"docker.svg\",\n  \"ui\": {\n    \"dashboard-tab\": {\n      \"title\": \"Example Extension\",\n      \"src\": \"index.html\",\n      \"root\": \"ui\"\n    }\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/kubectl\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/kubectl.exe\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nAs a result, when `TARGETARCH` equals:\n\n- `arm64`, the `kubectl` binary fetched corresponds to the `arm64` architecture, and is copied to `/darwin/kubectl` in the final stage.\n- `amd64`, two `kubectl` binaries are fetched. One for Darwin and another for Windows. They are copied to `/darwin/kubectl` and `/windows/kubectl.exe` respectively, in the final stage.\n\n> [!NOTE]\n>\n> The binary destination path for Darwin is `darwin/kubectl` in both cases. The only change is the architecture-specific binary that is downloaded.\n\nWhen the extension is installed, the extension framework copies the binaries from the extension image at `/darwin/kubectl` for Darwin, or `/windows/kubectl.exe` for Windows, to a specific location in the user’s host filesystem.\n\n## Can I develop extensions that run Windows containers?\n\nAlthough Docker Extensions is supported on Docker Desktop for Windows, Mac, and Linux, the extension framework only supports Linux containers. Therefore, you must target `linux` as the OS when you build your extension image.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/publish.md) -->\n---\ntitle: Publish in the Marketplace\ndescription: Docker extension distribution\nkeywords: Docker, extensions, publish\naliases: \n - /desktop/extensions-sdk/extensions/publish/\nweight: 50\n---\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\n## Submit your extension to the Marketplace\n\nDocker Desktop displays published extensions in the Extensions Marketplace on [Docker Desktop](https://open.docker.com/extensions/marketplace) and [Docker Hub](https://hub.docker.com/search?q=&type=extension).\nThe Extensions Marketplace is a space where developers can discover extensions to improve their developer experience and propose their own extension to be available for all Desktop users.\n\nWhenever you are [ready to publish](DISTRIBUTION.md) your extension in the Marketplace, you can [self-publish your extension](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n> [!NOTE]\n>\n> As the Extension Marketplace continues to add new features for both Extension users and publishers, you are expected\n> to maintain your extension over time to ensure it stays available in the Marketplace.\n\n> [!IMPORTANT]\n>\n> The Docker manual review process for extensions is paused at the moment. Submit your extension through the [automated submission process](https://github.com/docker/extensions-submissions/issues/new?assignees=&labels=&template=1_automatic_review.yaml&title=%5BSubmission%5D%3A+)\n\n### Before you submit\n\nBefore you submit your extension, it must pass the [validation](validate.md) checks.\n\nIt is highly recommended that your extension follows the guidelines outlined in this section before submitting your\nextension. If you request a review from the Docker Extensions team and have not followed the guidelines, the review process may take longer. \n\nThese guidelines don't replace Docker's terms of service or guarantee approval:\n- Review the [design guidelines](../design/design-guidelines.md)\n- Ensure the [UI styling](../design/_index.md) is in line with Docker Desktop guidelines\n- Ensure your extensions support both light and dark mode\n- Consider the needs of both new and existing users of your extension\n- Test your extension with potential users\n- Test your extension for crashes, bugs, and performance issues\n- Test your extension on various platforms (Mac, Windows, Linux)\n- Read the [Terms of Service](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)\n\n#### Validation process\n\nSubmitted extensions go through an automated validation process. If all the validation checks pass successfully, the extension is\npublished on the Marketplace and accessible to all users within a few hours.\nIt is the fastest way to get developers the tools they need and to get feedback from them as you work to\nevolve/polish your extension.\n\n> [!IMPORTANT]\n>\n> Docker Desktop caches the list of extensions available in the Marketplace for 12 hours. If you don't see your\n> extension in the Marketplace, you can restart Docker Desktop to force the cache to refresh.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/share.md) -->\n---\ntitle: Share your extension\ndescription: Share your extension with a share link\nkeywords: Docker, extensions, share\naliases: \n - /desktop/extensions-sdk/extensions/share/\nweight: 40\n---\n\nOnce your extension image is accessible on Docker Hub, anyone with access to the image can install the extension.\n\nPeople can install your extension by typing `docker extension install my/awesome-extension:latest` in to the terminal.\n\nHowever, this option doesn't provide a preview of the extension before it's installed.\n\n## Create a share URL\n\nDocker lets you share your extensions using a URL.\n\nWhen people navigate to this URL, it opens Docker Desktop and displays a preview of your extension in the same way as an extension in the Marketplace. From the preview, users can then select **Install**.\n\n![Navigate to extension link](images/open-share.png)\n\nTo generate this link you can either:\n\n- Run the following command:\n\n  ```console\n  $ docker extension share my/awesome-extension:0.0.1\n  ```\n\n- Once you have installed your extension locally, navigate to the **Manage** tab and select **Share**.\n\n  ![Share button](images/list-preview.png)\n\n> [!NOTE]\n>\n> Previews of the extension description or screenshots, for example, are created using [extension labels](labels.md).\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/extensions-sdk/extensions/validate.md) -->\n---\ntitle: Validate your extension\nlinkTitle: Validate\ndescription: Step three in the extension creation process\nkeywords: Docker, Extensions, sdk, validate, install\naliases:\n - /desktop/extensions-sdk/extensions/validation/\n - /desktop/extensions-sdk/build/build-install/\n - /desktop/extensions-sdk/dev/cli/build-test-install-extension/\n - /desktop/extensions-sdk/extensions/validate/\nweight: 20\n---\n\nValidate your extension before you share or publish it. Validating the extension ensures that the extension:\n\n- Is built with the [image labels](labels.md) it requires to display correctly in the marketplace\n- Installs and runs without problems\n\nThe Extensions CLI lets you validate your extension before installing and running it locally.\n\nThe validation checks if the extension’s `Dockerfile` specifies all the required labels and if the metadata file is valid against the JSON schema file.\n\nTo validate, run:\n\n```console\n$ docker extension validate <name-of-your-extension>\n```\n\nIf your extension is valid, the following message displays:\n\n```console\nThe extension image \"name-of-your-extension\" is valid\n```\n\nBefore the image is built, it's also possible to validate only the `metadata.json` file:\n\n```console\n$ docker extension validate /path/to/metadata.json\n```\n\nThe JSON schema used to validate the `metadata.json` file against can be found under the [releases page](https://github.com/docker/extensions-sdk/releases/latest).\n\n\n<!-- Skill/Rule: Guides Skill (content/manuals/extensions/extensions-sdk/guides/_index.md) -->\n---\nbuild:\n  render: never\ntitle: Developer Guides\n---\n\n\n<!-- Skill/Rule: Guides Skill (content/manuals/extensions/extensions-sdk/guides/invoke-host-binaries.md) -->\n---\ntitle: Invoke host binaries\ndescription: Add invocations to host binaries from the frontend with the extension\n  SDK.\nkeywords: Docker, extensions, sdk, build\naliases:\n - /desktop/extensions-sdk/guides/invoke-host-binaries/\n---\n\nIn some cases, your extension may need to invoke some command from the host. For example, you\nmight want to invoke the CLI of your cloud provider to create a new resource, or the CLI of a tool your extension\nprovides, or even a shell script that you want to run on the host. \n\nYou could do that by executing the CLI from a container with the extension SDK. But this CLI needs to access the host's filesystem, which isn't easy nor fast if it runs in a container.\n\nThis page describes how to run executables on the host (binaries, shell scripts) that are shipped as part of your extension and deployed to the host. As extensions can run on multiple platforms, this\nmeans that you need to ship the executables for all the platforms you want to support.\n\nLearn more about extensions [architecture](../architecture/_index.md).\n\n> [!NOTE]\n>\n>  Note that extensions run with user access rights, this API is not restricted to binaries listed in the [host section](../architecture/metadata.md#host-section) of the extension metadata (some extensions might install software during user interaction, and invoke newly installed binaries even if not listed in the extension metadata).\n\nIn this example, the CLI is a simple `Hello world` script that must be invoked with a parameter and returns a \nstring.\n\n## Add the executables to the extension\n\n{{< tabs >}}\n{{< tab name=\"Mac and Linux\" >}}\n\nCreate a `bash` script for macOS and Linux, in the file `binaries/unix/hello.sh` with the following content:\n\n```bash\n#!/bin/sh\necho \"Hello, $1!\"\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\nCreate a `batch script` for Windows in another file `binaries/windows/hello.cmd` with the following content:\n\n```bash\n@echo off\necho \"Hello, %1!\"\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThen update the `Dockerfile` to copy the `binaries` folder into the extension's container filesystem and make the\nfiles executable.\n\n```dockerfile\n# Copy the binaries into the right folder\nCOPY --chmod=0755 binaries/windows/hello.cmd /windows/hello.cmd\nCOPY --chmod=0755 binaries/unix/hello.sh /linux/hello.sh\nCOPY --chmod=0755 binaries/unix/hello.sh /darwin/hello.sh\n```\n\n## Invoke the executable from the UI\n\nIn your extension, use the Docker Desktop Client object to [invoke the shell script](../dev/api/backend.md#invoke-an-extension-binary-on-the-host)\nprovided by the extension with the `ddClient.extension.host.cli.exec()` function.\nIn this example, the binary returns a string as result, obtained by `result?.stdout`, as soon as the extension view is rendered.\n\n{{< tabs group=\"framework\" >}}\n{{< tab name=\"React\" >}}\n\n```typescript\nexport function App() {\n  const ddClient = createDockerDesktopClient();\n  const [hello, setHello] = useState(\"\");\n\n  useEffect(() => {\n    const run = async () => {\n      let binary = \"hello.sh\";\n      if (ddClient.host.platform === 'win32') {\n        binary = \"hello.cmd\";\n      }\n\n      const result = await ddClient.extension.host?.cli.exec(binary, [\"world\"]);\n      setHello(result?.stdout);\n\n    };\n    run();\n  }, [ddClient]);\n    \n  return (\n    <div>\n      {hello}\n    </div>\n  );\n}\n```\n\n{{< /tab >}}\n{{< tab name=\"Vue\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Vue yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Vue)\n> and let us know if you'd like a sample with Vue.\n\n{{< /tab >}}\n{{< tab name=\"Angular\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Angular yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Angular)\n> and let us know if you'd like a sample with Angular.\n\n{{< /tab >}}\n{{< tab name=\"Svelte\" >}}\n\n> [!IMPORTANT]\n>\n> We don't have an example for Svelte yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Svelte)\n> and let us know if you'd like a sample with Svelte.\n\n{{< /tab >}}\n{{< /tabs >}}\n\n## Configure the metadata file\n\nThe host binaries must be specified in the `metadata.json` file so that Docker Desktop copies them on to the host when installing\nthe extension. Once the extension is uninstalled, the binaries that were copied are removed as well.\n\n```json\n{\n  \"vm\": {\n    ...\n  },\n  \"ui\": {\n    ...\n  },\n  \"host\": {\n    \"binaries\": [\n      {\n        \"darwin\": [\n          {\n            \"path\": \"/darwin/hello.sh\"\n          }\n        ],\n        \"linux\": [\n          {\n            \"path\": \"/linux/hello.sh\"\n          }\n        ],\n        \"windows\": [\n          {\n            \"path\": \"/windows/hello.cmd\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nThe `path` must reference the path of the binary inside the container.\n\n\n<!-- Skill/Rule: Guides Skill (content/manuals/extensions/extensions-sdk/guides/kubernetes.md) -->\n---\ntitle: Interacting with Kubernetes from an extension\nlinkTitle: Interacting with Kubernetes\ndescription: How to connect to a Kubernetes cluster from an extension\nkeywords: Docker, Extensions, sdk, Kubernetes\naliases:\n - /desktop/extensions-sdk/dev/kubernetes/\n - /desktop/extensions-sdk/guides/kubernetes/\n---\n\nThe Extensions SDK does not provide any API methods to directly interact with the Docker Desktop managed Kubernetes cluster or any other created using other tools such as KinD. However, this page provides a way for you to use other SDK APIs to interact indirectly with a Kubernetes cluster from your extension.\n\nTo request an API that directly interacts with Docker Desktop-managed Kubernetes, you can upvote [this issue](https://github.com/docker/extensions-sdk/issues/181) in the Extensions SDK GitHub repository.\n\n## Prerequisites\n\n### Turn on Kubernetes\n\nYou can use the built-in Kubernetes in Docker Desktop to start a Kubernetes single-node cluster.\nA `kubeconfig` file is used to configure access to Kubernetes when used in conjunction with the `kubectl` command-line tool, or other clients.\nDocker Desktop conveniently provides the user with a local preconfigured `kubeconfig` file and `kubectl` command within the user’s home area. It is a convenient way to fast-tracking access for those looking to leverage Kubernetes from Docker Desktop.\n\n## Ship the `kubectl` as part of the extension\n\nIf your extension needs to interact with Kubernetes clusters, it is recommended that you include the `kubectl` command line tool as part of your extension. By doing this, users who install your extension get `kubectl` installed on their host.\n\nTo find out how to ship the `kubectl` command line tool for multiple platforms as part of your Docker Extension image, see [Build multi-arch extensions](../extensions/multi-arch.md#adding-multi-arch-binaries).\n\n## Examples\n\nThe following code snippets have been put together in the [Kubernetes Sample Extension](https://github.com/docker/extensions-sdk/tree/main/samples/kubernetes-sample-extension). It shows how to interact with a Kubernetes cluster by shipping the `kubectl` command-line tool.\n\n### Check the Kubernetes API server is reachable\n\nOnce the `kubectl` command-line tool is added to the extension image in the `Dockerfile`, and defined in the `metadata.json`, the Extensions framework deploys `kubectl` to the users' host when the extension is installed.\n\nYou can use the JS API `ddClient.extension.host?.cli.exec` to issue `kubectl` commands to, for instance, check whether the Kubernetes API server is reachable given a specific context:\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"cluster-info\",\n  \"--request-timeout\",\n  \"2s\",\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n### List Kubernetes contexts\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"-o\",\n  \"jsonpath='{.contexts}'\",\n]);\n```\n\n### List Kubernetes namespaces\n\n```typescript\nconst output = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"get\",\n  \"namespaces\",\n  \"--no-headers\",\n  \"-o\",\n  'custom-columns=\":metadata.name\"',\n  \"--context\",\n  \"docker-desktop\",\n]);\n```\n\n## Persisting the kubeconfig file\n\nBelow there are different ways to persist and read the `kubeconfig` file from the host filesystem. Users can add, edit, or remove Kubernetes context to the `kubeconfig` file at any time.\n\n> Warning\n>\n> The `kubeconfig` file is very sensitive and if found can give an attacker administrative access to the Kubernetes Cluster.\n\n### Extension's backend container\n\nIf you need your extension to persist the `kubeconfig` file after it's been read, you can have a backend container that exposes an HTTP POST endpoint to store the content of the file either in memory or somewhere within the container filesystem. This way, if the user navigates out of the extension to another part of Docker Desktop and then comes back, you don't need to read the `kubeconfig` file again.\n\n```typescript\nexport const updateKubeconfig = async () => {\n  const kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n    \"config\",\n    \"view\",\n    \"--raw\",\n    \"--minify\",\n    \"--context\",\n    \"docker-desktop\",\n  ]);\n  if (kubeConfig?.stderr) {\n    console.log(\"error\", kubeConfig?.stderr);\n    return false;\n  }\n\n  // call backend container to store the kubeconfig retrieved into the container's memory or filesystem\n  try {\n    await ddClient.extension.vm?.service?.post(\"/store-kube-config\", {\n      data: kubeConfig?.stdout,\n    });\n  } catch (err) {\n    console.log(\"error\", JSON.stringify(err));\n  }\n};\n```\n\n### Docker volume\n\nVolumes are the preferred mechanism for persisting data generated by and used by Docker containers. You can make use of them to persist the `kubeconfig` file.\nBy persisting the `kubeconfig` in a volume you won't need to read the `kubeconfig` file again when the extension pane closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nconst kubeConfig = await ddClient.extension.host?.cli.exec(\"kubectl\", [\n  \"config\",\n  \"view\",\n  \"--raw\",\n  \"--minify\",\n  \"--context\",\n  \"docker-desktop\",\n]);\nif (kubeConfig?.stderr) {\n  console.log(\"error\", kubeConfig?.stderr);\n  return false;\n}\n\nawait ddClient.docker.cli.exec(\"run\", [\n  \"--rm\",\n  \"-v\",\n  \"my-vol:/tmp\",\n  \"alpine\",\n  \"/bin/sh\",\n  \"-c\",\n  `\"touch /tmp/.kube/config && echo '${kubeConfig?.stdout}' > /tmp/.kube/config\"`,\n]);\n```\n\n### Extension's `localStorage`\n\n`localStorage` is one of the mechanisms of a browser's web storage. It allows users to save data as key-value pairs in the browser for later use.\n`localStorage` does not clear data when the browser (the extension pane) closes. This makes it ideal for persisting data when navigating out of the extension to other parts of Docker Desktop.\n\n```typescript\nlocalStorage.setItem(\"kubeconfig\", kubeConfig);\n```\n\n```typescript\nlocalStorage.getItem(\"kubeconfig\");\n```\n\n\n<!-- Skill/Rule: Guides Skill (content/manuals/extensions/extensions-sdk/guides/oauth2-flow.md) -->\n---\ntitle: Authentication\ndescription: Docker extension OAuth 2.0 flow\nkeywords: Docker, extensions, sdk, OAuth 2.0\naliases:\n - /desktop/extensions-sdk/dev/oauth2-flow/\n - /desktop/extensions-sdk/guides/oauth2-flow/\n---\n\n> [!NOTE]\n>\n> This page assumes that you already have an Identity Provider (IdP), such as Google, Entra ID (formerly Azure AD) or Okta, which handles the authentication process and returns an access token.\n\nLearn how you can let users authenticate from your extension using OAuth 2.0 via a web browser, and return to your extension.\n\nIn OAuth 2.0, the term \"grant type\" refers to the way an application gets an access token. Although OAuth 2.0 defines several grant types, this page only describes how to authorize users from your extension using the Authorization Code grant type.\n\n## Authorization code grant flow\n\nThe Authorization Code grant type is used by confidential and public clients to exchange an authorization code for an access token.\n\nAfter the user returns to the client via the redirect URL, the application gets the authorization code from the URL and uses it to request an access token.\n\n![Flow for OAuth 2.0](images/oauth.png)\n\nThe image above shows that:\n\n- The Docker extension asks the user to authorize access to their data.\n- If the user grants access, the extension then requests an access token from the service provider, passing the access grant from the user and authentication details to identify the client.\n- The service provider then validates these details and returns an access token.\n- The extension uses the access token to request the user data with the service provider.\n\n### OAuth 2.0 terminology\n\n- Auth URL: The endpoint for the API provider authorization server, to retrieve the auth code.\n- Redirect URI: The client application callback URL to redirect to after auth. This must be registered with the API provider.\n\nOnce the user enters the username and password, they're successfully authenticated.\n\n## Open a browser page to authenticate the user\n\nFrom the extension UI, you can provide a button that, when selected, opens a new window in a browser to authenticate the user.\n\nUse the [ddClient.host.openExternal](../dev/api/dashboard.md#open-a-url) API to open a browser to the auth URL. For\nexample:\n\n```typescript\nwindow.ddClient.openExternal(\"https://authorization-server.com/authorize?\n  response_type=code\n  &client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n  &redirect_uri=${REDIRECT_URI});\n```\n\n## Get the authorization code and access token\n\nYou can get the authorization code from the extension UI by listing `docker-desktop://dashboard/extension-tab?extensionId=awesome/my-extension` as the `redirect_uri` in the OAuth app you're using and concatenating the authorization code as a query parameter. The extension UI code will then be able to read the corresponding code query-param.\n\n> [!IMPORTANT]\n>\n> Using this feature requires the extension SDK 0.3.3 in Docker Desktop. You need to ensure that the required SDK version for your extension set with `com.docker.desktop.extension.api.version` in [image labels](../extensions/labels.md) is higher than 0.3.3.\n\n#### Authorization\n\nThis step is where the user enters their credentials in the browser. After the authorization is complete, the user is redirected back to your extension user interface, and the extension UI code can consume the authorization code that's part of the query parameters in the URL.\n\n#### Exchange the Authorization Code\n\nNext, you exchange the authorization code for an access token.\n\nThe extension must send a `POST` request to the 0Auth authorization server with the following parameters:\n\n```text\nPOST https://authorization-server.com/token\n&client_id=T70hJ3ls5VTYG8ylX3CZsfIu\n&client_secret=YABbyHQShPeO1T3NDQZP8q5m3Jpb_UPNmIzqhLDCScSnRyVG\n&redirect_uri=${REDIRECT_URI}\n&code=N949tDLuf9ai_DaOKyuFBXStCNMQzuQbtC1QbvLv-AXqPJ_f\n```\n\n> [!NOTE]\n>\n> The client's credentials are included in the `POST` query params in this example. OAuth authorization servers may require that the credentials are sent as a HTTP Basic Authentication header or might support different formats. See your OAuth provider docs for details.\n\n### Store the access token\n\nThe Docker Extensions SDK doesn't provide a specific mechanism to store secrets.\n\nIt's highly recommended that you use an external source of storage to store the access token.\n\n> [!NOTE]\n>\n> The user interface Local Storage is isolated between extensions (an extension can't access another extension's local storage), and each extension's local storage gets deleted when users uninstall an extension.\n\n## What's next\n\nLearn how to [publish and distribute your extension](../extensions/_index.md)\n\n\n<!-- Skill/Rule: Guides Skill (content/manuals/extensions/extensions-sdk/guides/use-docker-socket-from-backend.md) -->\n---\ntitle: Use the Docker socket from the extension backend\nlinkTitle: Use the Docker socket\ndescription: Docker extension metadata\nkeywords: Docker, extensions, sdk, metadata\naliases: \n - /desktop/extensions-sdk/guides/use-docker-socket-from-backend/\n---\n\nExtensions can invoke Docker commands directly from the frontend with the SDK. \n\nIn some cases, it is useful to also interact with Docker Engine from the backend. \n\nExtension backend containers can mount the Docker socket and use it to\ninteract with Docker Engine from the extension backend logic. Learn more about the [Docker Engine socket](/reference/cli/dockerd/#examples)\n\nHowever, when mounting the Docker socket from an extension container that lives in the Desktop virtual machine, you want\nto mount the Docker socket from inside the VM, and not mount `/var/run/docker.sock` from the host filesystem (using\nthe Docker socket from the host can lead to permission issues in containers).\n\nIn order to do so, you can use `/var/run/docker.sock.raw`. Docker Desktop mounts the socket that lives in the Desktop VM, and not from the host.\n\n```yaml\nservices:\n  myExtension:\n    image: ${DESKTOP_PLUGIN_IMAGE}\n    volumes:\n      - /var/run/docker.sock.raw:/var/run/docker.sock\n```\n\n\n<!-- Skill/Rule: Extensions-sdk Skill (content/manuals/extensions/extensions-sdk/process.md) -->\n---\ndescription: Understand the process of creating an extension.\ntitle: The build and publish process\nkeyword: Docker Extensions, sdk, build, create, publish\naliases:\n - /desktop/extensions-sdk/process/\nweight: 10\n---\n\nThis documentation is structured so that it matches the steps you need to take when creating your extension. \n\nThere are two main parts to creating a Docker extension:\n\n1. Build the foundations\n2. Publish the extension\n\n> [!NOTE]\n>\n> You do not need to pay to create a Docker extension. The [Docker Extension SDK](https://www.npmjs.com/package/@docker/extension-api-client) is licensed under the Apache 2.0 License and is free to use. Anyone can create new extensions and share them without constraints.\n> \n> There is also no constraint on how each extension should be licensed, this is up to you to decide when creating a new extension.\n\n## Part one: Build the foundations\n\nThe build process consists of:\n\n- Installing the latest version of Docker Desktop.\n- Setting up the directory with files, including the extension’s source code and the required extension-specific files.\n- Creating the `Dockerfile` to build, publish, and run your extension in Docker Desktop.\n- Configuring the metadata file which is required at the root of the image filesystem.\n- Building and installing the extension.\n\nFor further inspiration, see the other examples in the [samples folder](https://github.com/docker/extensions-sdk/tree/main/samples).\n\n> [!TIP]\n>\n> Whilst creating your extension, make sure you follow the [design](design/design-guidelines.md) and [UI styling](design/_index.md) guidelines to ensure visual consistency and [level AA accessibility standards](https://www.w3.org/WAI/WCAG2AA-Conformance).\n\n## Part two: Publish and distribute your extension\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nDocker Desktop displays published extensions in the Extensions Marketplace. The Extensions Marketplace is a curated space where developers can discover extensions to improve their developer experience and upload their own extension to share with the world.\n\nIf you want your extension published in the Marketplace, read the [publish documentation](extensions/publish.md).\n\n{{% include \"extensions-form.md\" %}}\n\n## What’s next?\n\nIf you want to get up and running with creating a Docker Extension, see the [Quickstart guide](quickstart.md).\n\nAlternatively, get started with reading the \"Part one: Build\" section for more in-depth information about each step of the extension creation process.\n\nFor an in-depth tutorial of the entire build process, we recommend the following video walkthrough from DockerCon 2022.\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Yv7OG-EGJsg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n\n\n<!-- Skill/Rule: Extensions-sdk Skill (content/manuals/extensions/extensions-sdk/quickstart.md) -->\n---\ntitle: Quickstart\ndescription: Guide on how to build an extension quickly\nkeywords: quickstart, extensions\naliases:\n - desktop/extensions-sdk/tutorials/initialize/\n - /desktop/extensions-sdk/quickstart/\nweight: 20\n---\n\nFollow this guide to get started with creating a basic Docker extension. The Quickstart guide automatically generates boilerplate files for you.\n\n## Prerequisites\n\n- [Docker Desktop](/manuals/desktop/release-notes.md)\n- [NodeJS](https://nodejs.org/)\n- [Go](https://go.dev/dl/)\n\n> [!NOTE]\n>\n> NodeJS and Go are only required when you follow the quickstart guide to create an extension. It uses the `docker extension init` command to automatically generate boilerplate files. This command uses a template based on a ReactJS and Go application.\n\nIn Docker Desktop settings, ensure you can install the extension you're developing. You may need to navigate to the **Extensions** tab in Docker Desktop settings and deselect **Allow only extensions distributed through the Docker Marketplace**.\n\n## Step one: Set up your directory\n\nTo set up your directory, use the `init` subcommand and provide a name for your extension.\n\n```console\n$ docker extension init <my-extension>\n```\n\nThe command asks a series of questions about your extension, such as its name, a description, and the name of your Hub repository. This helps the CLI generate a set of boilerplate files for you to get started. It stores the boilerplate files in the `my-extension` directory.\n\nThe automatically generated extension contains:\n\n- A Go backend service in the `backend` folder that listens on a socket. It has one endpoint `/hello` that returns a JSON payload.\n- A React frontend in the `frontend` folder that can call the backend and output the backend’s response.\n\nFor more information and guidelines on building the UI, see the [Design and UI styling section](design/design-guidelines.md).\n\n## Step two: Build the extension\n\nTo build the extension, move into the newly created directory and run:\n\n```console\n$ docker build -t <name-of-your-extension> .\n```\n\n`docker build` builds the extension and generates an image named the same as the chosen hub repository. For example, if you typed `john/my-extension` as the answer to the following question:\n\n```console\n? Hub repository (eg. namespace/repository on hub): john/my-extension`\n```\n\nThe `docker build` generates an image with name `john/my-extension`.\n\n## Step three: Install and preview the extension\n\nTo install the extension in Docker Desktop, run:\n\n```console\n$ docker extension install <name-of-your-extension>\n```\n\nTo preview the extension in Docker Desktop, once the installation is complete and you should\nsee a **Quickstart** item underneath the **Extensions** menu. Selecting this item opens the extension's frontend.\n\n> [!TIP]\n>\n> During UI development, it’s helpful to use hot reloading to test your changes without rebuilding your entire\n> extension. See [Preview whilst developing the UI](dev/test-debug.md#hot-reloading-whilst-developing-the-ui) for more information.\n\nYou may also want to inspect the containers that belong to the extension. By default, extension containers are\nhidden from the Docker Dashboard. You can change this in **Settings**, see\n[how to show extension containers](dev/test-debug.md#show-the-extension-containers) for more information.\n\n## Step four: Submit and publish your extension to the Marketplace\n\n> [!IMPORTANT]\n>\n> New submissions to the Docker Extensions Marketplace are paused while Docker reviews Marketplace security. You can still update existing extensions, and private Marketplace extensions are unaffected. Contact extensions@docker.com if you have additional questions.\n\nIf you want to make your extension available to all Docker Desktop users, you can submit it for publication in the Marketplace. For more information, see [Publish](extensions/_index.md).\n\n## Clean up\n\nTo remove the extension, run:\n\n```console\n$ docker extension rm <name-of-your-extension>\n```\n\n## What's next\n\n- Build a more [advanced frontend](build/frontend-extension-tutorial.md) for your extension.\n- Learn how to [test and debug](dev/test-debug.md) your extension.\n- Learn how to [setup CI for your extension](dev/continuous-integration.md).\n- Learn more about extensions [architecture](architecture/_index.md).\n- Learn more about [designing the UI](design/design-guidelines.md).\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/marketplace.md) -->\n---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace\ntitle: Marketplace extensions\nweight: 10\naliases:\n - /desktop/extensions/marketplace/\n---\n\nThere are two types of extensions available in the Extensions Marketplace:\n- Docker-reviewed extensions\n- Self-published extensions\n\nDocker-reviewed extensions are manually reviewed by the Docker Extensions team to ensure an extra level of trust\nand quality. They appear as **Reviewed** in the Marketplace.\n\nSelf-published extensions are autonomously published by extension developers and go through an automated validation process. They appear as **Not reviewed** in the Marketplace.\n\n> [!IMPORTANT]\n>\n> Marketplace extensions are reviewed by Docker, but are not subject to a full security audit. Extensions run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Only install extensions from publishers you trust.\n\n## Install an extension\n\n> [!NOTE]\n>\n> For some extensions, a separate account needs to be created before use.\n\nTo install an extension:\n\n1. Open Docker Desktop.\n2. From the Docker Desktop Dashboard, select the **Extensions** tab.\n   The Extensions Marketplace opens on the **Browse** tab.\n3. Browse the available extensions.\n   You can sort the list of extensions by **Recently added**, **Most installed**, or alphabetically. Alternatively, use the **Content** or **Categories** drop-down menu to search for extensions by whether they have been reviewed or not, or by category.\n4. Choose an extension and select **Install**.\n\nFrom here, you can select **Open** to access the extension or install additional extensions. The extension also appears in the left-hand menu and in the **Manage** tab.\n\n## Update an extension\n\nYou can update any extension outside of Docker Desktop releases. To update an extension to the latest version, navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n\nThe **Manage** tab displays with all your installed extensions. If an extension has a new version available, it displays an **Update** button.\n\n\n## Uninstall an extension\n\nYou can uninstall an extension at any time.\n\n> [!NOTE]\n>\n> Any data used by the extension that's stored in a volume must be manually deleted.\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the ellipsis to the right of extension you want to uninstall.\n3. Select **Uninstall**.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/non-marketplace.md) -->\n---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows,\ntitle: Non-marketplace extensions\nweight: 20\n---\n\n## Install an extension not available in the Marketplace\n\n> [!WARNING]\n>\n> Extensions installed outside the Marketplace have not gone through Docker's review process. Like all Docker extensions, they run with host-level privileges. They can install binaries, access Docker Engine, invoke commands, and access files on your machine. Install only if you trust the publisher and have verified the source.\n\nThe Extensions Marketplace is the trusted and official place to install extensions from within Docker Desktop. These extensions have gone through a review process by Docker. However, other extensions can also be installed in Docker Desktop if you trust the extension author.\n\nGiven the nature of a Docker Extension (i.e. a Docker image) you can find other places where users have their extension's source code published. For example on GitHub, GitLab or even hosted in image registries like DockerHub or GHCR.\nYou can install an extension that has been developed by the community or internally at your company from a teammate. You are not limited to installing extensions just from the Marketplace.\n\n> [!NOTE]\n>\n> Ensure the option **Allow only extensions distributed through the Docker Marketplace** is disabled. Otherwise, this prevents any extension not listed in the Marketplace, via the Extension SDK tools from, being installed.\n> You can change this option in **Settings**. \n\nTo install an extension which is not present in the Marketplace, you can use the Extensions CLI that is bundled with Docker Desktop.\n\nIn a terminal, type `docker extension install IMAGE[:TAG]` to install an extension by its image reference and optionally a tag. Use the `-f` or `--force` flag to avoid interactive confirmation.\n\nGo to the Docker Desktop Dashboard to see the new extension installed.\n\n## List installed extensions\n\nRegardless whether the extension was installed from the Marketplace or manually by using the Extensions CLI, you can use the `docker extension ls` command to display the list of extensions installed.\nAs part of the output you'll see the extension ID, the provider, version, the title and whether it runs a backend container or has deployed binaries to the host, for example:\n\n```console\n$ docker extension ls\nID                  PROVIDER            VERSION             UI                    VM                  HOST\njohn/my-extension   John                latest              1 tab(My-Extension)   Running(1)          -\n```\n\nGo to the Docker Desktop Dashboard, select **Add Extensions** and on the **Managed** tab to see the new extension installed.\nNotice that an `UNPUBLISHED` label displays which indicates that the extension has not been installed from the Marketplace.\n\n## Update an extension \n\nTo update an extension which isn't present in the Marketplace, in a terminal type `docker extension update IMAGE[:TAG]` where the `TAG` should be different from the extension that's already installed.\n\nFor instance, if you installed an extension with `docker extension install john/my-extension:0.0.1`, you can update it by running `docker extension update john/my-extension:0.0.2`.\nGo to the Docker Desktop Dashboard to see the new extension updated.\n\n> [!NOTE]\n>\n> Extensions that aren't installed through the Marketplace don't receive update notifications from Docker Desktop.\n\n## Uninstall an extension\n\nTo uninstall an extension which is not present in the Marketplace, you can either navigate to the **Managed** tab in the Marketplace and select the **Uninstall** button, or from a terminal type `docker extension uninstall IMAGE[:TAG]`.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/private-marketplace.md) -->\n---\ndescription: How to configure and use Docker Extensions' private marketplace\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, Marketplace, private, security, admin\ntitle: Configure a private marketplace for extensions\ntags: [admin]\nlinkTitle: Configure a private marketplace\nweight: 30\n---\n\n{{< summary-bar feature_name=\"Private marketplace\" >}}\n\nLearn how to configure and set up a private marketplace with a curated list of extensions for your Docker Desktop users.\n\nDocker Extensions' private marketplace is designed specifically for organizations who don’t give developers root access to their machines. It makes use of [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) so administrators have complete control over the private marketplace.\n\n## Prerequisites\n\n- [Download and install Docker Desktop](https://docs.docker.com/desktop/release-notes/).\n- You must be an administrator for your organization.\n- You have the ability to push the `extension-marketplace` folder and `admin-settings.json` file to the locations specified below through device management software such as [Jamf](https://www.jamf.com/).\n\n## Step one: Initialize the private marketplace\n\n1. Create a folder locally for the content that will be deployed to your developers’ machines:\n\n   ```console\n   $ mkdir my-marketplace\n   $ cd my-marketplace\n   ```\n\n2. Initialize the configuration files for your marketplace:\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ /Applications/Docker.app/Contents/Resources/bin/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin init\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ /opt/docker-desktop/extension-admin init\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\nThis creates 2 files:\n\n- `admin-settings.json`, which activates the private marketplace feature once it’s applied to Docker Desktop on your developers’ machines.\n- `extensions.txt`, which determines which extensions to list in your private marketplace.\n\n> [!IMPORTANT]\n>\n> If your org is using [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md) via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will not need the `admin-settings.json` file. Delete the generated file and keep only the `extensions.txt` file.\n\n## Step two: Set the behaviour\n\nThe generated `admin-settings.json` file includes various settings you can modify.\n\n> [!IMPORTANT]\n>\n> If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), you will define the same settings in Docker Home instead of the `admin-settings.json` file.\n\nEach setting has a `value` that you can set, including a `locked` field that lets you lock the setting and make it unchangeable by your developers.\n\n- `extensionsEnabled` enables Docker Extensions.\n- `extensionsPrivateMarketplace` activates the private marketplace and ensures Docker Desktop connects to content defined and controlled by the administrator instead of the public Docker marketplace.\n- `onlyMarketplaceExtensions` allows or blocks developers from installing other extensions by using the command line. Teams developing new extensions must have this setting unlocked (`\"locked\": false`) to install and test extensions being developed.\n- `extensionsPrivateMarketplaceAdminContactURL` defines a contact link for developers to request new extensions in the private marketplace. If `value` is empty then no link is shown to your developers on Docker Desktop, otherwise this can be either an HTTP link or a “mailto:” link. For example,\n\n  ```json\n  \"extensionsPrivateMarketplaceAdminContactURL\": {\n    \"locked\": true,\n    \"value\": \"mailto:admin@acme.com\"\n  }\n  ```\n\nTo find out more information about the `admin-settings.json` file, see [Settings Management](/manuals/enterprise/security/hardened-desktop/settings-management/_index.md).\n\n## Step three: List allowed extensions\n\nThe generated `extensions.txt` file defines the list of extensions that are available in your private marketplace.\n\nEach line in the file is an allowed extension and follows the format of `org/repo:tag`.\n\nFor example, if you want to permit the Disk Usage extension you would enter the following into your `extensions.txt` file:\n\n```console\ndocker/disk-usage-extension:0.2.8\n```\n\nIf no tag is provided, the latest tag available for the image is used. You can also comment out lines with `#` so the extension is ignored.\n\nThis list can include different types of extension images:\n\n- Extensions from the public marketplace or any public image stored in Docker Hub.\n- Extension images stored in Docker Hub as private images. Developers need to be signed in and have pull access to these images.\n- Extension images stored in a private registry. Developers need to be signed in and have pull access to these images.\n\n> [!IMPORTANT]\n>\n> Your developers can only install the version of the extension that you’ve listed.\n\n## Step four: Generate the private marketplace\n\nOnce the list in `extensions.txt` is ready, you can generate the marketplace:\n\n{{< tabs group=\"os_version\" >}}\n{{< tab name=\"Mac\" >}}\n\n```console\n$ /Applications/Docker.app/Contents/Resources/bin/extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Windows\" >}}\n\n```console\n# For all-user installations\n$ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin generate\n\n# For per-user installations\n$ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin generate\n```\n\n{{< /tab >}}\n{{< tab name=\"Linux\" >}}\n\n```console\n$ /opt/docker-desktop/extension-admin generate\n```\n\n{{< /tab >}}\n{{< /tabs >}}\n\nThis creates an `extension-marketplace` directory and downloads the marketplace metadata for all the allowed extensions.\n\nThe marketplace content is generated from extension image information as image labels, which is the [same format as public extensions](extensions-sdk/extensions/labels.md). It includes the extension title, description, screenshots, links, etc.\n\n## Step five: Test the private marketplace setup\n\nIt's recommended that you try the private marketplace on your Docker Desktop installation.\n\n1. Run the following command in your terminal. This command automatically copies the generated files to the location where Docker Desktop reads the configuration files. Depending on your operating system, the location is:\n\n    - Mac: `/Library/Application\\ Support/com.docker.docker`\n    - Windows: `C:\\ProgramData\\DockerDesktop`\n    - Linux: `/usr/share/docker-desktop`\n\n   {{< tabs group=\"os_version\" >}}\n   {{< tab name=\"Mac\" >}}\n\n   ```console\n   $ sudo /Applications/Docker.app/Contents/Resources/bin/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Windows (run as admin)\" >}}\n\n   ```console\n   # For all-user installations\n   $ C:\\Program Files\\Docker\\Docker\\resources\\bin\\extension-admin apply\n\n   # For per-user installations\n   $ %LOCALAPPDATA%\\Programs\\DockerDesktop\\resources\\bin\\extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< tab name=\"Linux\" >}}\n\n   ```console\n   $ sudo /opt/docker-desktop/extension-admin apply\n   ```\n\n   {{< /tab >}}\n   {{< /tabs >}}\n\n2. Quit and re-open Docker Desktop. \n3. Sign in with a Docker account.\n\n> [!IMPORTANT]\n>\n> > If your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md), in Docker Desktop 4.59 and earlier, you must manually delete the `admin-settings.json` file created in the target folder by the `apply` command before step 2. In Docker Desktop 4.60 and later, this step is no longer necessary. \n\nWhen you select the **Extensions** tab, you should see the private marketplace listing only the extensions you have allowed in `extensions.txt`.\n\n![Extensions Private Marketplace](/assets/images/extensions-private-marketplace.webp)\n\n## Step six: Distribute the private marketplace\n\nOnce you’ve confirmed that the private marketplace configuration works, the final step is to distribute the files to the developers’ machines with the MDM software your organization uses. For example, [Jamf](https://www.jamf.com/).\n\nThe files to distribute are:\n* `admin-settings.json` (except if your org is managing settings via [Docker Home](manuals/enterprise/security/hardened-desktop/settings-management/configure-admin-console/_index.md))\n* the entire `extension-marketplace` folder and its subfolders\n\nThese files must be placed on developer's machines. Depending on your operating system, the target location is (as mentioned above):\n\n- Mac: `/Library/Application\\ Support/com.docker.docker`\n- Windows: `C:\\ProgramData\\DockerDesktop`\n- Linux: `/usr/share/docker-desktop`\n\nMake sure your developers are signed in to Docker Desktop in order for the private marketplace configuration to take effect. As an administrator, you should [enforce sign-in](/manuals/enterprise/security/enforce-sign-in/_index.md).\n\n## Feedback\n\nGive feedback or report any bugs you may find by emailing `extensions@docker.com`.\n\n\n<!-- Skill/Rule: Extensions Skill (content/manuals/extensions/settings-feedback.md) -->\n---\ndescription: Extensions\nkeywords: Docker Extensions, Docker Desktop, Linux, Mac, Windows, feedback\ntitle: Settings and feedback for Docker Extensions\nlinkTitle: Settings and feedback\nweight: 40\n---\n\n## Settings\n\n### Turn on or turn off extensions\n\nDocker Extensions is switched off by default. To change your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Enable Docker Extensions**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> If you are an [organization owner](/manuals/admin/organization/manage/manage-a-team.md#what-is-an-organization-owner), you can turn off extensions for your users. Open the `settings-store.json` file, and set `\"extensionsEnabled\"` to `false`.\n> The `settings-store.json` file is located at:\n>   - `~/Library/Group Containers/group.com.docker/settings-store.json` on Mac\n>   - `C:\\Users\\[USERNAME]\\AppData\\Roaming\\Docker\\settings-store.json` on Windows\n>\n> This can also be done with [Hardened Docker Desktop](/manuals/enterprise/security/hardened-desktop/_index.md)\n\n### Turn on or turn off extensions not available in the Marketplace\n\nYou can install extensions through the Marketplace or through the Extensions SDK tools. You can choose to only allow published extensions. These are extensions that have been reviewed and published in the Extensions Marketplace.\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Allow only extensions distributed through the Docker Marketplace**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n### See containers created by extensions\n\nBy default, containers created by extensions are hidden from the list of containers in the Docker Desktop Dashboard and the Docker CLI. To make them visible\nupdate your settings:\n\n1. Navigate to **Settings**.\n2. Select the **Extensions** tab.\n3. Next to **Show Docker Extensions system containers**, select or clear the checkbox to set your desired state.\n4. In the bottom-right corner, select **Apply**.\n\n> [!NOTE]\n>\n> Enabling extensions doesn't use computer resources (CPU / Memory) by itself.\n>\n> Specific extensions might use computer resources, depending on the features and implementation of each extension, but there is no reserved resources or usage cost associated with enabling extensions.\n\n## Submit feedback\n\nFeedback can be given to an extension author through a dedicated Slack channel or GitHub. To submit feedback about a particular extension:\n\n1. Navigate to the Docker Desktop Dashboard and select the **Manage** tab.\n   This displays a list of extensions you've installed.\n2. Select the extension you want to provide feedback on. \n3. Scroll down to the bottom of the extension's description and, depending on the \nextension, select:\n    - Support\n    - Slack\n    - Issues. You'll be sent to a page outside of Docker Desktop to submit your feedback.\n\nIf an extension doesn't provide a way for you to give feedback, contact us and we'll pass on the feedback for you. To provide feedback, select the **Give feedback** to the right of **Extensions Marketplace**.\n\n\n<!-- Skill/Rule: Subagent: index (_vendor/github.com/docker/docker-agent/docs/concepts/agents/index.md) -->\n---\ntitle: \"Agents\"\ndescription: \"Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents.\"\nkeywords: docker agent, ai agents, concepts, agents\nweight: 10\ncanonical: https://docs.docker.com/ai/docker-agent/concepts/agents/\n---\n\n_Agents are the core building blocks of Docker Agent. Each agent is an AI-powered entity with a model, instructions, tools, and optional sub-agents._\n\n## What is an Agent?\n\nAn agent in Docker Agent is defined by:\n\n- **Model** — The AI model powering it (e.g., Claude, GPT-5, Gemini). See [Models](../models/index.md).\n- **Description** — A brief summary of what the agent does (used by other agents for delegation)\n- **Instruction** — The system prompt that defines the agent's behavior and personality\n- **Tools** — Capabilities like filesystem access, shell commands, or external APIs\n- **Sub-agents** — Other agents it can delegate tasks to\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Expert software developer\n    instruction: |\n      You are an expert developer. Write clean, efficient code\n      and explain your reasoning step by step.\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n```\n\n## The Root Agent\n\nEvery Docker Agent configuration has a **root agent** — the entry point that receives user messages. In a single-agent setup, this is the only agent. In a multi-agent setup, the root agent acts as a coordinator, delegating tasks to specialized sub-agents.\n\n> [!NOTE]\n> **Naming**\n>\n> The first agent defined in your YAML (or the one named `root`) is the root agent by default. You can also specify which agent to start with using `docker agent run config.yaml -a agent_name`.\n\n## Agent Properties\n\n| Property               | Type    | Required | Description                                                    |\n| ---------------------- | ------- | -------- | -------------------------------------------------------------- |\n| `model`                | string  | ✓        | Model reference (inline like `openai/gpt-5` or a named model) |\n| `description`          | string  | ✓        | What the agent does — used by other agents for delegation      |\n| `instruction`          | string  | ✓        | System prompt defining behavior                                |\n| `toolsets`             | array   | ✗        | List of tool configurations                                    |\n| `sub_agents`           | array   | ✗        | Names of agents this agent can delegate to                     |\n| `fallback`             | object  | ✗        | Fallback model configuration for resilience                    |\n| `add_date`             | boolean | ✗        | Include current date in context                                |\n| `add_environment_info` | boolean | ✗        | Include OS, working directory, git info in context             |\n| `max_iterations`       | int     | ✗        | Max tool-calling loops (default: unlimited)                    |\n| `commands`             | object  | ✗        | Named prompts callable via `/command`                          |\n| `skills`               | boolean \\| list | ✗    | Enable skill discovery and loading. `true` = `[\"local\"]`; list values may combine `\"local\"` with remote skill-server URLs. |\n\n## Model Fallbacks\n\nAgents can automatically fail over to alternative models when the primary model is unavailable:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2 # retries per model for 5xx errors\n      cooldown: 1m # stick with fallback after 429\n```\n\n## Named Commands\n\nDefine reusable prompts that can be invoked as commands:\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a helpful assistant.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\n```bash\n# Run a named command\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n```\n\nCommands support environment variable interpolation using JavaScript template literal syntax. Undefined variables expand to empty strings.\n\n## Default Agent\n\nRunning `docker agent run` without a config argument uses `docker-agent.yaml`, `docker-agent.yml`, or `docker-agent.hcl` from the current directory when present. Otherwise, it uses a capable built-in default agent for quick tasks without needing any configuration.\n\n```bash\n# Use the project config or built-in default agent\n$ docker agent run\n\n# Override the default with an alias\n$ docker agent alias add default /path/to/my-agent.yaml\n$ docker agent run  # now runs your custom agent\n```\n\n> [!TIP]\n> **See also**\n>\n> For reusable task-specific instructions, see [Skills](../../features/skills/index.md). For multi-agent patterns, see [Multi-Agent](../multi-agent/index.md). For full config reference, see [Agent Config](../../configuration/agents/index.md).\n\n\n<!-- Skill/Rule: Subagent: index (_vendor/github.com/docker/docker-agent/docs/configuration/agents/index.md) -->\n---\ntitle: \"Agent Configuration\"\ndescription: \"Complete reference for defining agents in your YAML configuration.\"\nkeywords: docker agent, ai agents, configuration, yaml, agent configuration\nlinkTitle: \"Agent Config\"\nweight: 30\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/agents/\n---\n\n_Complete reference for defining agents in your YAML configuration._\n\nA configuration must define at least one agent under `agents`.\n\n## Full Schema\n\n<!-- yaml-lint:skip -->\n```yaml\nagents:\n  agent_name:\n    model: string # Required: model reference\n    description: string # Required: what this agent does\n    instruction: string # Required (unless instruction_file): system prompt\n    instruction_file: string | [list] # Optional: load the system prompt from one or more files relative to this config (mutually exclusive with instruction)\n    sub_agents: [list] # Optional: local or external sub-agent references\n    toolsets: [list] # Optional: tool configurations (use `type: rag` for RAG sources)\n    fallback: # Optional: fallback config\n      models: [list]\n      retries: 2\n      cooldown: 1m\n    add_date: boolean # Optional: add date to context\n    add_environment_info: boolean # Optional: add env info to context\n    add_prompt_files: [list] # Optional: include additional prompt files\n    add_description_parameter: bool # Optional: add description to tool schema\n    redact_secrets: boolean # Optional: scrub detected secrets out of tool args, outgoing chat messages, and tool output\n    code_mode_tools: boolean # Optional: let the agent write JavaScript to orchestrate tool calls (see Code Mode)\n    max_iterations: int # Optional: max tool-calling loops\n    max_consecutive_tool_calls: int # Optional: max identical consecutive tool calls\n    max_old_tool_call_tokens: int # Optional: token budget for old tool call content (disabled unless positive)\n    max_tool_result_tokens: int # Optional: per-tool-result token cap with middle-out truncation (disabled unless positive)\n    num_history_items: int # Optional: limit conversation history\n    session_compaction: boolean # Optional: disable automatic session compaction (default: true)\n    compaction_threshold: float # Optional: context-window fraction that triggers auto-compaction (0–1, default: 0.9)\n    compaction_model: string # Optional: model used for session-compaction (summary generation)\n    use_toolsets: [list] # Optional: names of top-level toolsets to merge into this agent\n    readonly: boolean # Optional: restrict all toolsets to read-only tools only\n    skills: boolean | [list] # Optional: enable skill discovery (true/false or list of names and/or sources)\n    use_commands: [list] # Optional: names of top-level commands groups to merge into this agent\n    use_skills: [list] # Optional: names of top-level skills groups to merge into this agent\n    commands: # Optional: named prompts\n      name: \"prompt text\" # or {instruction: \"prompt\", agent: \"sub_agent_name\"} or {url: \"https://...\"} (TUI only)\n    welcome_message: string # Optional: message shown at session start\n    handoffs: [list] # Optional: agent names this agent can hand off to\n    force_handoff: string # Optional: agent that always receives the conversation when this agent stops\n    hooks: # Optional: lifecycle hooks\n      pre_tool_use: [list]\n      tool_response_transform: [list]\n      post_tool_use: [list]\n      session_start: [list]\n      session_end: [list]\n      on_user_input: [list]\n      stop: [list]\n      notification: [list]\n    structured_output: # Optional: constrain output format\n      name: string\n      schema: object\n    cache: # Optional: response cache (skip the model on repeat questions)\n      enabled: boolean\n      case_sensitive: boolean\n      trim_spaces: boolean\n      path: string\n    harness: # Optional: delegate to an external coding CLI (Claude Code, Codex, opencode, pi)\n      type: string # Required: claude-code | codex | opencode | pi\n      model: string # Optional: model override forwarded to the CLI (omit for the CLI's own default)\n      effort: string # claude-code only: low | medium | high | xhigh | max (omit for the Claude Code default)\n      agent: string # opencode only: agent profile name\n      thinking: boolean # opencode only: enable extended thinking\n```\n\n> [!TIP]\n> **See also**\n>\n> For model parameters, see [Model Config](../models/index.md). For tool details, see [Tool Config](../tools/index.md). For multi-agent patterns, see [Multi-Agent](../../concepts/multi-agent/index.md).\n\n## Properties Reference\n\n| Property                    | Type    | Required | Description                                                                                                                                                                   |\n| --------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `model`                     | string  | ✓        | Model reference. Either inline (`openai/gpt-5`) or a named model from the `models` section.                                                                              |\n| `description`               | string  | ✓        | Brief description of the agent's purpose. Used by coordinators to decide delegation.                                                                                          |\n| `instruction`               | string  | ✓        | System prompt that defines the agent's behavior, personality, and constraints. Required unless `instruction_file` is set.                                                      |\n| `instruction_file`          | string \\| array  | ✗        | Path(s) to a file or files (relative to the config file's directory) whose contents become the agent's instruction, loaded at startup. Accepts a single path or a list; multiple files are concatenated in order, separated by a blank line. Mutually exclusive with `instruction`. Each path must be a local relative path inside the config directory (absolute paths and `..` traversal are rejected). Only supported for local file-based configs, not OCI/URL sources. See [External Instruction Files](#external-instruction-files) below. |\n| `sub_agents`                | array   | ✗        | List of agent names or external OCI references this agent can delegate to. Supports local agents, registry references (e.g., `myorg/agent:tag`), and named references (`name:reference`). Automatically enables the `transfer_task` tool. Pin external OCI references to a digest (`name@sha256:…`) to skip the per-run registry lookup that tag references incur. See [External Sub-Agents](../../concepts/multi-agent/index.md#external-sub-agents-from-registries). |\n| `toolsets`                  | array   | ✗        | List of tool configurations. See [Tool Config](../tools/index.md).                                                                                                        |\n| `fallback`                  | object  | ✗        | Automatic model failover configuration.                                                                                                                                       |\n| `add_date`                  | boolean | ✗        | When `true`, injects the current date into the agent's context.                                                                                                               |\n| `add_environment_info`      | boolean | ✗        | When `true`, injects working directory, OS, CPU architecture, and git info into context.                                                                                      |\n| `add_prompt_files`          | array   | ✗        | List of file paths whose contents are appended to the system prompt. Useful for including coding standards, guidelines, or additional context.                                |\n| `add_description_parameter` | boolean | ✗        | When `true`, adds agent descriptions as a parameter in tool schemas. Helps with tool selection in multi-agent scenarios.                                                      |\n| `redact_secrets`            | boolean | ✗        | When `true`, scrubs detected secrets (API keys, tokens, private keys, etc.) out of tool-call arguments, outgoing chat messages, and tool output before they reach a tool, the model, or downstream consumers. See [Redacting Secrets](#redacting-secrets) below.   |\n| `code_mode_tools`           | boolean | ✗        | When `true`, replaces the agent's individual tools with a single tool that runs a JavaScript script calling as many of them as needed in one turn. See [Code Mode](../../features/code-mode/index.md). |\n| `max_iterations`            | int     | ✗        | Maximum number of tool-calling loops. Default: unlimited (0). Set this to prevent infinite loops.                                                                             |\n| `max_consecutive_tool_calls` | int     | ✗        | Maximum consecutive identical tool calls before the agent is terminated, preventing degenerate loops. Default: `5`.                                                          |\n| `max_old_tool_call_tokens`  | int     | ✗        | Maximum number of tokens to keep from old tool call arguments and results. Older tool calls beyond this budget have their content replaced with a placeholder, saving context space. Tokens are approximated as `len/4`. Truncation is disabled by default; set a positive value to enable it. Set to `-1` to disable truncation (unlimited). |\n| `max_tool_result_tokens`    | int     | ✗        | Maximum number of tokens to keep from each tool result when it is added to the session. Oversized results are truncated middle-out: the head and tail are kept and the removed middle is replaced with a truncation marker. Textual documents attached to the result share the same budget. Tokens are approximated as `len/4`. The cap is disabled by default; set a positive value to enable it. `0` and `-1` both leave tool results unbounded. |\n| `num_history_items`         | int     | ✗        | Limit the number of conversation history messages sent to the model. Useful for managing context window size with long conversations. Default: unlimited (all messages sent). |\n| `session_compaction`        | boolean | ✗        | When `false`, disables automatic session compaction for this agent: neither the proactive threshold trigger nor the post-overflow auto-recovery runs. The manual `/compact` command remains available. Default: `true`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_threshold`      | float   | ✗        | Fraction of the model's context window at which proactive auto-compaction triggers. Must be greater than `0` and at most `1`. A `compaction_threshold` set on the agent's model takes precedence. Default: `0.9`. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `compaction_model`          | string  | ✗        | Model used for session compaction (summary generation). Can be a named model or an inline `provider/model` string. This agent-level value takes precedence over a `compaction_model` set on the agent's model or provider; when none is set, the agent's own model compacts. See the [Context & Compaction guide](../../guides/compaction/index.md). |\n| `skills`                    | bool/array | ✗     | Enable automatic skill discovery. `true` loads all discovered local skills, `false` disables them. A list can mix skill sources (`local` or `https://…` URLs) and skill names to include — see [Skills](../../features/skills/index.md).                                                     |\n| `commands`                  | object  | ✗        | Named prompts that can be run with `docker agent run config.yaml /command_name`. Can be simple strings or objects with `instruction` and/or `agent` fields for agent switching, or a `url` field to open a link in the browser (TUI only). See [Named Commands](#named-commands) below. |\n| `use_commands`              | list of string | ✗   | Names of top-level `commands` groups to merge into this agent. Inline `commands` entries take precedence on name conflicts. Default: `[]`. |\n| `use_skills`                | list of string | ✗   | Names of top-level `skills` groups to merge into this agent. Inline skills are deduplicated by name against merged entries. Default: `[]`. |\n| `use_toolsets`              | list of string | ✗   | Names of top-level `toolsets` groups to merge into this agent. See [Reusable Toolsets](../overview/index.md#reusable-toolsets-toolsets). Default: `[]`. |\n| `readonly`                  | boolean | ✗   | When `true`, every toolset on this agent is filtered to expose only read-only tools (those annotated with a read-only hint). Mutating tools are removed at load time and cannot be called even if the model tries. See [Read-Only Agents](#read-only-agents) below. |\n| `welcome_message`           | string  | ✗        | Message displayed to the user when a session starts. Rendered as Markdown in the TUI. **Not sent to the model** — it exists purely for the user's benefit. Useful for telling users what the agent can do and what commands are available. |\n| `handoffs`                  | array   | ✗        | List of agent names this agent can hand off the conversation to. Enables the `handoff` tool. See [Handoffs Routing](../../concepts/multi-agent/index.md#handoffs-routing).                  |\n| `force_handoff`             | string  | ✗        | Name of an agent that unconditionally receives the conversation whenever this agent produces a final response. The runtime performs the switch itself, bypassing the LLM's tool-calling, guaranteeing deterministic pipelines. Must not reference the agent itself, and chains must not form a cycle. See [Forced Handoffs](../../concepts/multi-agent/index.md#forced-handoffs). |\n| `hooks`                     | object  | ✗        | Lifecycle hooks for running commands at various points. See [Hooks](../hooks/index.md).                                                                                   |\n| `structured_output`         | object  | ✗        | Constrain agent output to match a JSON schema. See [Structured Output](../structured-output/index.md).                                                                    |\n| `cache`                     | object  | ✗        | Response cache. When the same user question is asked again, the previous answer is replayed verbatim and the model is not called. See [Response Cache](#response-cache) below.                  |\n| `harness`                   | object  | ✗        | Run this agent through an external coding CLI instead of a model. **Note:** Any `toolsets:` defined on the same agent are silently ignored when `harness:` is set — the external CLI brings its own tools. See [Coding Harnesses](../../features/harnesses/index.md). |\n\n> [!WARNING]\n> **max_iterations**\n>\n> Default is `0` (unlimited). Always set `max_iterations` for agents with powerful tools like `shell` to prevent infinite loops. A value of 20–50 is typical for development agents.\n\n> [!TIP]\n> **Managing long sessions**\n>\n> `max_old_tool_call_tokens`, `max_tool_result_tokens`, `num_history_items`, `session_compaction`, and `compaction_threshold` all help keep long-running sessions inside the model's context window. See the [Context & Compaction guide](../../guides/compaction/index.md) for how to combine them.\n\n## External Instruction Files\n\nLong system prompts can be kept in their own files instead of being inlined in\nthe YAML, using `instruction_file`. This separates infrastructure configuration\n(models, providers, tools) from behavioral content (the prompt), which keeps\nversion-control diffs focused, reduces merge conflicts on shared configs, and\nlets instruction content be edited without risking YAML syntax errors.\n\n```yaml\nagents:\n  coordinator:\n    model: openai/gpt-5-mini\n    description: Routes work between specialist agents\n    instruction_file: instructions/coordinator.md\n    sub_agents:\n      - writer\n  writer:\n    model: openai/gpt-5-mini\n    description: Drafts and edits written content\n    instruction_file: instructions/writer.md\n```\n\nThe path is resolved relative to the config file's directory and the file's\ncontents are loaded as the agent's instruction when the config is loaded. Notes:\n\n- **Mutually exclusive** with `instruction`. Setting both is an error.\n- Each path must be a **local relative path inside the config directory**.\n  Absolute paths and `..` traversal are rejected.\n- A **list** of files is also accepted; their contents are concatenated in\n  order, separated by a blank line. This lets a shared preamble be reused\n  across agents while each agent appends its own specifics:\n\n  ```yaml\n  agents:\n    writer:\n      model: openai/gpt-5-mini\n      description: Drafts and edits written content\n      instruction_file:\n        - instructions/shared-preamble.md\n        - instructions/writer.md\n  ```\n\n- Only supported for **local file-based configs**, not agents loaded from OCI\n  registries or URLs. When an agent is pushed with `docker agent share push`,\n  the file contents are inlined into the pushed artifact, so the published\n  agent stays self-contained.\n\nA runnable example lives in [`examples/instruction_file.yaml`](https://github.com/docker/docker-agent/blob/main/examples/instruction_file.yaml).\n\n## Prompt Files\n\n`add_prompt_files` injects the contents of one or more files into the agent's\ncontext at the start of every turn — handy for repo-wide conventions like\n`AGENTS.md` or `CLAUDE.md` that should stay available without being pasted\ninto `instruction`:\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A helpful coding assistant\n    instruction: You are an expert software developer.\n    add_prompt_files:\n      - AGENTS.md\n```\n\nFor each name, the agent loads the closest match found by walking up from the\ncurrent working directory, plus (if it's a different file) a copy at that\nname directly under the user's home directory — so a personal `~/AGENTS.md`\ncan layer on top of a repo-local one. Missing files are skipped rather than\nerroring. Because resolution and the read happen on every turn, edits to the\nfile are picked up without restarting the agent.\n\nUse `--prompt-file` to add files for a single run without editing the\nconfig. It's merged with any `add_prompt_files` already set on the agent,\nwith duplicates dropped:\n\n```bash\n$ docker agent run agent.yaml --prompt-file CONTRIBUTING.md\n```\n\nResolved prompt files show up as their own entries in the `/context` dialog — see [File Attachments](../../features/tui/index.md#file-attachments) in the Terminal UI guide.\n\nSee [Choosing a Large-Input Strategy](../../guides/headless/index.md#choosing-a-large-input-strategy) for how prompt files compare to `@`/`/attach` attachments, the `rag` toolset, and sending content over the API/chat server.\n\n## Response Cache\n\nThe response cache short-circuits the model when the same user question is asked again. The first time a question is asked, the agent calls the model normally and stores the assistant's reply. Subsequent identical questions skip the model entirely and replay the stored reply verbatim.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Cached assistant\n    instruction: You are a helpful assistant.\n    cache:\n      enabled: true          # required to turn the cache on\n      case_sensitive: false  # default: false (\"Hello\" == \"hello\")\n      trim_spaces: true      # default: false (\"  hello  \" == \"hello\")\n      path: ./cache.json     # optional: persist to disk; omit for in-memory\n```\n\n| Property         | Type    | Default | Description                                                                                                                                                                                                                       |\n| ---------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `enabled`        | boolean | `false` | Master switch. When `false` (or when the `cache` section is omitted), no caching is performed.                                                                                                                                     |\n| `case_sensitive` | boolean | `false` | When `true`, questions must match exactly (including case) to hit the cache.                                                                                                                                                       |\n| `trim_spaces`    | boolean | `false` | When `true`, leading and trailing whitespace is stripped from the question before it is compared.                                                                                                                                  |\n| `path`           | string  | _empty_ | When set, cache entries are persisted to a JSON file at the given path and reloaded on startup so the cache survives restarts. Relative paths resolve against the agent config directory. When empty, the cache lives in memory only. |\n\n**How it works**\n\n- The cache key is the latest user message in the session, normalized according to `case_sensitive` and `trim_spaces`.\n- On a hit, the cached reply is added to the session as the assistant message and stop hooks fire normally — the rest of the agent (tools, sub-agents, the model) is bypassed.\n- On a miss, the agent runs normally; the final assistant message produced by the first stop of the run is then stored under the question's key.\n- Only the response to the original user question of a run is cached; follow-up turns inside the same `RunStream` are not.\n\n**File-backed storage**\n\nWhen `path` is set, every `Store` rewrites the entire cache file. Writes are **atomic**: the new content is written to a sibling temp file, `fsync`'d, and renamed over the destination, so a concurrent reader (or a process that crashes mid-write) will always see either the previous content or the new content in full — never a partially written file. The parent directory is also `fsync`'d after the rename so the rename itself is durable.\n\n**Cross-process sharing**\n\nMultiple processes can share the same `path:` cache file safely. Every `Store` takes an exclusive advisory lock on a sibling `<path>.lock` file (POSIX `flock(2)` on Unix, `LockFileEx` on Windows), reloads the current on-disk state under the lock, merges the new entry, and writes back atomically. Two processes that store *different* keys at the same time both see their writes preserved on disk; the lock window is short (one read + one fsync'd write).\n\n`Lookup` watches the file's modification time and reloads the in-memory map when the file has advanced since its last load, so writes from a sibling process become visible without a restart. The `<path>.lock` sentinel file is created on first write and never deleted: removing it would let two processes lock different inodes and lose mutual exclusion.\n\n## Redacting Secrets\n\nThe `redact_secrets` flag is a single agent-level switch that scrubs accidentally leaked credentials, tokens, and private keys out of an agent's I/O. It wires up three complementary defenses:\n\n1. A `pre_tool_use` built-in hook that scrubs detected secrets from the **arguments of every tool call**, before the tool sees them.\n2. A `before_llm_call` built-in hook that scrubs the same patterns from **outgoing chat messages** — message content, multi-part text content, prior reasoning content, and the JSON-encoded arguments of any tool call still in the conversation — before they reach the model provider.\n3. A `tool_response_transform` built-in hook that scrubs **tool output at the source**, so the secret never reaches event consumers, the persisted session file, the `post_tool_use` hook input, or the next LLM call.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: A helpful assistant that scrubs secrets before they leak\n    instruction: |\n      You are a helpful assistant. If the user accidentally pastes a token,\n      do your best work without echoing the secret back.\n    redact_secrets: true\n    toolsets:\n      - type: shell\n```\n\nDetection uses the [portcullis](https://github.com/docker/portcullis) ruleset, which recognises common secret patterns including:\n\n- GitHub Personal Access Tokens (`ghp_*`, `gho_*`, `ghu_*`, `ghs_*`, `ghr_*`, fine-grained `github_pat_*`)\n- AWS access keys (`AKIA*`, `ASIA*`, …) and secret access keys\n- GitLab PATs (`glpat-*`), Hugging Face tokens (`hf_*`)\n- Stripe (`sk_live_*`, `pk_test_*`, …), Slack (`xoxb-*`, …), Shopify, Twilio, Discord, Atlassian, Mailchimp, SendGrid, and many more\n- JWTs, GCP service-account JSON, Heroku keys, Docker Hub PATs (`dckr_pat_*`)\n- PEM-encoded private keys (`-----BEGIN … PRIVATE KEY-----` blocks)\n\nEach detected span is replaced with the literal string `[REDACTED]`; the surrounding text is preserved so a redacted argument still looks like a legitimate flag (e.g. `--token=[REDACTED]`). Redaction is idempotent — applying it twice yields the same result.\n\n> [!NOTE]\n> **False positives vs. false negatives**\n>\n> False positives are extremely rare: every rule pairs a regex with a discriminating keyword, so plain English never trips detection. **False negatives are possible** — only patterns the ruleset recognises are scrubbed, so this is a defense-in-depth feature, not a substitute for keeping secrets out of the conversation in the first place. Pair it with a proper [secret manager](../../guides/secrets/index.md) for the credentials your agent actually needs.\n\n> [!NOTE]\n> **Equivalent hook entry**\n>\n> Setting `redact_secrets: true` on the agent is shorthand for auto-registering all three legs of the feature as hook entries. They share the _same_ built-in name (`type: builtin`, `command: redact_secrets`) on `pre_tool_use`, `before_llm_call`, and `tool_response_transform` respectively — the implementation dispatches on the hook event. You can spell them out by hand to scope a leg to a subset of tools (set `matcher:` to a regex), stack them with other rewriters in a specific order, or enable just one or two legs. See [`examples/redact_secrets_hooks.yaml`](https://github.com/docker/docker-agent/blob/main/examples/redact_secrets_hooks.yaml) for a complete manual wiring and the [Hooks reference](../hooks/index.md#available-built-ins) for the builtin's event coverage.\n\n## Welcome Message\n\nDisplay a message when users start a session:\n\n```yaml\nagents:\n  assistant:\n    model: openai/gpt-5\n    description: Development assistant\n    instruction: You are a helpful coding assistant.\n    welcome_message: |\n      👋 Welcome! I'm your development assistant.\n\n      I can help you with:\n      - Writing and reviewing code\n      - Running tests and debugging\n      - Explaining concepts\n\n      What would you like to work on?\n```\n\n## Deferred Tool Loading\n\nToolsets support `defer` to load tools on-demand and speed up agent startup. See [Deferred Tool Loading](../tools/index.md#deferred-tool-loading) for details.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Multi-purpose assistant\n    instruction: You have access to many tools.\n    toolsets:\n      - type: mcp\n        ref: docker:github-official\n        defer: true\n      - type: filesystem\n```\n\n## Fallback Configuration\n\nAutomatically switch to backup models when the primary fails:\n\n| Property   | Type   | Default | Description                                                |\n| ---------- | ------ | ------- | ---------------------------------------------------------- |\n| `models`   | array  | `[]`    | Fallback models to try in order                            |\n| `retries`  | int    | `2`     | Retries per model for 5xx errors. `-1` to disable.         |\n| `cooldown` | string | `1m`    | How long to stick with a fallback after a rate limit (429) |\n\n**Error handling:**\n\n- **Retryable** (same model with backoff): HTTP 5xx, 408, network timeouts\n- **Non-retryable** (skip to next model): HTTP 429, 4xx client errors\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    fallback:\n      models:\n        - openai/gpt-5\n        - google/gemini-3.5-flash\n      retries: 2\n      cooldown: 1m\n```\n\n## Named Commands\n\n> [!TIP]\n> **Full reference**\n>\n> This section covers the basics. For URL commands, agent-switching commands, reusable top-level `commands:` groups, and hiding commands with `--disable-commands`, see [Custom Commands](../commands/index.md).\n\nDefine reusable prompt shortcuts that can send prompts to the current agent, switch to a different sub-agent, or open a URL in the browser:\n\n> **Note:** Named slash commands execute immediately, even while the agent is processing another message. Unlike regular chat messages (which are queued), slash commands interrupt or direct the agent even while it is mid-response.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n      deploy: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n      \n      # Advanced format with agent switching\n      plan:\n        agent: planner  # Switch to the 'planner' agent\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"  # Optional: send this prompt after switching\n      \n      # Agent switching without instruction - forwards remaining text as prompt\n      review:\n        agent: reviewer  # Any text after /review is sent to the reviewer agent\n\n      # URL command - opens a link in the browser instead of messaging the agent\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n```\n\n### Command Formats\n\nCommands support three formats:\n\n1. **Simple string format**: The string becomes the instruction sent to the current agent\n\n   ```yaml\n   df: \"Check disk space\"\n   ```\n\n2. **Advanced object format**: Supports agent switching and optional instructions\n\n   ```yaml\n   plan:\n     agent: planner  # Required: name of any agent defined in the team\n     instruction: \"Plan: ${args.join(\\\" \\\")}\"  # Optional: prompt to send after switching\n     description: \"Switch to planning mode\"  # Optional: shown in help text\n   ```\n\n3. **URL format**: Opens a link in the browser instead of messaging the agent\n\n   ```yaml\n   docs:\n     url: https://docs.docker.com/          # Required: URL to open\n     description: \"Open the documentation\"  # Optional: shown in help text\n   ```\n\nWhen `agent` is set without `instruction`, any text typed after the slash command (e.g., `/plan build a web app`) is forwarded as a prompt to the target agent. The target agent can be **any agent defined in the team configuration** — it does not need to be listed in the current agent's `sub_agents` array.\n\n**Argument and expansion syntax**\n\nAn `instruction` string can reference the command's arguments and expand tool calls:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments, in the order the user typed them after the command\n- `${args.join(\" \")}` — all arguments joined into a single string\n- `${tool_name({...})}` — calls a tool and inlines its return value (any tool available to the agent)\n- `!tool_name(key=value)` — legacy tool-call form: calls a tool with plain `key=value` arguments and inlines its output\n\n### Agent-Switching Commands\n\nCommands with an `agent` field switch the active agent for that command's scope. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the appropriate specialist.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(\\\" \\\")}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n      # Simple prompt command (no switching)\n      status: \"Summarize what we have accomplished so far\"\n\n  planner:\n    model: openai/gpt-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\n**Agent-switching vs. `handoff`**\n\n| | Agent-switching command | `handoff` tool |\n| --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` |\n| **Session** | Stays in the same session | Stays in the same session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation |\n| **Return** | User must explicitly switch back | Target agent can chain to another agent |\n\n**Agent-switching vs. `transfer_task`**\n\n`transfer_task` launches a **sub-session**: the root agent sends a task, the child runs in isolation, and the result is returned to the root. The root agent stays in control and the child's work is never in the main conversation. Use `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n```bash\n# Run commands from the CLI\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\nCommands use JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation. Undefined variables expand to empty strings.\n\nThe same syntax is also expanded in agent and toolset instructions: `agents.<name>.instruction` and `toolsets[*].instruction` support `${env.X}` placeholders (with optional `||` defaults and ternary expressions). `agents.<name>.description` and `agents.<name>.welcome_message` also support it.\n\nNote that path-like fields (`working_dir`, `path`) primarily use a shell-style syntax (`$VAR`, `${VAR}`, `~`), and also accept `${env.X}` as an alias (though not richer JS expressions). See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full table.\n\n### URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — both standard web URLs and custom schemes such as `docker-desktop://` for deep links. URL commands are TUI-only — they have no effect when run from the CLI.\n\n```yaml\nagents:\n  root:\n    model: openai/gpt-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Read-Only Agents\n\nSet `readonly: true` on an agent to restrict all of its toolsets to tools that are annotated as read-only. Mutating tools are filtered out at load time — the agent cannot list or call them, even if the model hallucinates a call.\n\nYou can also set `readonly: true` on an individual toolset to restrict only that toolset while leaving others unrestricted.\n\n```yaml\nagents:\n  # Agent-level readonly: every toolset is restricted to read-only tools.\n  inspector:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only inspector that can explore but never modify.\n    instruction: Explore the project. Do not make changes.\n    readonly: true\n    toolsets:\n      - type: filesystem\n      - type: shell\n\n  # Toolset-level readonly: only the filesystem toolset is restricted;\n  # the shell toolset keeps all of its tools.\n  mixed:\n    model: anthropic/claude-sonnet-4-5\n    description: Read-only file access, full shell access.\n    instruction: You can read files and run any shell command.\n    toolsets:\n      - type: filesystem\n        readonly: true\n      - type: shell\n```\n\nSee [`examples/readonly.yaml`](https://github.com/docker/docker-agent/blob/main/examples/readonly.yaml) for a complete example.\n\n> [!NOTE]\n> **Which tools are read-only?**\n>\n> Whether a tool is read-only is determined by its `ReadOnlyHint` annotation. For built-in tools, read-only operations (list/read/search) carry the hint; mutating operations (write/delete/execute) do not. Custom and MCP tools expose the hint via their own annotations.\n\n## Complete Example\n\n```yaml\nmodels:\n  claude:\n    provider: anthropic\n    model: claude-sonnet-4-5\n    max_tokens: 64000\n\nagents:\n  root:\n    model: claude\n    description: Technical lead coordinating development\n    instruction: |\n      You are a technical lead. Analyze requests and delegate\n      to the right specialist. Always review work before responding.\n    welcome_message: \"👋 I'm your tech lead. How can I help today?\"\n    sub_agents: [developer, researcher]\n    add_date: true\n    add_environment_info: true\n    fallback:\n      models: [openai/gpt-5]\n    toolsets:\n      - type: think\n    commands:\n      review: \"Review all recent code changes for issues\"\n    hooks:\n      session_start:\n        - type: command\n          command: \"./scripts/setup.sh\"\n\n  developer:\n    model: claude\n    description: Expert software developer\n    instruction: Write clean, tested, production-ready code.\n    max_iterations: 30\n    toolsets:\n      - type: filesystem\n      - type: shell\n      - type: think\n      - type: todo\n\n  researcher:\n    model: openai/gpt-5\n    description: Web researcher with memory\n    instruction: Search for information and remember findings.\n    toolsets:\n      - type: mcp\n        ref: docker:duckduckgo\n      - type: memory\n        path: ./research.db\n```\n\n\n<!-- Skill/Rule: Subagent: index (content/manuals/ai/sandboxes/agents/_index.md) -->\n---\ntitle: Supported agents\nlinkTitle: Agents\nweight: 40\ndescription: AI coding agents supported by Docker Sandboxes.\nkeywords: docker sandboxes, ai agents, claude code, codex, cursor, gemini\n---\n\nDocker Sandboxes runs the following agents out of the box:\n\n- [Claude Code](claude-code/)\n- [Codex](codex/)\n- [Copilot](copilot/)\n- [Cursor](cursor/)\n- [Docker Agent](docker-agent/)\n- [Droid](droid/)\n- [Gemini](gemini/)\n- [Kiro](kiro/)\n- [OpenCode](opencode/)\n- [Shell](shell/) — agent-less sandbox for manual setup or testing\n\nWant to pre-install tools or customize an agent's environment?\nSee [Customize](../customize/).\n\n\n<!-- Skill/Rule: Subagent: claude-code (content/manuals/ai/sandboxes/agents/claude-code.md) -->\n---\ntitle: Claude Code\nweight: 10\ndescription: |\n  Use Claude Code in Docker Sandboxes with authentication, local models,\n  configuration, and YOLO mode for AI-assisted development.\nkeywords: docker sandboxes, claude code, anthropic, ai agent, sbx, local models, llmman, ollama\n---\n\nOfficial documentation: [Claude Code](https://code.claude.com/docs)\n\n## Quick start\n\nLaunch Claude Code in a sandbox by pointing it at a project directory:\n\n```console\n$ sbx run claude ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so `sbx run claude`\nfrom inside your project works too. To start Claude with a specific prompt:\n\n```console\n$ sbx run claude --name my-sandbox -- \"Add error handling to the login function\"\n```\n\nEverything after `--` is passed directly to Claude Code. You can also pipe in a\nprompt from a file with `-- \"$(cat prompt.txt)\"`.\n\n## Authentication\n\nClaude Code requires either an Anthropic API key or a Claude subscription.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set anthropic\n```\n\n**Claude subscription**: If no API key is set, use the `/login` command inside\nClaude Code to authenticate via OAuth.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.claude`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Remote control\n\nTo use Claude Code's `/remote-control` command inside a sandbox, turn on remote\ncontrol:\n\n```console\n$ sbx settings set claude.remoteControl true\n```\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nclaude --dangerously-skip-permissions\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--dangerously-skip-permissions` is\npreserved:\n\n```console\n$ sbx run claude -- -c   # runs claude --dangerously-skip-permissions -c\n```\n\nWhen the first argument is a bare word, such as the `agents` subcommand, it\nreplaces the defaults instead.\n\nSee the [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference)\nfor available options.\n\n## Agents view\n\nClaude Code's [agents view](https://code.claude.com/docs/en/agent-view)\nstarts background sessions that run tasks in parallel. Pair it with\n[clone mode](../workflows/git.md#clone-mode) to keep their changes inside the\nsandbox:\n\n```console\n$ sbx run --clone claude -- agents\n```\n\nThis invocation replaces the\n[default startup command](#default-startup-command), so it doesn't\ninclude `--dangerously-skip-permissions` and you can't switch to\nbypass-permissions mode inside the sandbox. To work around this, either\nuse Claude Code's auto mode or pass the flag explicitly:\n\n```console\n$ sbx run --clone claude -- --dangerously-skip-permissions agents\n```\n\nClaude Code may use branches or worktrees to keep changes from its background\nsessions separate. This depends on the task, Claude Code configuration, and\nproject instructions. The `--clone` flag doesn't control this behavior. Claude\nCode creates any branches and worktrees inside the sandbox, not in your host\ncheckout.\n\nTo review a branch created by a session, fetch the\n`sandbox-<sandbox-name>` remote from the host:\n\n```console\n$ git fetch sandbox-<sandbox-name>\n$ git diff main..sandbox-<sandbox-name>/<branch>\n```\n\nSee [Git workflows](../workflows/git.md) for clone-mode details.\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:claude-code`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n\n## Use a local model\n\nThe `--model` flag routes Claude Code's Anthropic API requests to a model\nserved on your host. This feature is experimental and isn't supported on\nWindows.\n\nEnable the feature:\n\n```console\n$ sbx settings set platform.allowExperimentalFeatures true\n$ sbx settings set feature.model true\n```\n\nTo use the bundled `llmman` model server, pass a GGUF model reference or short\nname:\n\n```console\n$ sbx run --model gemma4 claude\n```\n\nOn first use, `sbx` starts `llmman`, pulls the model, and leaves the server\nrunning on your host. Later sandboxes reuse the server and its model store.\n\nTo use an existing Ollama installation instead, set the provider to `ollama`:\n\n```console\n$ sbx run --model gemma4 --provider ollama claude\n```\n\nOllama must already be installed and running. `sbx` connects to it but doesn't\nstart or manage the Ollama process.\n\nYou can also change the model for an existing sandbox:\n\n```console\n$ sbx run --name <sandbox-name> --model <model-name>\n```\n\nChanging the model recreates the sandbox container. The workspace and\nkit-owned volumes persist.\n\nTo use Docker Model Runner instead, see\n[Run Claude Code in a Docker Sandbox with Docker Model Runner](/guides/claude-code-sandbox-model-runner/).\n\n\n<!-- Skill/Rule: Subagent: codex (content/manuals/ai/sandboxes/agents/codex.md) -->\n---\ntitle: Codex\nweight: 20\ndescription: |\n  Use OpenAI Codex in Docker Sandboxes with API key authentication and YOLO\n  mode configuration.\nkeywords: docker sandboxes, codex, openai, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Codex in a\nsandboxed environment.\n\nOfficial documentation: [Codex CLI](https://developers.openai.com/codex/cli)\n\n## Quick start\n\nCreate a sandbox and run Codex for a project directory:\n\n```console\n$ sbx run codex ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run codex\n```\n\n## Authentication\n\nIf you haven't stored an OpenAI credential, `sbx run codex` prompts you to\nauthenticate on your host before launching the sandbox. The flow runs on the\nhost, so credentials are never exposed inside the sandbox.\n\nTo set up authentication ahead of time, choose one of the following methods.\n\n**OAuth**: Start the OAuth flow on your host with:\n\n```console\n$ sbx secret set openai --oauth\n```\n\nThis opens a browser window for authentication and stores the resulting tokens\nin your OS keychain. The OAuth flow runs on the host, not inside the sandbox,\nso browser-based authentication works without any extra setup.\n\n**API key**: Store your OpenAI API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n```\n\nSee [Credentials](../configuration/credentials.md) for more details.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.codex`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncodex --dangerously-bypass-approvals-and-sandbox\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). A bare word — such as a prompt — replaces the\ndefaults instead, so lead with the flag to keep bypass mode:\n\n```console\n$ sbx run codex -- --dangerously-bypass-approvals-and-sandbox \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:codex`\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: copilot (content/manuals/ai/sandboxes/agents/copilot.md) -->\n---\ntitle: Copilot\nweight: 30\ndescription: |\n  Use GitHub Copilot in Docker Sandboxes with GitHub token authentication and\n  trusted folder configuration.\nkeywords: docker sandboxes, github copilot, ai agent, github token, sbx\n---\n\nThis guide covers authentication, configuration, and usage of GitHub Copilot\nin a sandboxed environment.\n\nOfficial documentation: [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli)\n\n## Quick start\n\nCreate a sandbox and run Copilot for a project directory:\n\n```console\n$ sbx run copilot ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run copilot\n```\n\n## Authentication\n\nCopilot requires a GitHub token with Copilot access. Store your token using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set github --command 'gh auth token'\n```\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCopilot is configured to trust the workspace directory by default, so it\noperates without repeated confirmations for workspace files.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncopilot --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run copilot -- -p \"review this PR\"   # runs copilot --yolo -p \"review this PR\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:copilot`\n\nPreconfigured to trust the workspace directory.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: cursor (content/manuals/ai/sandboxes/agents/cursor.md) -->\n---\ntitle: Cursor\nweight: 40\ndescription: |\n  Use Cursor in Docker Sandboxes with API key or proxy-managed OAuth\n  authentication.\nkeywords: docker sandboxes, cursor, cursor agent, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Cursor in a\nsandboxed environment.\n\nOfficial documentation: [Cursor CLI](https://cursor.com/cli)\n\n## Quick start\n\nCreate a sandbox and run Cursor for a project directory:\n\n```console\n$ sbx run cursor ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run cursor\n```\n\n## Authentication\n\nCursor supports two authentication methods: an API key or OAuth.\n\n**API key**: Store your Cursor API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set cursor\n```\n\n**OAuth**: If no API key is set, Cursor prompts you to sign in interactively\non first run. The proxy intercepts the token exchange with\n`api2.cursor.sh/auth/poll`, so credentials are managed by the host and aren't\nstored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.cursor`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nCursor reads `AGENTS.md` from the workspace for agent-specific instructions.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ncursor-agent --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run cursor -- -p \"refactor this\"   # runs cursor-agent --yolo -p \"refactor this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:cursor-agent-docker`\n\nPreconfigured with HTTP/1.1 and server-sent events for agent traffic so\nrequests flow through the host proxy. Authentication state is persisted across\nsandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: docker-agent (content/manuals/ai/sandboxes/agents/docker-agent.md) -->\n---\ntitle: Docker Agent\nweight: 50\ndescription: |\n  Use Docker Agent in Docker Sandboxes with multi-provider authentication\n  supporting OpenAI, Anthropic, and more.\nkeywords: docker sandboxes, docker agent, openai, anthropic, sbx\n---\n\nOfficial documentation: [Docker Agent](/manuals/ai/docker-agent/_index.md)\n\n## Quick start\n\nCreate a sandbox and run Docker Agent for a project directory:\n\n```console\n$ sbx run docker-agent ~/my-project\n```\n\nThe workspace parameter defaults to the current directory, so\n`sbx run docker-agent` from inside your project works too.\n\n## Authentication\n\nDocker Agent supports multiple providers. Store keys for the providers you want\nto use with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set nebius\n$ sbx secret set mistral\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. Docker Agent detects\navailable credentials and routes requests to the appropriate provider.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ndocker-agent run --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`). When the first argument is a bare word — such\nas the `run` subcommand or a config file — it replaces the defaults, so include\n`run --yolo` yourself:\n\n```console\n$ sbx run docker-agent -- run --yolo agent.yml\n```\n\n## Base image\n\nThe sandbox uses `docker/sandbox-templates:docker-agent`. See\n[Templates](../customize/templates.md) to build your own image on top of\nthis base.\n\n\n<!-- Skill/Rule: Subagent: droid (content/manuals/ai/sandboxes/agents/droid.md) -->\n---\ntitle: Droid\nweight: 60\ndescription: |\n  Use Droid in Docker Sandboxes with API key or OAuth authentication.\nkeywords: docker sandboxes, droid, factory, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Droid, an AI\ncoding agent by Factory, in a sandboxed environment.\n\nOfficial documentation: [Droid](https://docs.factory.ai/)\n\n## Quick start\n\nCreate a sandbox and run Droid for a project directory:\n\n```console\n$ sbx run droid ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run droid\n```\n\n## Authentication\n\nDroid requires a [Factory account](https://factory.ai). Both authentication\nmethods authenticate you to Factory's service directly — unlike other agents\nwhere you supply a model provider key, Factory manages model access through\nyour Factory account.\n\n**API key**: Store your Factory API key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set droid\n```\n\n**OAuth**: If no API key is set, Droid prompts you to authenticate\ninteractively on first run. The proxy handles the OAuth flow, so credentials\naren't stored inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\n### Default startup command\n\nThe sandbox runs `droid` with no implicit flags. Args after `--` are passed\nstraight through:\n\n```console\n$ sbx run droid -- exec \"fix the build\"\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:droid-docker`\n\nPreconfigured to run without approval prompts. Authentication state is\npersisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: gemini (content/manuals/ai/sandboxes/agents/gemini.md) -->\n---\ntitle: Gemini\nweight: 70\ndescription: |\n  Use Google Gemini in Docker Sandboxes with proxy-managed authentication and\n  API key configuration.\nkeywords: docker sandboxes, gemini, google, ai agent, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Google Gemini in\na sandboxed environment.\n\nOfficial documentation: [Gemini CLI](https://geminicli.com/docs/)\n\n## Quick start\n\nCreate a sandbox and run Gemini for a project directory:\n\n```console\n$ sbx run gemini ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run gemini\n```\n\n## Authentication\n\nGemini requires either a Google API key or a Google account with Gemini access.\n\n**API key**: Store your key using\n[stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set google\n```\n\n**Google account**: If no API key is set, Gemini prompts you to sign in\ninteractively when it starts. Interactive authentication is scoped to the\nsandbox and doesn't persist if you remove and recreate it.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host, such as\n`~/.gemini`. Only project-level configuration in the working directory is\navailable inside the sandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nThe sandbox disables Gemini's built-in sandbox tool (since the sandbox itself\nprovides isolation).\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\ngemini --yolo\n```\n\nArguments after `--` are added after the default flags when the first one is\nitself a flag (begins with `-`), so `--yolo` is preserved:\n\n```console\n$ sbx run gemini -- -p \"explain this\"   # runs gemini --yolo -p \"explain this\"\n```\n\nWhen the first argument is a bare word — a subcommand or prompt — it replaces\nthe defaults instead.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:gemini`\n\nGemini is configured to disable its built-in OAuth flow. Authentication is\nmanaged through the proxy with API keys.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: kiro (content/manuals/ai/sandboxes/agents/kiro.md) -->\n---\ntitle: Kiro\nweight: 80\ndescription: |\n  Use Kiro in Docker Sandboxes with device flow authentication for interactive\n  AI-assisted development.\nkeywords: docker sandboxes, kiro, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of Kiro in a\nsandboxed environment.\n\nOfficial documentation: [Kiro CLI](https://kiro.dev/docs/cli/)\n\n## Quick start\n\nCreate a sandbox and run Kiro for a project directory:\n\n```console\n$ sbx run kiro ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run kiro\n```\n\nOn first run, Kiro prompts you to authenticate using device flow.\n\n## Authentication\n\nKiro uses device flow authentication, which requires interactive login through\na web browser. This method provides secure authentication without storing API\nkeys directly.\n\n### Device flow login\n\nWhen you first run Kiro, it prompts you to authenticate:\n\n1. Kiro displays a URL and a verification code\n2. Open the URL in your web browser\n3. Enter the verification code\n4. Complete the authentication flow in your browser\n5. Return to the terminal - Kiro proceeds automatically\n\nThe authentication session is persisted in the sandbox and doesn't require\nrepeated login unless you destroy and recreate the sandbox.\n\n### Manual login\n\nYou can trigger the login flow manually:\n\n```console\n$ sbx run kiro --name <sandbox-name> -- login --use-device-flow\n```\n\nThis command initiates device flow authentication without starting a coding\nsession.\n\n### Authentication persistence\n\nKiro stores authentication state in `~/.local/share/kiro-cli/data.sqlite3`\ninside the sandbox. This database persists as long as the sandbox exists. If\nyou destroy the sandbox, you'll need to authenticate again when you recreate\nit.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nKiro requires minimal configuration. The agent runs with trust-all-tools mode\nby default, which lets it execute commands without repeated approval prompts.\n\n### Default startup command\n\nWithout extra args, the sandbox runs:\n\n```text\nkiro chat --trust-all-tools\n```\n\nWhen the first argument after `--` is a flag (begins with `-`), it's added\nafter the defaults — for example, `sbx run kiro -- --resume` runs\n`kiro chat --trust-all-tools --resume`. When the first argument is a bare word,\nit replaces the defaults, which is why `sbx run kiro -- login --use-device-flow`\nruns the login subcommand on its own. To run `chat` with extra arguments of\nyour own, include the subcommand:\n\n```console\n$ sbx run kiro -- chat --trust-all-tools --resume\n```\n\n## Base image\n\nTemplate: `docker/sandbox-templates:kiro`\n\nAuthentication state is persisted across sandbox restarts.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: opencode (content/manuals/ai/sandboxes/agents/opencode.md) -->\n---\ntitle: OpenCode\nweight: 90\ndescription: |\n  Use OpenCode in Docker Sandboxes with multi-provider authentication and TUI\n  interface for AI development.\nkeywords: docker sandboxes, opencode, ai agent, authentication, sbx\n---\n\nThis guide covers authentication, configuration, and usage of OpenCode in a\nsandboxed environment.\n\nOfficial documentation: [OpenCode](https://opencode.ai/docs)\n\n## Quick start\n\nCreate a sandbox and run OpenCode for a project directory:\n\n```console\n$ sbx run opencode ~/my-project\n```\n\nThe workspace parameter is optional and defaults to the current directory:\n\n```console\n$ cd ~/my-project\n$ sbx run opencode\n```\n\nOpenCode launches a TUI (text user interface) where you can select your\npreferred LLM provider and interact with the agent.\n\n## Authentication\n\nOpenCode supports multiple providers. Store keys for the providers you want to\nuse with [stored secrets](../configuration/credentials.md#stored-secrets):\n\n```console\n$ sbx secret set openai\n$ sbx secret set anthropic\n$ sbx secret set google\n$ sbx secret set xai\n$ sbx secret set groq\n$ sbx secret set aws\n$ sbx secret set openrouter\n```\n\nYou only need to configure the providers you want to use. OpenCode detects\navailable credentials and offers those providers in the TUI.\n\n### OpenCode Zen API keys\n\nOpenCode Zen API keys aren't part of the built-in OpenCode credentials that\n`sbx secret set` supports. To use an OpenCode Zen API key, store it as a\n[custom secret](../configuration/credentials.md#custom-secrets):\n\nSet the `OPENCODE_API_KEY` environment variable on the host, then store it:\n\n```console\n$ sbx secret set-custom \\\n    --host opencode.ai \\\n    --env OPENCODE_API_KEY \\\n    --value \"$OPENCODE_API_KEY\"\n```\n\nCustom secrets keep the real key in the host secret store. The sandbox receives\n`OPENCODE_API_KEY` as a placeholder, and the host-side proxy replaces that\nplaceholder with the real key on requests to `opencode.ai`.\n\nOpenCode Zen also requires network access to `opencode.ai`:\n\n```console\n$ sbx policy allow network opencode.ai:443\n```\n\nIf you add a global custom secret, recreate existing OpenCode sandboxes so the\nnew environment variable is available inside the sandbox.\n\n## Configuration\n\nSandboxes don't pick up user-level configuration from your host. Only\nproject-level configuration in the working directory is available inside the\nsandbox. See\n[Why doesn't the sandbox use my user-level agent configuration?](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration)\nfor workarounds.\n\nOpenCode uses a TUI interface and doesn't require extensive configuration\nfiles. The agent prompts you to select a provider when it starts, and you can\nswitch providers during a session.\n\n### Default startup command\n\nThe sandbox runs `opencode` with no implicit flags. Args after `--` are passed\nstraight through. For example, to resume an existing session:\n\n```console\n$ sbx run opencode -- -s <session-id>\n```\n\n### TUI mode\n\nOpenCode launches in TUI mode by default. The interface shows:\n\n- Available LLM providers (based on configured credentials)\n- Current conversation history\n- File operations and tool usage\n- Real-time agent responses\n\nUse keyboard shortcuts to navigate the interface and interact with the agent.\n\n## Base image\n\nTemplate: `docker/sandbox-templates:opencode`\n\nOpenCode supports multiple LLM providers with automatic credential injection\nthrough the sandbox proxy.\n\nSee [Customize](../customize/) to pre-install tools or customize this\nenvironment.\n\n\n<!-- Skill/Rule: Subagent: shell (content/manuals/ai/sandboxes/agents/shell.md) -->\n---\ntitle: Shell\nweight: 100\ndescription: Run an agent-less sandbox with a Bash login shell for manual setup, testing custom agent implementations, or inspecting a running environment.\nkeywords: sandboxes, sbx, shell, agent, manual setup, testing\n---\n\n`sbx run shell` drops you into a Bash login shell inside a sandbox with no\npre-installed agent binary. It's useful for installing and configuring\nagents manually, testing custom implementations, or inspecting a running\nenvironment.\n\n```console\n$ sbx run shell ~/my-project\n```\n\nThe workspace path defaults to the current directory. To run a one-off\ncommand instead of an interactive shell, pass it after `--`:\n\n```console\n$ sbx run shell -- -c \"echo 'Hello from sandbox'\"\n```\n\n## Default startup command\n\nWithout extra args, the sandbox runs `bash -l`. When the first argument after\n`--` is a flag (begins with `-`), it's added after `-l`, so login-shell\nbehavior is preserved:\n\n```console\n$ sbx run shell -- -c \"echo hi\"   # runs bash -l -c \"echo hi\"\n```\n\nWhen the first argument is a bare word, it replaces `-l` instead.\n\nStore credentials using [stored secrets](../configuration/credentials.md#stored-secrets)\nbefore running the sandbox. The proxy injects them into outbound API requests;\ncredentials are never stored inside the VM:\n\n```console\n$ sbx secret set anthropic\n$ sbx secret set openai\n```\n\nOnce inside the shell, you can install agents using their standard methods,\nfor example `npm install -g @continuedev/cli`. For complex setups, build a\n[custom template](../customize/templates.md) instead of installing\ninteractively each time.\n\n## Base image\n\nThe shell sandbox uses the `shell` base image — the common base environment\nwithout a pre-installed agent.\n\n\n<!-- Skill/Rule: Command: /index (_vendor/github.com/docker/docker-agent/docs/configuration/commands/index.md) -->\n---\ntitle: \"Custom Commands\"\ndescription: \"Define slash commands that send prompts, open URLs, or switch agents, and reuse them across agents with top-level command groups.\"\nkeywords: docker agent, ai agents, configuration, yaml, custom commands, slash commands\nlinkTitle: \"Custom Commands\"\nweight: 55\ncanonical: https://docs.docker.com/ai/docker-agent/configuration/commands/\n---\n\n_Define slash commands that send prompts, open URLs, or switch agents._\n\n## What Slash Commands Are\n\nA slash command is a named shortcut a user types in the TUI (`/df`, `/deploy`, `/plan`) or on the CLI (`docker agent run agent.yaml /df`) instead of typing out a full prompt. Every agent can declare its own commands under `commands:`, and top-level `commands:` groups let multiple agents share the same set without duplicating them.\n\nUnlike regular chat messages — which are queued while the agent is busy — slash commands (both built-in and named) execute immediately, even mid-response.\n\nCommands come in three shapes:\n\n| Shape | What it does |\n| --- | --- |\n| [Prompt command](#prompt-commands) | Sends a prompt to the current agent |\n| [URL command](#url-commands) | Opens a link in the user's browser (full TUI only) |\n| [Agent-switching command](#agent-switching-commands) | Switches the active agent, optionally with a prompt (full TUI and CLI) |\n\n> [!IMPORTANT]\n> **Behavior differs by frontend**\n>\n> `url` and `agent` are only fully honored in the **full TUI**, which checks `url` before `agent` (a URL command opens the browser and stops there; an agent-switching command switches before sending any instruction). The **lean TUI** doesn't special-case either field — it only resolves a command's expanded text and sends it as a chat message, so a URL-only command silently sends whatever trailing text followed the slash (often nothing, opening no browser) and an agent-switching command sends its instruction to the *current* agent instead of the target. The **CLI** (`docker agent run agent.yaml /command`) switches agents like the full TUI, but has no browser to open, so `url` has no effect there. The **HTTP API** (`POST /api/sessions/:id/agent/:agent`) resolves agent-switching commands server-side: if the message content starts with a slash command whose `agent` field is set, the active agent is switched and the message is rewritten before the turn runs. Prompt-only and URL commands are not resolved server-side and pass through to the model unchanged.\n\n## Prompt Commands\n\nThe simplest form: a string value that becomes the instruction sent to the current agent.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: A system administrator assistant.\n    instruction: You are a system administrator.\n    commands:\n      df: \"Check how much free space I have on my disk\"\n      logs: \"Show me the last 50 lines of system logs\"\n      greet: \"Say hello to ${env.USER}\"\n```\n\nFor more control, use the object form with an `instruction:` field, plus an optional `description:` shown in completion dialogs and help text:\n\n```yaml\ncommands:\n  deploy:\n    description: \"Deploy the application to staging\"\n    instruction: \"Deploy ${env.PROJECT_NAME || 'app'} to ${env.ENV || 'staging'}\"\n```\n\nCommands support JavaScript template literal syntax (`${env.VAR}`) for environment variable interpolation, with optional `||` defaults and ternary expressions — the same syntax as agent `instruction` and `description`. Undefined variables expand to the empty string. See [Variable Expansion in Config Fields](../overview/index.md#variable-expansion-in-config-fields) for the full picture.\n\nPrompt commands can also reference the text typed after the slash and call tools, using the same `${...}` expansion engine as `${env.VAR}`:\n\n- `${args[0]}`, `${args[1]}`, … — individual positional arguments (whitespace-tokenized; quoted substrings keep their spaces together).\n- `${args}` or `${args.join(\" \")}` — the full argument list.\n- `${tool_name({key: value, ...})}` — calls an agent tool and inlines its output. JS expressions are evaluated before tool commands, so tool output is never itself re-evaluated as JS.\n- `` !tool_name(key=value) `` — legacy bang syntax for the same tool-call inlining; still supported alongside `${tool_name({...})}`.\n\nIf `instruction` uses none of the `${args...}` placeholders, any text typed after the slash is appended to the resolved instruction automatically.\n\n```yaml\ncommands:\n  fix:\n    description: \"Fix a file, with optional extra options\"\n    instruction: \"Fix the file ${args[0]} with options ${args[1]}\"\n  run:\n    description: \"Run a command with all the typed arguments\"\n    instruction: 'Run command with args: ${args.join(\" \")}'\n  lint:\n    description: \"Show the current lint output\"\n    instruction: 'Lint: ${shell({cmd: \"task lint\"})}'\n```\n\n```bash\n# Run commands from the CLI too\n$ docker agent run agent.yaml /df\n$ docker agent run agent.yaml /greet\n$ docker agent run agent.yaml /fix main.go --verbose\n$ PROJECT_NAME=myapp ENV=production docker agent run agent.yaml /deploy\n```\n\n## URL Commands\n\nA command with a `url` field opens that URL in the user's default browser instead of sending a prompt to the agent. Any URI scheme the OS knows how to dispatch works — standard web URLs and custom schemes such as `docker-desktop://` for deep links.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: An agent with handy URL shortcuts.\n    instruction: You are a helpful assistant.\n    commands:\n      feedback:\n        description: \"Open the feedback site for this session\"\n        url: https://example.com/feedback?session={{session_id}}\n      docs:\n        description: \"Open the documentation\"\n        url: https://docs.docker.com/\n      desktop:\n        description: \"Open this session in Docker Desktop\"\n        url: docker-desktop://dashboard/session/{{session_id}}\n```\n\nThe `{{session_id}}` token is replaced at invocation time with the current session ID (URL-query-escaped so it can't break the URL or inject extra query parameters), letting a command deep-link to something scoped to the conversation. This token deliberately uses `{{...}}` rather than the `${...}` JS-expansion syntax, since the session ID is only known at dispatch time.\n\nURLs are validated before being handed to the OS opener: a parseable URL with a non-empty scheme is required, and flag-like inputs (those starting with `-`) are rejected to prevent argument injection.\n\n> [!NOTE]\n> **Full TUI only**\n>\n> URL commands only open a browser in the full TUI. The CLI and lean TUI don't check the `url` field at all, so `docker agent run agent.yaml /docs` never opens a browser there — but the command is still dispatched: its resolved text (usually empty, for a URL-only command) is sent as a prompt and can trigger a model turn.\n\nSee [`examples/url_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/url_commands.yaml) for a complete example.\n\n## Agent-Switching Commands\n\nA command with an `agent` field switches the active agent for the rest of the conversation. This is useful for building workflow shortcuts where `/plan`, `/review`, `/deploy` each route the user to the right specialist.\n\n```yaml\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Main assistant\n    instruction: You are a project coordinator.\n    sub_agents: [planner, reviewer]\n    commands:\n      # Switch to planner with a pre-filled prompt\n      plan:\n        agent: planner\n        instruction: \"Create a detailed plan for: ${args.join(' ')}\"\n      # Switch to reviewer; any text after /review is forwarded\n      review:\n        agent: reviewer\n\n  planner:\n    model: anthropic/claude-sonnet-4-5\n    description: Planning specialist\n    instruction: You create detailed project plans.\n\n  reviewer:\n    model: anthropic/claude-sonnet-4-5\n    description: Code review specialist\n    instruction: You review code and suggest improvements.\n```\n\nWhen `agent` is set **without** `instruction`, any text typed after the slash command (e.g. `/review fix the auth bug`) is forwarded as a prompt to the target agent. When both are set, the agent is switched first, then the instruction is sent to the new agent. Either way, the target can be **any agent defined in the team**, not just one of the current agent's own `sub_agents` — `sub_agents` above is shown because `planner` and `reviewer` also happen to be delegation targets, not because `agent:` requires it.\n\nAgent switching stays in the same session — the target agent sees the full conversation history, and the user must explicitly switch back (there's no automatic return). This is different from the two other ways agents hand off work:\n\n| | Agent-switching command | `handoff` tool | `transfer_task` |\n| --- | --- | --- | --- |\n| **Trigger** | User runs `/command` | Model calls `handoff()` | Model calls `transfer_task()` |\n| **Session** | Stays in the same session | Stays in the same session | Launches an isolated sub-session |\n| **History** | Target agent sees full conversation | Target agent sees full conversation | Child runs in isolation; only the result returns |\n| **Control** | User must explicitly switch back | Target agent can chain to another agent | Root agent stays in control |\n\nUse `transfer_task` (via `sub_agents`) when you want delegation with a clean result; use agent-switching commands when you want to *become* a different agent for the rest of the conversation.\n\nSee [`examples/agent_switching_commands.yaml`](https://github.com/docker/docker-agent/blob/main/examples/agent_switching_commands.yaml) for a complete example.\n\n## Reusable Command Groups\n\nRepeated command sets across agents can be hoisted into the top-level `commands:` section and pulled in by name with `use_commands:` — the same reuse pattern as `mcps:` for MCP servers and `toolsets:` for shared toolsets.\n\n```yaml\ncommands:\n  ci:\n    deploy: \"Deploy the application\"\n    test: \"Run the test suite\"\n\nagents:\n  root:\n    model: anthropic/claude-sonnet-4-5\n    description: Lead developer\n    instruction: You are the lead developer. Coordinate the team.\n    use_commands: [ci]      # reuse the \"ci\" command group\n    commands:\n      lint: \"Run the linter\"  # inline command, merged in (wins on conflict)\n\n  docs-writer:\n    model: anthropic/claude-sonnet-4-5\n    description: Documentation writer\n    instruction: You write and maintain the project documentation.\n    use_commands: [ci]      # same group, reused without duplication\n```\n\nAn agent's own inline `commands:` entries take precedence over merged `use_commands:` entries on name conflicts. See [`examples/shared-commands-skills.yaml`](https://github.com/docker/docker-agent/blob/main/examples/shared-commands-skills.yaml) for a complete example that also covers the equivalent `skills:` / `use_skills:` pattern.\n\n## Hiding Commands\n\nUse `--disable-commands` to hide and disable specific slash commands in the TUI — built-in ones (`/cost`, `/eval`, `/model`, …) or your own named ones. Accepts a comma-separated list; the leading slash is optional and matching is case-insensitive.\n\n```bash\n$ docker agent run agent.yaml --disable-commands=\"/cost,/eval,/model\"\n```\n\nThis is useful for shipping a distributed agent with a narrower command surface — for example, hiding `/model` so a published agent always runs its intended model.\n\n## Built-in Commands\n\nThe TUI ships its own slash commands (`/new`, `/compact`, `/sessions`, `/settings`, …) alongside whatever an agent defines. See [Slash Commands](../../features/tui/index.md#slash-commands) in the TUI reference for the full list.\n\n## Command Configuration Reference\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `description` | string | Shown in completion dialogs and help text. |\n| `instruction` | string | The prompt sent to the agent. Supports argument expansion (`${args[0]}`, `${args.join(\" \")}`, …), tool calls (`${tool_name({...})}`), and the legacy bang syntax `!tool_name(...)`. |\n| `agent` | string | Name of an agent in the team to switch to when this command is invoked — any agent in the team's `agents:` map, not just one of the current agent's `sub_agents`. When set without `instruction`, any text typed after the slash command is forwarded as a prompt to the target agent. |\n| `url` | string | URL to open in the user's default browser when this command is invoked, instead of sending a prompt to the agent (full TUI only — see [URL Commands](#url-commands)). The token `{{session_id}}` is replaced at invocation time with the current session ID (URL-query-escaped). |\n\n`instruction` and `agent` can be combined (the agent is switched first, then the instruction is sent to the new agent). In the full TUI, if `url` is set, it takes precedence over `agent` and `instruction` — the command only opens the browser; the lean TUI and CLI don't check `url` at all, so a URL-only command instead sends its (usually empty) resolved text as a prompt. See [Behavior differs by frontend](#what-slash-commands-are) above. The simple string form is shorthand for `{ instruction: \"...\" }`.\n\n\n</agent_rules>"}