These docs track the main branch and may describe unreleased features. The stable documentation lives at docs.docker.com.

HCL Configuration

Write docker-agent configs in HCL instead of YAML. It maps to the same docker-agent schema and validation rules.

docker-agent supports .hcl config files anywhere it supports .yaml or .yml files. HCL is useful if you prefer labeled blocks, less punctuation, and heredocs for long prompts.

Same config model, different syntax

YAML and HCL are just two syntaxes for the same docker-agent configuration model. docker-agent converts HCL to the equivalent YAML structure internally, then runs the normal schema validation and loading pipeline.

Minimal Example

#!/usr/bin/env docker agent run

agent "root" {
  model       = "openai/gpt-5"
  description = "A helpful assistant"
  instruction = <<-EOT
  You are a helpful assistant.
  EOT

  toolset "think" {}
}

Run it exactly like a YAML config:

$ docker agent run agent.hcl
$ docker agent run --exec agent.hcl "Summarize this repository"
$ docker agent serve api ./agents/   # directories may mix .yaml, .yml, and .hcl files
See also

HCL changes the syntax, not the meaning of fields. For what each field does, see Agent Config, Model Config, and Tool Config.

YAML vs HCL

These two configs are equivalent:

models:
  claude:
    provider: anthropic
    model: claude-sonnet-4-5

agents:
  root:
    model: claude
    description: Coding assistant
    instruction: You help with software development.
    toolsets:
      - type: filesystem
      - type: shell
model "claude" {
  provider = "anthropic"
  model    = "claude-sonnet-4-5"
}

agent "root" {
  model       = "claude"
  description = "Coding assistant"
  instruction = "You help with software development."

  toolset "filesystem" {}
  toolset "shell" {}
}

Core Conventions

HCL follows a few simple mapping rules:

HCL syntax YAML shape
agent "root" { ... } agents.root
model "claude" { ... } models.claude
provider "team" { ... } providers.team
mcp "github" { ... } mcps.github
rag "docs" { ... } rag.docs
command "fix" { ... } inside an agent commands.fix
toolset "shell" {} list item in toolsets with type: shell
metadata { ... }, permissions { ... } singleton blocks with the same top-level name

Top-level keyed maps become labeled blocks

In YAML, several sections are maps keyed by name. In HCL, those become labeled blocks:

model "claude" {
  provider = "anthropic"
  model    = "claude-sonnet-4-5"
}

agent "root" {
  model       = "claude"
  description = "Primary assistant"
  instruction = "You are helpful."
}

The supported top-level labeled blocks are:

The supported top-level singleton blocks are:

Toolsets use the block label as type

Instead of writing list entries with type: ..., HCL uses a toolset block whose label becomes the tool type:

agent "root" {
  model       = "openai/gpt-5"
  description = "Dev assistant"
  instruction = "You can inspect and modify code."

  toolset "filesystem" {}

  toolset "mcp" {
    ref = "docker:github-official"
  }
}

Commands use labeled blocks too

Agent commands are often nicer to write in HCL because each command gets its own block:

agent "root" {
  model       = "openai/gpt-5"
  description = "Build helper"
  instruction = "You help with builds."

  command "fix-lint" {
    description = "Fix lint issues"
    instruction = "Run the linter, then fix any problems."
  }
}

Strings and Heredocs

Use quoted strings for short values and heredocs for long prompts, welcome messages, or embedded JSON.

agent "root" {
  model       = "openai/gpt-5"
  description = "Friendly assistant"

  instruction = <<-EOT
  You are a helpful assistant.

  Keep answers concise and practical.
  EOT
}

Escaping literal ${...}

HCL treats ${...} inside strings and heredocs as template interpolation. If you need the literal text ${...} in your prompt, escape it as $${...}.

This matters for command prompts that intentionally show docker-agent template snippets:

command "fix-lint" {
  instruction = <<-EOT
  Run the linter and inspect the result:

  $${shell({cmd: "task lint"})}
  EOT
}

The model will receive the literal ${shell({cmd: "task lint"})} text.

Loading Files with file()

The file() function reads a UTF-8 text file and returns its contents as a string. Relative paths are resolved from the HCL config file's directory, and reads are restricted to that directory.

This keeps long prompts out of the config:

agent "root" {
  model       = "openai/gpt-5"
  description = "Coding assistant"
  instruction = file("prompts/coding.md")
}

With a single argument, the file contents are returned exactly as written — any ${...} in the file stays literal, so runtime snippets like ${shell({cmd: "..."})} pass through untouched.

Rendering files as templates

Pass an object as the second argument to render the file as an HCL template. Each key becomes a variable available inside the file:

agent "reviewer" {
  model       = "openai/gpt-5"
  description = "Go reviewer"
  instruction = file("prompts/reviewer.md", {
    language   = "Go"
    strictness = "high"
  })
}

agent "py_reviewer" {
  model       = "openai/gpt-5"
  description = "Python reviewer"
  instruction = file("prompts/reviewer.md", {
    language   = "Python"
    strictness = "relaxed"
  })
}

With prompts/reviewer.md containing:

You review ${language} code with ${strictness} strictness.

Templates support the full HCL template syntax, including %{ for } and %{ if } directives:

Rules:
%{ for rule in rules ~}
- ${rule}
%{ endfor ~}

Two things to keep in mind:

Repeated Blocks Become Lists

Some YAML sections are lists. In HCL, those are written as repeated blocks.

For example, model routing rules become repeated routing { ... } blocks:

model "smart_router" {
  provider = "openai"
  model    = "gpt-5"

  routing {
    model    = "anthropic/claude-sonnet-4-5"
    examples = [
      "Write a detailed technical document",
      "Review this code for security issues",
    ]
  }

  routing {
    model    = "openai/gpt-5"
    examples = [
      "Generate some creative ideas",
      "Help me brainstorm",
    ]
  }
}

The same idea applies to other list-shaped sections such as RAG strategy blocks and hook event entries.

Important Differences from Terraform

docker-agent uses HCL as a configuration syntax, not as Terraform:

If you already know Terraform, think of docker-agent HCL as a thin block-based syntax over the existing config schema.

Examples

See these real configs in the repository: