Skip to content

Environment Variables

herdctl supports environment variable interpolation in configuration files, allowing you to inject secrets and environment-specific values without hardcoding them.

Environment variables can be referenced in any string value within your configuration using the ${VAR_NAME} syntax.

Use ${VAR_NAME} to reference a required environment variable:

mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}

If the variable is not defined, herdctl will throw an error at configuration load time:

UndefinedVariableError: Undefined environment variable 'GITHUB_TOKEN' at 'mcp.servers[0].env.GITHUB_TOKEN' (no default provided)

Use ${VAR_NAME:-default} to provide a fallback value:

workspace:
root: ${HERDCTL_WORKSPACE_ROOT:-~/herdctl-workspace}
defaults:
model: ${CLAUDE_MODEL:-claude-sonnet-4-20250514}

If HERDCTL_WORKSPACE_ROOT is not set, the value ~/herdctl-workspace will be used instead.


Environment variable interpolation works on any string value in your configuration, at any nesting depth:

# Top-level strings
version: 1
fleet:
name: ${FLEET_NAME:-production}
description: Fleet for ${ENVIRONMENT:-development} environment
# Nested in objects
workspace:
root: ${WORKSPACE_ROOT:-~/herdctl}
# Inside arrays
agents:
- path: ./agents/${AGENT_PROFILE:-default}.yaml
- path: ${CUSTOM_AGENT_PATH}
# Deeply nested
defaults:
permissions:
bash:
allowed_commands:
- ${PACKAGE_MANAGER:-npm}
# MCP server configuration
mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
API_URL: ${GITHUB_API_URL:-https://api.github.com}

Interpolation only applies to strings. Non-string values (numbers, booleans, null) are preserved as-is:

defaults:
max_turns: 50 # Number - no interpolation
docker:
enabled: true # Boolean - no interpolation
workspace:
clone_depth: 1 # Number - no interpolation
auto_clone: true # Boolean - no interpolation

You can use multiple variables in a single string value:

fleet:
description: "${TEAM_NAME} fleet in ${ENVIRONMENT}"
workspace:
root: ${HOME}/${PROJECT_NAME:-herdctl}/workspace

When a required variable (without a default) is undefined, configuration loading fails immediately with a descriptive error:

UndefinedVariableError: Undefined environment variable 'API_KEY' at 'mcp.servers[0].env.API_KEY' (no default provided)

The error message includes:

  • The variable name that’s missing
  • The full path in the configuration where it was referenced
  • A reminder that no default was provided

Environment variables are resolved when the configuration is loaded, not when it’s parsed. This means:

  1. YAML syntax is validated first
  2. Schema validation runs second
  3. Environment variable interpolation happens third

If interpolation fails, you’ll see the UndefinedVariableError after YAML and schema validation pass.


Keep sensitive values out of version control:

.gitignore
.env
.env.local
.env.*.local

Always use interpolation for sensitive values:

# Good: Secrets come from environment
mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
- name: anthropic
env:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
webhooks:
secret_env: WEBHOOK_SECRET # Reference by name, not value
# Bad: Secrets hardcoded in config
mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ghp_xxxxxxxxxxxx # Never do this!

Export variables in your shell or use a secrets manager:

Terminal window
# In your shell profile (~/.bashrc, ~/.zshrc)
export ANTHROPIC_API_KEY="sk-ant-..."
export GITHUB_TOKEN="ghp_..."
# Or set them when running herdctl
ANTHROPIC_API_KEY="sk-ant-..." herdctl run coder

For production deployments, use your platform’s secrets management:

  • CI/CD: Use GitHub Actions secrets, GitLab CI variables, etc.
  • Kubernetes: Use Kubernetes Secrets or external secrets operators
  • Cloud: Use AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
  • Docker: Use Docker secrets or environment files

mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
- name: linear
env:
LINEAR_API_KEY: ${LINEAR_API_KEY}
# Chat integrations reference env vars by name
chat:
discord:
enabled: true
token_env: DISCORD_BOT_TOKEN # Name of the env var, not the value
mcp:
servers:
- name: custom-api
env:
API_BASE_URL: ${API_BASE_URL:-https://api.example.com}
API_VERSION: ${API_VERSION:-v1}
workspace:
root: ${HERDCTL_WORKSPACE:-~/herdctl-workspace}
agents:
- path: ${AGENTS_DIR:-./agents}/coder.yaml
- path: ${AGENTS_DIR:-./agents}/reviewer.yaml
fleet:
name: ${FLEET_NAME:-development}
description: ${FLEET_DESCRIPTION:-Local development fleet}
defaults:
model: ${CLAUDE_MODEL:-claude-sonnet-4-20250514}
# Different settings per environment
instances:
max_concurrent: ${MAX_CONCURRENT_AGENTS:-2}
agents:
- path: ./agents/${AGENT_PROFILE:-standard}.yaml

Set AGENT_PROFILE=advanced to load ./agents/advanced.yaml instead of the default.


While herdctl doesn’t require specific environment variables, these are commonly used:

VariableDescriptionExample
ANTHROPIC_API_KEYClaude API key for agent sessionssk-ant-...
GITHUB_TOKENGitHub API token for MCP serverghp_...
LINEAR_API_KEYLinear API key for issue trackinglin_api_...
DISCORD_BOT_TOKENDiscord bot token for chat integration
SLACK_BOT_TOKENSlack bot token for chat integration

Here’s a full configuration demonstrating environment variable usage:

version: 1
fleet:
name: ${FLEET_NAME:-production}
description: ${FLEET_DESCRIPTION:-Agent fleet for ${TEAM_NAME:-engineering}}
defaults:
model: ${CLAUDE_MODEL:-claude-sonnet-4-20250514}
max_turns: ${MAX_TURNS:-50}
permissions:
mode: acceptEdits
bash:
allowed_commands:
- ${PACKAGE_MANAGER:-npm}
- git
- node
workspace:
root: ${HERDCTL_WORKSPACE:-~/herdctl-workspace}
auto_clone: true
clone_depth: 1
agents:
- path: ./agents/${AGENT_PROFILE:-default}/coder.yaml
- path: ./agents/${AGENT_PROFILE:-default}/reviewer.yaml
mcp:
servers:
- name: github
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
- name: filesystem
env:
ALLOWED_PATHS: ${ALLOWED_PATHS:-/tmp}
chat:
discord:
enabled: ${DISCORD_ENABLED:-false}
token_env: DISCORD_BOT_TOKEN
webhooks:
enabled: true
port: ${WEBHOOK_PORT:-8081}
secret_env: WEBHOOK_SECRET

”Undefined environment variable” Error

Section titled “”Undefined environment variable” Error”

Problem: Configuration fails to load with UndefinedVariableError.

Solution: Either set the missing variable or provide a default:

Terminal window
# Option 1: Set the variable
export MISSING_VAR="value"
# Option 2: Add a default in config
# Change: ${MISSING_VAR}
# To: ${MISSING_VAR:-default_value}

Problem: The ${VAR} syntax appears in the final config instead of the value.

Possible causes:

  1. Variable name contains invalid characters (must match [A-Za-z_][A-Za-z0-9_]*)
  2. Malformed syntax (missing }, extra spaces)
  3. Value is not a string type in YAML
# Invalid variable names
${123_VAR} # Can't start with number
${VAR-NAME} # Hyphens not allowed (use underscores)
${VAR NAME} # Spaces not allowed
# Valid variable names
${VAR_NAME}
${_PRIVATE_VAR}
${myVar123}

Validate your configuration before running:

Terminal window
# Check that all required variables are set
herdctl config validate
# See the resolved configuration
herdctl config show