Hostcfg supports Ansible-style playbook patterns through roles and role dependencies. This allows you to compose multiple reusable roles and apply them to hosts in a deterministic order.
Define multiple roles in your host configuration with depends_on to control execution order:
# hosts/webserver/hostcfg.hcl
role "base" {
source = "../../roles/base"
}
role "redis" {
source = "../../roles/redis"
depends_on = ["role.base"]
variables = {
port = 6379
maxmemory = "512mb"
}
}
role "webapp" {
source = "../../roles/webapp"
depends_on = ["role.redis"]
variables = {
redis_host = "localhost"
redis_port = 6379
}
}When you specify depends_on = ["role.redis"], hostcfg expands this to include all resources in the redis role. This ensures the entire role completes before dependent roles start.
For example, if the redis role contains:
package.redis_redisfile.redis_configservice.redis_redis
Then depends_on = ["role.redis"] is equivalent to:
depends_on = [
"package.redis_redis",
"file.redis_config",
"service.redis_redis"
]Resources are applied in topological order based on dependencies:
- Resources with no dependencies run first
- Resources wait for all their dependencies to complete
- Role-level dependencies ensure entire roles complete before dependent roles start
Example execution order for the configuration above:
1. base role resources (no dependencies)
├── package.base_essentials
├── file.base_sshd_config
└── ...
2. redis role resources (depends on role.base)
├── package.redis_redis
├── file.redis_config
└── service.redis_redis
3. webapp role resources (depends on role.redis)
├── directory.webapp_root
├── file.webapp_config
└── service.webapp_app
Variables are resolved with the following precedence (highest to lowest):
- CLI variables (
-e port=6380) - override everything - Role instantiation variables (
variables = { ... }) - Role defaults (
roles/redis/variables.hcl)
# roles/redis/variables.hcl
variable "port" {
default = 6379
}
variable "maxmemory" {
default = "256mb"
}# hosts/webserver/hostcfg.hcl
role "redis" {
source = "../../roles/redis"
variables = {
maxmemory = "1gb" # Overrides default
}
}# CLI override takes highest precedence
hostcfg apply -c ./hosts/webserver -e port=6380A typical multi-host setup with shared roles:
.
├── roles/
│ ├── base/
│ │ ├── variables.hcl
│ │ ├── resources.hcl
│ │ └── files/
│ │ └── sshd_config.tpl
│ ├── redis/
│ │ ├── variables.hcl
│ │ ├── resources.hcl
│ │ └── files/
│ │ └── redis.conf.tpl
│ └── webapp/
│ ├── variables.hcl
│ ├── resources.hcl
│ └── files/
│ └── config.tpl
├── hosts/
│ ├── webserver/
│ │ ├── hostcfg.hcl
│ │ └── variables.hcl
│ ├── database/
│ │ ├── hostcfg.hcl
│ │ └── variables.hcl
│ └── loadbalancer/
│ ├── hostcfg.hcl
│ └── variables.hcl
# hosts/webserver/variables.hcl
variable "environment" {
default = "production"
}
variable "app_port" {
default = 8080
}# hosts/webserver/hostcfg.hcl
# Base system configuration
role "base" {
source = "../../roles/base"
variables = {
environment = var.environment
}
}
# Redis for session storage
role "redis" {
source = "../../roles/redis"
depends_on = ["role.base"]
variables = {
port = 6379
maxmemory = "256mb"
bind = "127.0.0.1"
}
}
# Application server
role "app" {
source = "../../roles/app"
depends_on = ["role.redis"]
variables = {
port = var.app_port
environment = var.environment
redis_host = "127.0.0.1"
redis_port = 6379
}
}
# Nginx reverse proxy
role "nginx" {
source = "../../roles/nginx"
depends_on = ["role.app"]
variables = {
upstream_port = var.app_port
server_name = "example.com"
}
}
# Monitoring agent
role "monitoring" {
source = "../../roles/monitoring"
depends_on = ["role.nginx"]
variables = {
environment = var.environment
}
}Apply the configuration:
# Plan changes
hostcfg plan -c ./hosts/webserver
# Apply changes
hostcfg apply -c ./hosts/webserver
# Apply with environment override
hostcfg apply -c ./hosts/webserver -e environment=staging- Keep roles focused: Each role should handle one concern (e.g., redis, nginx, app)
- Use role defaults: Define sensible defaults in
variables.hcl - Document variables: Add descriptions to variables for clarity
- Test incrementally: Apply roles one at a time during development
- Use
recursive = true: For directory resources that may need parent directories created