tutorial

Monitoring as Code with Vigilmon: A 2026 Guide for Infrastructure-as-Code Teams

Infrastructure-as-code has become the default for modern engineering teams. Servers, DNS records, databases, load balancers, and Kubernetes clusters are all ...

Infrastructure-as-code has become the default for modern engineering teams. Servers, DNS records, databases, load balancers, and Kubernetes clusters are all declared in version-controlled configuration files — reviewed, tested, and deployed through CI/CD pipelines. But monitoring is often the last piece to join this workflow. Engineers still log into a dashboard, click through a wizard, and add monitors by hand. Those monitors aren't version-controlled, aren't reviewed, and aren't tied to the services they cover.

Monitoring as code closes this gap. It means your uptime monitors, heartbeat checks, and alert configurations are defined in code, committed to your repository, deployed automatically, and managed with the same discipline as the rest of your infrastructure.

This guide covers the why, the how, and practical patterns for implementing monitoring as code with the Vigilmon REST API — including CI/CD integration, Terraform patterns, and GitOps workflows for uptime monitoring.


Why Infrastructure-as-Code Teams Need Monitoring as Code

If you already manage infrastructure declaratively, the case for monitoring as code follows directly from the same principles.

The Drift Problem

When monitors are created manually, they drift from reality. A service gets renamed — the old monitor still checks the old URL. A background job gets removed — its heartbeat monitor lingers, generating false alerts. A new microservice launches — nobody remembers to add a monitor. The monitoring configuration becomes a stale artifact that reflects how the infrastructure was, not how it is.

Defining monitors in code, colocated with the services they cover, eliminates drift structurally. When a service is removed, the monitor definition is removed in the same commit. When a service is renamed, the URL in the monitor definition changes in the same PR.

Visibility and Review

When monitors live only in a dashboard, they're invisible to code review. A monitoring configuration change — adding an alert, changing a check interval, removing a monitor for a service that was supposed to stay up — doesn't generate a PR for teammates to review. Mistakes slip through because there's no review step.

Monitoring as code puts monitoring changes in PRs. A reviewer sees "this commit removes the heartbeat monitor for the order-processing worker" and can ask the right question before the change lands.

Reproducibility

In a monitoring-as-code setup, spinning up a new environment — staging, preview, QA — automatically creates the corresponding monitors. The monitors for production and staging are derived from the same configuration, parameterized by environment. No manual steps, no forgotten monitors, no environments flying blind.

Disaster Recovery

If you lose access to your monitoring dashboard — account issue, provider outage, team member departure — you can recreate your entire monitoring setup from the code in your repository. All monitor configurations, alert targets, and check settings are in version control.


The Vigilmon REST API

Vigilmon exposes a REST API for full programmatic control of your monitoring configuration. Every operation available in the dashboard is available via API:

  • Create, update, and delete HTTP monitors
  • Create, update, and delete TCP port monitors
  • Create, update, and delete heartbeat (cron) monitors
  • Retrieve monitor status and response time history
  • Manage webhook notification endpoints

Authentication

All API requests use Bearer token authentication. Generate an API token from your Vigilmon account settings. Include it in every request:

Authorization: Bearer <your-api-token>

Tokens are scoped to your account and carry full read/write access to your monitors. Treat API tokens as secrets — store them in your CI/CD secret store (GitHub Actions secrets, GitLab CI variables, HashiCorp Vault), never in source code.

Base URL

https://vigilmon.online/api/v1

Creating and Managing Monitors via API

Creating an HTTP Monitor

curl -X POST https://vigilmon.online/api/v1/monitors \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production API",
    "type": "http",
    "url": "https://api.yourapp.com/health",
    "interval": 60,
    "alertThreshold": 2,
    "notifications": {
      "email": true,
      "webhooks": ["https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"]
    }
  }'

Response:

{
  "id": "mon_abc123",
  "name": "Production API",
  "type": "http",
  "url": "https://api.yourapp.com/health",
  "interval": 60,
  "status": "active",
  "createdAt": "2026-03-15T10:00:00Z"
}

Creating a Heartbeat Monitor

curl -X POST https://vigilmon.online/api/v1/monitors \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Processing Worker",
    "type": "heartbeat",
    "expectedInterval": 3600,
    "gracePeriod": 300,
    "notifications": {
      "email": true,
      "webhooks": ["https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"]
    }
  }'

The response includes a heartbeatUrl — the URL your cron job pings on each successful run:

{
  "id": "mon_xyz789",
  "name": "Order Processing Worker",
  "type": "heartbeat",
  "heartbeatUrl": "https://vigilmon.online/ping/mon_xyz789",
  "expectedInterval": 3600,
  "gracePeriod": 300,
  "status": "active"
}

Creating a TCP Monitor

curl -X POST https://vigilmon.online/api/v1/monitors \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Database",
    "type": "tcp",
    "host": "db.yourapp.com",
    "port": 5432,
    "interval": 60
  }'

Updating a Monitor

curl -X PATCH https://vigilmon.online/api/v1/monitors/mon_abc123 \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "interval": 30
  }'

Deleting a Monitor

curl -X DELETE https://vigilmon.online/api/v1/monitors/mon_abc123 \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN"

Listing All Monitors

curl https://vigilmon.online/api/v1/monitors \
  -H "Authorization: Bearer $VIGILMON_API_TOKEN"

CI/CD Integration Patterns

Pattern 1: Deploy-Time Monitor Sync

The simplest CI/CD integration: after each deployment, run a script that syncs your monitor configuration from a declarative file to Vigilmon.

monitors.json (committed to your repository):

{
  "monitors": [
    {
      "name": "Production API - Health",
      "type": "http",
      "url": "https://api.yourapp.com/health",
      "interval": 60
    },
    {
      "name": "Production Frontend",
      "type": "http",
      "url": "https://yourapp.com",
      "interval": 60
    },
    {
      "name": "Production Database",
      "type": "tcp",
      "host": "db.yourapp.com",
      "port": 5432,
      "interval": 60
    },
    {
      "name": "Nightly Backup Job",
      "type": "heartbeat",
      "expectedInterval": 86400,
      "gracePeriod": 3600
    }
  ]
}

sync-monitors.sh (runs in CI/CD pipeline after deploy):

#!/bin/bash
set -e

VIGILMON_TOKEN="${VIGILMON_API_TOKEN}"
BASE_URL="https://vigilmon.online/api/v1"

# Fetch current monitors
existing=$(curl -s -H "Authorization: Bearer $VIGILMON_TOKEN" "$BASE_URL/monitors")

# Sync each monitor from local config
jq -c '.monitors[]' monitors.json | while read -r monitor; do
  name=$(echo "$monitor" | jq -r '.name')
  
  # Check if monitor exists
  existing_id=$(echo "$existing" | jq -r --arg name "$name" '.[] | select(.name == $name) | .id')
  
  if [ -n "$existing_id" ]; then
    # Update existing monitor
    curl -s -X PATCH "$BASE_URL/monitors/$existing_id" \
      -H "Authorization: Bearer $VIGILMON_TOKEN" \
      -H "Content-Type: application/json" \
      -d "$monitor" > /dev/null
    echo "Updated monitor: $name"
  else
    # Create new monitor
    curl -s -X POST "$BASE_URL/monitors" \
      -H "Authorization: Bearer $VIGILMON_TOKEN" \
      -H "Content-Type: application/json" \
      -d "$monitor" > /dev/null
    echo "Created monitor: $name"
  fi
done

GitHub Actions integration:

name: Deploy and Sync Monitors

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy application
        run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
      
      - name: Sync Vigilmon monitors
        run: bash sync-monitors.sh
        env:
          VIGILMON_API_TOKEN: ${{ secrets.VIGILMON_API_TOKEN }}

Pattern 2: Preview Environment Monitors

For teams that deploy preview environments for each pull request, create matching monitors automatically so preview environments are monitored with the same coverage as production.

name: PR Preview Deploy

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy preview environment
        id: deploy
        run: |
          URL=$(./deploy-preview.sh ${{ github.event.pull_request.number }})
          echo "url=$URL" >> $GITHUB_OUTPUT
      
      - name: Create preview monitor
        run: |
          curl -X POST https://vigilmon.online/api/v1/monitors \
            -H "Authorization: Bearer ${{ secrets.VIGILMON_API_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d "{
              \"name\": \"PR #${{ github.event.pull_request.number }} Preview\",
              \"type\": \"http\",
              \"url\": \"${{ steps.deploy.outputs.url }}/health\",
              \"interval\": 300,
              \"tags\": [\"preview\", \"pr-${{ github.event.pull_request.number }}\"]
            }"

And on PR close:

  - name: Delete preview monitor
    if: github.event.action == 'closed'
    run: |
      # Find and delete the monitor for this PR
      monitor_id=$(curl -s -H "Authorization: Bearer ${{ secrets.VIGILMON_API_TOKEN }}" \
        "https://vigilmon.online/api/v1/monitors?tag=pr-${{ github.event.pull_request.number }}" | \
        jq -r '.[0].id')
      
      curl -X DELETE "https://vigilmon.online/api/v1/monitors/$monitor_id" \
        -H "Authorization: Bearer ${{ secrets.VIGILMON_API_TOKEN }}"

Terraform Integration

For teams using Terraform for infrastructure management, defining Vigilmon monitors as Terraform resources provides full declarative lifecycle management — create, update, and delete monitors with terraform apply.

Using the Vigilmon Terraform Provider

If a Vigilmon Terraform provider is available in your setup, define monitors as resources:

terraform {
  required_providers {
    vigilmon = {
      source  = "vigilmon/vigilmon"
      version = "~> 1.0"
    }
  }
}

provider "vigilmon" {
  api_token = var.vigilmon_api_token
}

variable "vigilmon_api_token" {
  description = "Vigilmon API token"
  sensitive   = true
}

resource "vigilmon_http_monitor" "api_health" {
  name     = "Production API - Health"
  url      = "https://api.${var.domain}/health"
  interval = 60

  notifications {
    email = true
    webhooks = [var.slack_webhook_url]
  }
}

resource "vigilmon_http_monitor" "frontend" {
  name     = "Production Frontend"
  url      = "https://${var.domain}"
  interval = 60
}

resource "vigilmon_tcp_monitor" "database" {
  name     = "Production Database"
  host     = var.db_host
  port     = 5432
  interval = 60
}

resource "vigilmon_heartbeat_monitor" "backup_job" {
  name              = "Nightly Backup Job"
  expected_interval = 86400
  grace_period      = 3600
}

output "backup_heartbeat_url" {
  value       = vigilmon_heartbeat_monitor.backup_job.heartbeat_url
  description = "URL to ping after each successful backup run"
}

Using Terraform's HTTP Provider (API-Level)

For teams that want Terraform integration before a native provider is available, the generic http provider can call the Vigilmon API:

resource "null_resource" "vigilmon_api_monitor" {
  triggers = {
    url      = "https://api.${var.domain}/health"
    name     = "Production API - Health"
    interval = "60"
  }

  provisioner "local-exec" {
    command = <<-EOT
      curl -X POST https://vigilmon.online/api/v1/monitors \
        -H "Authorization: Bearer ${var.vigilmon_api_token}" \
        -H "Content-Type: application/json" \
        -d '{"name":"${self.triggers.name}","type":"http","url":"${self.triggers.url}","interval":${self.triggers.interval}}'
    EOT
  }

  provisioner "local-exec" {
    when    = destroy
    command = <<-EOT
      MONITOR_ID=$(curl -s https://vigilmon.online/api/v1/monitors \
        -H "Authorization: Bearer ${var.vigilmon_api_token}" | \
        jq -r '.[] | select(.name == "${self.triggers.name}") | .id')
      curl -X DELETE "https://vigilmon.online/api/v1/monitors/$MONITOR_ID" \
        -H "Authorization: Bearer ${var.vigilmon_api_token}"
    EOT
  }
}

Pulumi Integration

For teams using Pulumi, the same monitoring-as-code principle applies with Pulumi's resource model. Using Pulumi's Command resource to call the Vigilmon API:

import * as pulumi from "@pulumi/pulumi";
import * as command from "@pulumi/command";

const config = new pulumi.Config();
const vigilmonToken = config.requireSecret("vigilmonApiToken");
const domain = config.require("domain");

const apiMonitor = new command.local.Command("api-monitor", {
  create: pulumi.interpolate`curl -s -X POST https://vigilmon.online/api/v1/monitors \
    -H "Authorization: Bearer ${vigilmonToken}" \
    -H "Content-Type: application/json" \
    -d '{"name":"Production API","type":"http","url":"https://api.${domain}/health","interval":60}' \
    | jq -r '.id'`,
  delete: pulumi.interpolate`MONITOR_ID=$(curl -s https://vigilmon.online/api/v1/monitors \
    -H "Authorization: Bearer ${vigilmonToken}" | \
    jq -r '.[] | select(.name == "Production API") | .id') && \
    curl -X DELETE "https://vigilmon.online/api/v1/monitors/$MONITOR_ID" \
    -H "Authorization: Bearer ${vigilmonToken}"`,
});

export const apiMonitorId = apiMonitor.stdout;

Version-Controlled Monitor Configurations

The key to GitOps for monitoring is a canonical, version-controlled monitor configuration that is the source of truth. Here's a recommended structure:

infra/
  monitoring/
    monitors.yaml          # Declarative monitor definitions
    environments/
      production.yaml      # Production-specific overrides
      staging.yaml         # Staging-specific settings
    scripts/
      sync.sh              # Sync script called by CI/CD
      validate.sh          # Validate monitors.yaml before apply

monitors.yaml (base configuration):

monitors:
  - name: "{{ env }}-api-health"
    type: http
    url: "https://api.{{ domain }}/health"
    interval: "{{ check_interval }}"
    notifications:
      email: true
      webhooks:
        - "{{ slack_webhook }}"

  - name: "{{ env }}-frontend"
    type: http
    url: "https://{{ domain }}"
    interval: "{{ check_interval }}"

  - name: "{{ env }}-database"
    type: tcp
    host: "{{ db_host }}"
    port: 5432
    interval: 60

  - name: "{{ env }}-nightly-backup"
    type: heartbeat
    expectedInterval: 86400
    gracePeriod: 3600

environments/production.yaml:

env: production
domain: yourapp.com
db_host: db.yourapp.com
check_interval: 60
slack_webhook: "https://hooks.slack.com/services/PROD/SLACK/WEBHOOK"

environments/staging.yaml:

env: staging
domain: staging.yourapp.com
db_host: db-staging.yourapp.com
check_interval: 300
slack_webhook: "https://hooks.slack.com/services/STAGING/SLACK/WEBHOOK"

The sync script reads the base configuration, applies environment overrides, and calls the Vigilmon API to reconcile the actual monitor state with the declared configuration.


GitOps Workflow for Uptime Monitoring

A full GitOps workflow for monitoring treats monitors as declarative resources with the same lifecycle as infrastructure:

1. Define in code: Engineers define monitors in YAML/JSON files colocated with services or in a dedicated monitoring directory.

2. Review in PRs: Changes to monitor configurations go through PR review. Adding, removing, or modifying monitors is visible to the team before it happens.

3. Validate before apply: CI runs a validation step that checks the configuration for common errors — invalid URLs, duplicate monitor names, missing required fields.

4. Apply on merge: When a PR merges to main, the sync script runs and applies the changes to Vigilmon via API.

5. Drift detection: A scheduled CI job (e.g., nightly) runs the sync script in check mode, alerting if Vigilmon's actual state has drifted from the declared configuration.

GitHub Actions full GitOps example:

name: Monitoring as Code

on:
  push:
    branches: [main]
    paths:
      - 'infra/monitoring/**'
  schedule:
    - cron: '0 6 * * *'  # Daily drift detection

jobs:
  sync-monitors:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Validate monitor config
        run: bash infra/monitoring/scripts/validate.sh
      
      - name: Sync monitors to Vigilmon
        run: bash infra/monitoring/scripts/sync.sh
        env:
          VIGILMON_API_TOKEN: ${{ secrets.VIGILMON_API_TOKEN }}
          ENVIRONMENT: production
      
      - name: Post sync summary
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            // Post sync results as a comment on any related PRs

Heartbeat Monitoring in CI/CD Pipelines

CI/CD pipelines themselves benefit from heartbeat monitoring. If your deployment pipeline stops running — a broken scheduled workflow, a runner quota issue, a misconfigured cron in Kubernetes — you want to know before 48 hours of missed deployments pile up.

Add a heartbeat ping to the end of your CI/CD pipeline:

# GitHub Actions — ping Vigilmon at the end of each successful deploy pipeline
- name: Notify Vigilmon - deploy pipeline ran
  if: success()
  run: |
    curl -s https://vigilmon.online/ping/${{ secrets.DEPLOY_PIPELINE_HEARTBEAT_ID }}

Configure the heartbeat monitor with an expectedInterval matching your deploy cadence. If you deploy at least once a day, set expectedInterval: 86400. If deployment pipelines should run at least weekly, set expectedInterval: 604800.


Best Practices

Colocate monitor definitions with services: A service's monitor configuration lives in the same repository as the service code. When the service is deleted, the monitor definition is deleted in the same commit.

Use environment variables for secrets: Never commit API tokens or webhook URLs. Use your CI/CD secret store and reference secrets as environment variables in your sync scripts.

Name monitors consistently: Use a naming convention that includes environment and service name: production-api-health, staging-payments-tcp. Consistent names make the sync script's reconciliation logic reliable.

Version control the heartbeat URL outputs: After creating a heartbeat monitor via API, store the resulting heartbeatUrl in your environment's secret store or configuration, not in source code. The URL is a secret (anyone with it can fake heartbeats).

Validate before apply: Add a validation step in CI that catches configuration errors before they reach the Vigilmon API. Fail the pipeline on invalid monitor configurations rather than silently skipping bad entries.

Test your sync script on staging first: Run the sync script against a staging Vigilmon account (or with dry-run mode) before deploying changes to production monitor configurations.


Conclusion

Monitoring as code brings the same discipline to uptime monitoring that infrastructure-as-code brought to provisioning: version control, peer review, reproducibility, and automatic drift correction. For teams already working with Terraform, Pulumi, or GitOps workflows, adding Vigilmon monitors to the declarative configuration layer is a natural extension.

The Vigilmon REST API makes monitoring as code practical today. Define your monitors in YAML or JSON, sync them via CI/CD, and treat uptime monitoring as a first-class artifact of your infrastructure — reviewed, versioned, and deployed alongside the services it covers.

Start building monitoring as code with Vigilmon's free tier at vigilmon.online — REST API access included, no credit card required, multi-region consensus monitoring from the first monitor.


Tags: #monitoring #monitoringascode #devops #sre #terraform #pulumi #gitops #cicd #vigilmon #infrastructure #2026

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →