Environment Variables
herdctl supports environment variable interpolation in configuration files, allowing you to inject secrets and environment-specific values without hardcoding them.
Interpolation Syntax
Section titled “Interpolation Syntax”Environment variables can be referenced in any string value within your configuration using the ${VAR_NAME} syntax.
Required Variables
Section titled “Required Variables”Use ${VAR_NAME} to reference a required environment variable:
# agent config (fleet-level equivalent: defaults.mcp_servers)mcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-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.github.env.GITHUB_TOKEN' (no default provided)Variables with Default Values
Section titled “Variables with Default Values”Use ${VAR_NAME:-default} to provide a fallback value:
working_directory: root: ${HERDCTL_WORKSPACE_ROOT:-/home/user/herdctl-workspace}
defaults: model: ${CLAUDE_MODEL:-claude-sonnet-4-20250514}If HERDCTL_WORKSPACE_ROOT is not set, the value /home/user/herdctl-workspace will be used instead.
Where Interpolation Works
Section titled “Where Interpolation Works”Environment variable interpolation works on any string value in your configuration, at any nesting depth:
# Top-level stringsversion: 1
fleet: name: ${FLEET_NAME:-production} description: Fleet for ${ENVIRONMENT:-development} environment
# Nested in objectsworking_directory: root: ${WORKSPACE_ROOT:-/home/user/herdctl}
# Inside arraysagents: - path: ./agents/${AGENT_PROFILE:-default}.yaml - path: ${CUSTOM_AGENT_PATH}
# Deeply nesteddefaults: allowed_tools: - "Bash(${PACKAGE_MANAGER:-npm} *)"
# MCP server configuration (fleet defaults; also valid at agent top level) mcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN} API_URL: ${GITHUB_API_URL:-https://api.github.com}String Fields Only
Section titled “String Fields Only”Interpolation only works in string fields. This is a hard rule, not just a convenience: herdctl validates the configuration against its schema before interpolation runs. A ${VAR} reference in a field the schema types as a number or boolean is still the literal string "${VAR}" at validation time, so validation fails before interpolation ever gets a chance to substitute the value.
# These FAIL schema validation - number/boolean fields cannot be interpolateddefaults: max_turns: ${MAX_TURNS:-50} # Error: expected number, got string docker: enabled: ${DOCKER_ENABLED:-true} # Error: expected boolean, got string# Set numeric/boolean values literally insteaddefaults: max_turns: 50 docker: enabled: trueNon-string values written as literals (numbers, booleans, null) are preserved as-is and never interpolated:
working_directory: root: /home/user/herdctl-workspace clone_depth: 1 # Number - no interpolation auto_clone: true # Boolean - no interpolationMultiple Variables in One String
Section titled “Multiple Variables in One String”You can use multiple variables in a single string value:
fleet: description: "${TEAM_NAME} fleet in ${ENVIRONMENT}"
working_directory: root: ${HOME}/${PROJECT_NAME:-herdctl}/workspaceError Behavior
Section titled “Error Behavior”Undefined Required Variables
Section titled “Undefined Required Variables”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
Validation Timing
Section titled “Validation Timing”Environment variables are resolved when the configuration is loaded, not when it’s parsed. This means:
- YAML syntax is validated first
- Schema validation runs second
- Environment variable interpolation happens third
If interpolation fails, you’ll see the UndefinedVariableError after YAML and schema validation pass.
Because schema validation runs before interpolation, ${VAR} references are only valid in string fields — see String Fields Only above.
Security Recommendations
Section titled “Security Recommendations”Never Commit Secrets
Section titled “Never Commit Secrets”Keep sensitive values out of version control:
.env.env.local.env.*.localUse Environment Variables for All Secrets
Section titled “Use Environment Variables for All Secrets”Always use interpolation for sensitive values:
# Good: Secrets come from environment (herdctl.yaml)defaults: mcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN} custom-api: command: node args: ["./mcp-servers/custom.js"] env: API_KEY: ${API_KEY}
webhooks: secret_env: WEBHOOK_SECRET # Reference by name, not value# Bad: Secrets hardcoded in config (agent config)mcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ghp_xxxxxxxxxxxx # Never do this!Set Variables in Your Environment
Section titled “Set Variables in Your Environment”Export variables in your shell or use a secrets manager:
# In your shell profile (~/.bashrc, ~/.zshrc)export ANTHROPIC_API_KEY="sk-ant-..."export GITHUB_TOKEN="ghp_..."
# Or set them when running herdctlANTHROPIC_API_KEY="sk-ant-..." herdctl run coderProduction Environments
Section titled “Production Environments”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
Common Patterns
Section titled “Common Patterns”API Tokens and Keys
Section titled “API Tokens and Keys”# MCP servers - agent config top level, or fleet-level under defaults.mcp_serversmcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN}# Docker environment variables (fleet-level only)defaults: docker: env: GITHUB_TOKEN: ${GITHUB_TOKEN} API_KEY: ${API_KEY}
# Chat integrations are per-agent (each agent has its own bot)# See agent config for chat settingsURLs and Endpoints
Section titled “URLs and Endpoints”mcp_servers: custom-api: command: node args: ["./mcp-servers/custom-api.js"] env: API_BASE_URL: ${API_BASE_URL:-https://api.example.com} API_VERSION: ${API_VERSION:-v1}Paths and Directories
Section titled “Paths and Directories”working_directory: root: ${HERDCTL_WORKSPACE:-/home/user/herdctl-workspace}
agents: - path: ${AGENTS_DIR:-./agents}/coder.yaml - path: ${AGENTS_DIR:-./agents}/reviewer.yamlEnvironment-Specific Configuration
Section titled “Environment-Specific Configuration”fleet: name: ${FLEET_NAME:-development} description: ${FLEET_DESCRIPTION:-Local development fleet}
defaults: model: ${CLAUDE_MODEL:-claude-sonnet-4-20250514}
# Numeric fields like instances.max_concurrent cannot be interpolated # (validation runs before interpolation) - set them literally: instances: max_concurrent: 2Dynamic Agent Selection
Section titled “Dynamic Agent Selection”agents: - path: ./agents/${AGENT_PROFILE:-standard}.yamlSet AGENT_PROFILE=advanced to load ./agents/advanced.yaml instead of the default.
Core Environment Variables
Section titled “Core Environment Variables”While herdctl doesn’t require specific environment variables, these are commonly used:
| Variable | Description | Example |
|---|---|---|
ANTHROPIC_API_KEY | Claude API key for agent sessions | sk-ant-... |
GITHUB_TOKEN | GitHub API token for MCP server | ghp_... |
LINEAR_API_KEY | Linear API key for issue tracking | lin_api_... |
<AGENT>_DISCORD_TOKEN | Per-agent Discord bot token (e.g., SUPPORT_DISCORD_TOKEN) | — |
SLACK_BOT_TOKEN | Slack Bot User OAuth Token (xoxb-) — shared across agents | — |
SLACK_APP_TOKEN | Slack App-Level Token (xapp-) for Socket Mode | — |
SLACK_CHANNEL_ID | Slack channel ID for agent routing | C0123456789 |
Complete Example
Section titled “Complete Example”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 is a number field - it cannot use ${VAR} interpolation max_turns: 50
permission_mode: acceptEdits allowed_tools: - "Bash(${PACKAGE_MANAGER:-npm} *)" - "Bash(git *)" - "Bash(node *)"
mcp_servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN} filesystem: command: npx args: ["-y", "@modelcontextprotocol/server-filesystem", "${ALLOWED_PATHS:-/tmp}"]
working_directory: root: ${HERDCTL_WORKSPACE:-/home/user/herdctl-workspace} auto_clone: true clone_depth: 1
agents: - path: ./agents/${AGENT_PROFILE:-default}/coder.yaml - path: ./agents/${AGENT_PROFILE:-default}/reviewer.yaml
# Note: Chat is configured per-agent, not at fleet level.# Discord: each agent references its own bot token env var.# Slack: agents share one bot token, with channel-to-agent routing.
webhooks: enabled: true # port is a number field - it cannot use ${VAR} interpolation port: 8081 secret_env: WEBHOOK_SECRETTroubleshooting
Section titled “Troubleshooting””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:
# Option 1: Set the variableexport MISSING_VAR="value"
# Option 2: Add a default in config# Change: ${MISSING_VAR}# To: ${MISSING_VAR:-default_value}Variable Not Being Replaced
Section titled “Variable Not Being Replaced”Problem: The ${VAR} syntax appears in the final config instead of the value.
Possible causes:
- Variable name contains invalid characters (must match
[A-Za-z_][A-Za-z0-9_]*) - Malformed syntax (missing
}, extra spaces) - 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}Testing Configuration
Section titled “Testing Configuration”Validate your configuration before running:
# Check that all required variables are setherdctl config validate
# See the resolved configurationherdctl config showRelated
Section titled “Related”- Fleet Configuration - Complete fleet configuration reference
- Agent Configuration - Agent-specific settings
- MCP Servers - MCP server configuration