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-mini"
  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-mini"
  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-mini"
  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-mini"
  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.

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-mini"

  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: