Structure your YAML configs and CI workflows to handle multiple environments with shared base definitions and per-environment overrides.
File structure
Separate files per environment
The simplest approach — one file per environment:
devhelm.staging.yml
devhelm.production.yml
Each file is self-contained. Deploy the right file based on the branch or CI environment.
Base + environment files
Share common definitions in a base file and add environment-specific resources in separate files:
config/
base.yml # shared channels, policies, tags
staging.yml # staging-only monitors
production.yml # production-only monitors
Deploy with multiple -f flags:
devhelm deploy -f config/base.yml -f config/staging.yml --yes
Sections from all files are concatenated. Resource names must be disjoint across files — defining the same monitor name in two files is a validation error, not an override. There is no per-resource override merging; to vary a monitor between environments, use environment variables (below) or separate self-contained files. The one exception is defaults.monitors, which shallow-merges across files (later files win per field).
Using environment variables
Share a single file and vary behavior with environment variables:
monitors :
- name : API Health
type : HTTP
config :
url : ${API_URL}
method : GET
frequencySeconds : 300
Set different values in each CI environment:
# Staging
API_URL = https://staging-api.example.com devhelm deploy -f devhelm.yml --yes
# Production
API_URL = https://api.example.com devhelm deploy -f devhelm.yml --yes
${VAR} interpolation only works in string fields (URLs, tokens, names). Numeric fields like frequencySeconds cannot be interpolated — the value would arrive as a string and fail schema validation. To vary frequencies per environment, use separate files per environment.
GitHub Actions
Using environments
jobs :
deploy :
strategy :
matrix :
env : [ staging , production ]
environment : ${{ matrix.env }}
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : devhelmhq/setup-devhelm@v1
with :
api-token : ${{ secrets.DEVHELM_API_TOKEN }}
- run : devhelm deploy -f devhelm.${{ matrix.env }}.yml --yes
Sequential with approval
jobs :
deploy-staging :
environment : staging
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : devhelmhq/setup-devhelm@v1
with :
api-token : ${{ secrets.DEVHELM_API_TOKEN }}
- run : devhelm deploy -f devhelm.staging.yml --yes
deploy-production :
needs : deploy-staging
environment : production
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : devhelmhq/setup-devhelm@v1
with :
api-token : ${{ secrets.DEVHELM_API_TOKEN }}
- run : devhelm deploy -f devhelm.production.yml --yes
Configure the production environment in GitHub Settings to require manual approval.
GitLab CI
.deploy-monitoring :
image : node:20
script :
- npm install -g devhelm
- devhelm validate devhelm.$CI_ENVIRONMENT_NAME.yml
- devhelm deploy -f devhelm.$CI_ENVIRONMENT_NAME.yml --yes
deploy-staging :
extends : .deploy-monitoring
stage : deploy
environment : staging
only : [ main ]
deploy-production :
extends : .deploy-monitoring
stage : deploy
environment : production
when : manual
only : [ main ]
Use Terraform workspaces or separate state files:
terraform workspace select staging
terraform apply -var-file=staging.tfvars
terraform workspace select production
terraform apply -var-file=production.tfvars
Or use separate directories:
terraform/
staging/
main.tf
terraform.tfvars
production/
main.tf
terraform.tfvars
Best practices
Separate API tokens per environment for isolation
Deploy staging first , then promote to production
Use GitHub Environments or equivalent for approval gates on production
Pin CLI versions for reproducible builds across environments
Keep environment differences minimal — vary URLs and frequencies, not monitor structure
Next steps
GitHub Actions Full setup-devhelm action reference.
Environments CLI Manage environments from the command line.