Skip to main content
By the end of this guide, you’ll have separate monitoring configurations for staging and production, with shared base definitions and environment-specific overrides.

Strategy: separate files

The simplest approach is one YAML file per environment:
repo/
├── devhelm.yml              # Production
├── devhelm.staging.yml      # Staging
└── .github/workflows/
    └── devhelm-deploy.yml

Production config

version: "1"

tags:
  - name: production
    color: "#ef4444"

monitors:
  - name: API Health
    type: HTTP
    config:
      url: https://api.example.com/health
    frequencySeconds: 30
    regions:
      - us-east
      - eu-west
      - ap-south
    tags: [production]
    assertions:
      - config:
          type: status_code
          expected: "200"
          operator: equals
        severity: fail
      - config:
          type: response_time
          thresholdMs: 1000
        severity: fail

alert-channels:
  - name: Production Slack
    type: SLACK
    config:
      webhookUrl: ${SLACK_WEBHOOK_PRODUCTION}

  - name: PagerDuty On-Call
    type: PAGERDUTY
    config:
      routingKey: ${PAGERDUTY_ROUTING_KEY}

Staging config

Staging typically has longer intervals, fewer regions, and less aggressive alerting:
version: "1"

tags:
  - name: staging
    color: "#f59e0b"

monitors:
  - name: API Health (Staging)
    type: HTTP
    config:
      url: https://api.staging.example.com/health
    frequencySeconds: 300
    regions:
      - us-east
    tags: [staging]
    assertions:
      - config:
          type: status_code
          expected: "200"
          operator: equals
        severity: fail

alert-channels:
  - name: Staging Slack
    type: SLACK
    config:
      webhookUrl: ${SLACK_WEBHOOK_STAGING}
Key differences from production:
AspectProductionStaging
Frequency30s300s (5 min)
Regions31
Response time assertionYes (1000ms)No
PagerDutyYesNo
Alert channelProduction Slack + PagerDutyStaging Slack only

Deploy with environment-specific tokens

Each environment uses its own API token:
name: Deploy Monitoring
on:
  push:
    branches: [main]
    paths: ['devhelm.yml', 'devhelm.staging.yml']

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - uses: devhelmhq/setup-devhelm@v1
        with:
          api-token: ${{ secrets.DEVHELM_API_TOKEN_STAGING }}
      - run: devhelm validate devhelm.staging.yml
      - run: devhelm deploy -f devhelm.staging.yml --yes

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: devhelmhq/setup-devhelm@v1
        with:
          api-token: ${{ secrets.DEVHELM_API_TOKEN }}
      - run: devhelm validate devhelm.yml
      - run: devhelm deploy -f devhelm.yml --yes
Staging deploys first. Production only deploys if staging succeeds.

Secret management

Each environment needs its own secrets:
# Production
devhelm secrets set SLACK_WEBHOOK_PRODUCTION=https://hooks.slack.com/...
devhelm secrets set PAGERDUTY_ROUTING_KEY=your-routing-key

# Staging (switch context or use a different token)
DEVHELM_API_TOKEN=$STAGING_TOKEN devhelm secrets set SLACK_WEBHOOK_STAGING=https://hooks.slack.com/...
Never share API tokens between environments. Use separate tokens so staging changes can’t accidentally affect production.

Next steps

CI/CD pipeline

Full GitHub Actions deployment workflow.

Monitoring as Code tutorial

YAML format and deploy workflow.

Migrating from Dashboard

Move existing monitors to config files.