Infrastructure Drift Detection

Detect and remediate unauthorized changes to infrastructure. Covers drift detection tools, reconciliation strategies, policy enforcement, and the patterns that ensure your actual infrastructure matches your declared state.

Infrastructure drift occurs when a system's actual state diverges from its intended state—configuration, software, network, or hardware—over time. It matters most in environments where infrastructure is declared (infrastructure as code), but not continuously enforced: after deployments, upgrades, manual changes, or incident responses, the live system slowly departs from its original blueprint. Drift detection is the practice of identifying, reporting, and tracking these discrepancies, enabling teams to maintain consistency and reliability across their environments.

Detecting Drift: The Core Workflow

Identify the Intended State

The first step is defining a canonical representation of the desired infrastructure state. This is typically a set of configuration files, versioned in Git, that describe the full environment: servers, services, network rules, security policies, and software versions.


version: 1.0
services:
  - name: web
    image: nginx:1.25
    ports:
      - 80:80
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=info
    volumes:
      - /var/www/html:/usr/share/nginx/html
    restart: always
  - name: database
    image: postgres:15
    ports:
      - 5432:5432
    environment:
      - POSTGRES_DB=app
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=securepass
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
    driver: local
    options:
      type: ext4
      device: /dev/sdb

This file serves as the source of truth. It may be generated by a tool like Terraform, Pulumi, or a custom script, but it is not assumed to be *executed*—only *declared*. The intended state is frozen at a Git commit, and the detection process compares this to what is running.

Capture the Actual State

The actual state is collected from the live system. This can be done via agents, periodic snapshots, or ad-hoc queries. The process must gather enough detail to compare against the intended state.

# Collect actual state from a single host
$ sudo systemctl status nginx
$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
$ curl -s http://localhost:8080/health
$ cat /etc/nginx/nginx.conf
$ ip route show
$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT

For Kubernetes clusters, actual state is extracted using kubectl:

$ kubectl get pods,deployments,services,configmaps,secrets -A -o yaml
$ kubectl get nodes -o json
$ kubectl describe node worker-01

These outputs are combined into a single structured report. The result is a JSON object containing all relevant runtime data, including:

Compare Intended and Actual States

The comparison is the heart of drift detection. It aligns the intended state (from infrastructure.yaml) with the actual state (from the live system), then identifies differences.

A common approach is to define a set of checks in a configuration file that specifies how to match and compare each component.

# drift-checks.yaml
checks:
  - type: container
    name: nginx
    expected:
      image: nginx:1.25
      ports:
        - 80:80
      environment:
        - NODE_ENV=production
        - LOG_LEVEL=info
      volumes:
        - /var/www/html:/usr/share/nginx/html
      restart: always
    actual:
      source: docker ps
      key: name
      fields:
        - image
        - ports
        - environment
        - volumes
        - restart
  - type: file
    name: nginx.conf
    path: /etc/nginx/nginx.conf
    expected:
      content: |
        user www-data;
        worker_processes auto;
        pid /run/nginx.pid;
        events {
          worker_connections 768;
        }
        http {
          include /etc/nginx/mime.types;
          default_type application/octet-stream;
          sendfile on;
        }
    actual:
      source: file
      path: /etc/nginx/nginx.conf
  - type: network
    name: web-service-port
    expected:
      port: 80
      protocol: tcp
      listening: true
    actual:
      source: netstat
      command: netstat -tuln
      parser: grep "LISTEN.*:80"
      fields:
        - port
        - protocol
        - listening

The comparison engine reads both the intended and actual state, applies the checks, and outputs a report.

{
  "drift": [
    {
      "check": "nginx",
      "type": "container",
      "status": "drifted",
      "details": [
        {
          "field": "image",
          "expected": "nginx:1.25",
          "actual": "nginx:1.24",
          "changed": true
        },
        {
          "field": "environment",
          "expected": [
            "NODE_ENV=production",
            "LOG_LEVEL=info"
          ],
          "actual": [
            "NODE_ENV=production",
            "LOG_LEVEL=info",
            "DEBUG=true"
          ],
          "extra": [ "DEBUG=true" ],
          "missing": []
        },
        {
          "field": "volumes",
          "expected": [ "/var/www/html:/usr/share/nginx/html" ],
          "actual": [ "/var/www/html:/usr/share/nginx/html", "/data:/data" ],
          "extra": [ "/data:/data" ],
          "missing": []
        }
      ]
    },
    {
      "check": "nginx.conf",
      "type": "file",
      "status": "identical",
      "details": []
    }
  ]
}

Report and Alert on Drift

Once drift is detected, the system must surface it to operators and developers. This involves generating reports, storing results, and triggering alerts.

A standard format is a structured JSON report stored in a database or file system.

{
  "timestamp": "2024-04-15T12:34:56Z",
  "target": "production",
  "environment": "prod",
  "checks": [
    {
      "name": "nginx",
      "status": "drifted",
      "diffs": [
        {
          "field": "image",
          "expected": "nginx:1.25",
          "actual": "nginx:1.24"
        },
        {
          "field": "environment",
          "expected": [ "NODE_ENV=production", "LOG_LEVEL=info" ],
          "actual": [ "NODE_ENV=production", "LOG_LEVEL=info", "DEBUG=true" ]
        }
      ],
      "metadata": {
        "host": "web-01.prod.example.com",
        "source": "docker ps"
      }
    }
  ],
  "summary": {
    "total_checks": 12,
    "drifted_checks": 5,
    "new_drifts": 3,
    "resolved_drifts": 2
  }
}

This report can be used to populate a dashboard, trigger a notification, or feed into a change management system.

Alerts are configured based on thresholds and conditions:

Alerts are sent via email, Slack, or a webhook:

{
  "title": "Infrastructure Drift Detected",
  "text": "5 checks are drifting in production.\n\n- nginx: image mismatch (1.25 → 1.24)\n- database: missing LOG_LEVEL=debug\n- nginx.conf: extra DEBUG=true in environment",
  "channel": "#infrastructure",
  "attachments": [
    {
      "title": "Drift Report",
      "text": "Full report available at https://drift.example.com/reports/2024-04-15T12:34:56Z",
      "fields": [
        { "title": "Total Checks", "value": "12" },
        { "title": "Drifted", "value": "5" },
        { "title": "New", "value": "3" },
        { "title": "Resolved", "value": "2" }
      ],
      "color": "#FF6B35"
    }
  ]
}

Schedule and Execute Drift Detection

Drift detection is not a one-off operation. It must be automated and scheduled.

A cron job runs the full detection pipeline every 15 minutes:

# Run drift detection every 15 minutes
*/15 * * * * /opt/drift/detect.sh --target=production --output=/var/reports/drift-$(date +\%Y-\%m-\%d-\%H-\%M).json

The script orchestrates the entire workflow:

#!/bin/bash
# detect.sh

set -euo pipefail

TARGET="${1:-}"
OUTPUT="${2:-/tmp/drift-report.json}"

# 1. Pull latest infrastructure.yaml
git pull origin main

# 2. Collect actual state from all nodes in target
mkdir -p /tmp/actual
for host in $(cat /etc/hosts | grep -v '^#' | awk '{print $2}' | sort -u); do
  ssh "$host" 'systemctl status nginx; docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"; ip route show' > /tmp/actual/"$host".out
done

# 3. Run comparison
python3 /opt/drift/compare.py \
  --intended infrastructure.yaml \
  --actual /tmp/actual \
  --checks drift-checks.yaml \
  --output "$OUTPUT"

# 4. Upload to central store
curl -X POST \
  -H "Content-Type: application/json" \
  -d "@$OUTPUT" \
  https://api.infrastructure.example.com/v1/reports

This pipeline can be triggered manually, on demand, or in response to events (e.g., after a deployment).

Drift Detection Patterns and Edge Cases

Silent Failures

Drift detection often fails silently. A check may be defined but not executed due to misconfiguration.

Confusion in Configuration

Production Failures

Idempotent Drift Detection

To avoid double-counting and inconsistent state, drift detection should be idempotent: running it multiple times produces the same result.

This requires:

Drift Detection in CI/CD

Drift detection integrates naturally into CI/CD:

# .github/workflows/drift.yml
name: Drift Detection
on:
  push:
    branches: [ main ]

jobs:
  detect:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          ./detect.sh --target=staging
          curl -X POST https://api.infrastructure.example.com/v1/reports \
            -H "Authorization: Bearer $DRIFT_API_KEY" \
            -d @report.json
      - run: |
          # Optional: Send summary to Slack
          curl -X POST https://hooks.slack.com/services/... \
            -H "Content-Type: application/json" \
            -d '{
              "text": "Drift detection completed in staging.",
              "attachments": [
                {
                  "title": "Summary",
                  "fields": [
                    { "title": "Drifted", "value": "3" },
                    { "title": "Total", "value": "10" }
                  ]
                }
              ]
            }'

This ensures that every code change triggers a drift detection, validating that the new code does not introduce configuration drift.

Summary

Infrastructure drift detection is the process of identifying and reporting the difference between a system's intended and actual state. It relies on a clear definition of the intended state, collection of actual state from live systems, and a structured comparison process. The output is a report that enables alerting, tracking, and remediation. The workflow is repeatable, automated, and robust when attention is paid to edge cases: silent failures, configuration confusion, and idempotency. When properly implemented, drift detection becomes a foundational practice for maintaining consistency across infrastructure.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.