docker agent is Docker’s agent runtime (the CLI plugin built on the open-source cagent project — you’ll see that name in ~/.config/cagent). Its whole contract is a single YAML file: one block describing agents, one describing models. Nothing stops you from pointing the model block at localhost, which is the interesting part — the same declarative agent, tools and all, running against a model on your own machine.

The whole file

agents:
  root:
    description: Software Engineer
    instruction: You are a helpful assistant.
    model: omlx

    commands:
      cheese: What is the best French cheese?

    # Optional tools – adjust to your taste
    toolsets:
      - type: filesystem
      - type: shell
      - type: think
      - type: script
        shell:
          get_ip:
            cmd: "curl -s https://ipinfo.io | jq -r .ip"
            description: "Get public IP"

models:
  omlx:
    provider: openai
    model: gemma-4-e4b-it-4bit
    base_url: http://localhost:8000/v1
    token_key: OMLX_API_KEY

Run it:

docker agent run ./config.yaml

That’s it — no image to build, no compose file. The TUI opens and you’re talking to a model served from your own laptop.

What each block does

agents.root — root is the entry point. A file with a single agent is the common case; add siblings and the extra agents become delegates the root can hand work to. description is what other agents see, instruction is the system prompt the model sees.

model: omlx — a reference into the models map, not a model name. The indirection is what makes the config portable: swap the omlx definition for a hosted provider and every agent follows.

commands — named prompt shortcuts, exposed as slash commands in the session. cheese above becomes /cheese, which expands to its instruction and sends it. Handy for the three or four prompts you retype every day (/review-diff, /explain-this-error).

toolsets — what the agent is allowed to do. filesystem, shell and think are built-in types; script is the escape hatch that turns any shell one-liner into a named tool with a description the model can reason about. docker agent toolsets lists all the built-in types (there are 26, including git, memory, rag, mcp and fetch).

models.omlx — provider: openai here means the OpenAI wire protocol, not OpenAI the company. Any server that speaks /v1/chat/completions works: mlx-omni-server (the omlx in this config), llama.cpp’s server, LM Studio, vLLM, Ollama’s compat endpoint. The base_url must include the /v1 suffix.

Verify before you trust it

Two subcommands are worth knowing. debug toolsets resolves the config and prints the exact tools the model will be offered:

docker agent debug toolsets ./config.yaml
12 tool(s) for root:
 + directory_tree - Get a recursive tree view of files and directories as a JSON structure.
 + edit_file - Make line-based edits to a text file. ...
 + list_directory - Get a detailed listing of all files and directories in a specified path.
 + read_file - Read the contents of a file from the file system. ...
 + read_multiple_files - Read the contents of multiple files simultaneously.
 + search_files_content - Searches for text or regex patterns ...
 + write_file - Create a new file or completely overwrite an existing file with new content.
 + create_directory - Create one or more new directories or nested directory structures.
 + remove_directory - Remove one or more empty directories.
 + shell - Executes the given shell command with zsh on macOS.
 + think - Use the tool to think about something. ...
 + get_ip - Get public IP

Note that - type: filesystem alone expanded to nine tools, write_file and edit_file among them. On a small local model, that’s a lot of surface area — drop the toolsets you don’t need rather than pasting the list from a tutorial.

And debug config prints the canonical form, with every default filled in — the fastest way to see what the parser actually made of your file:

docker agent debug config ./config.yaml

--dry-run is the third useful one: it initializes the agent, validates everything, and exits without calling the model.

Two gotchas

token_key is optional. It names an environment variable, and if you declare it, it must be set — otherwise the run aborts before reaching the model:

The following environment variables must be set:
 - OMLX_API_KEY

Most local servers don’t check the key at all, so you can simply omit the line. Keep it only if your server enforces one (then docker agent setup will store the value in ~/.config/cagent/.env for you, or use --env-from-file).

Custom script tools can take typed parameters. get_ip above takes none, which is why it’s a one-liner. For anything parameterized, declare args and the model fills them in:

      - type: script
        shell:
          dig_host:
            cmd: "dig +short $HOST"
            description: "Resolve a hostname to an IP"
            args:
              HOST:
                type: string
                description: "hostname to resolve"
            required: [HOST]

If you don’t have a local server yet

Docker Model Runner skips the whole models block — it pulls and serves the model itself:

docker agent run --model dmr/ai/qwen3 ./config.yaml

That needs Model Runner enabled in Docker Desktop (the agent talks to it on 127.0.0.1:12434, and says so loudly if it isn’t listening); the model is pulled on first use, and docker model ls shows what you already have.

Or run docker agent setup for an interactive walk through providers and local endpoints. Either way, the agent definition stays the same file — which is the point. The YAML is the assistant; where the tokens come from is a swappable detail.

Same idea, different front-end: opencode.json wires a terminal coding agent to the same local endpoint, if what you want is a coding TUI rather than a declarative agent.

Tested with docker agent v1.122.0.