GoDriftDetector is a lightweight daemon that continuously compares the desired state (Desired State) of your infrastructure declared in a docker-compose.yaml or Kubernetes manifest with the actual state (Actual State) of running containers. Designed to detect "Shadow IT", service downtime, version/port divergences, and environment variable mismatches, emitting structured alerts for rapid mitigation.
- Overview
- Features
- Tech Stack
- Prerequisites
- Installation
- Quick Start
- Configuration
- Usage Guide
- Testing
- Architecture
- Drift Types
- Providers
- Observability
- Examples
- Roadmap
- Contributing
When a drift is detected in your infrastructure, the agent reports visually in the terminal and can notify via Webhook:
--- Verification Cycle: 2026-04-14T10:00:00Z ---
Reading configuration: ./docker-compose.yaml
DRIFT DETECTED!
[ SHADOW_IT ] Undeclared container running: 1a2b3c4d5e6f (Image: redis:alpine)
[ MISSING ] Service 'db' (image postgres:15) is not running
[ PORT_MISMATCH ] Desired port '80:80' not found in container
[ ENV_MISMATCH ] Environment variable 'DB_PASSWORD' differs. Expected: 'exp***', Actual: 'act***'
Alert sent successfully to webhook
| Feature | Description |
|---|---|
| π Multi-Drift Detection | Detects MISSING, SHADOW_IT, PORT_MISMATCH, IMAGE_MISMATCH, ENV_MISMATCH, ENV_INJECTED |
| π³ Multi-Provider | Supports Docker and Kubernetes with pluggable architecture |
| π Sensitive Data Masking | Automatically masks passwords, tokens, and secrets in logs |
| π Prometheus Metrics | Real-time metrics for Grafana integration |
| π¨ Webhooks | Send alerts to Slack, Discord, or custom endpoints |
| π GitOps Ready | Automatic Git repository sync for configuration management |
| π JSON Reports | Export audit reports in JSON format for CI/CD integration |
| π» Daemon Mode | Background polling with configurable intervals |
| π¨ Colored Output | Terminal-friendly with lipgloss styling |
| Technology | Purpose |
|---|---|
| Go | High-performance compiled language with static binaries |
| Docker SDK | Runtime state extraction from containers |
| go-git | Remote Git repository synchronization |
| yaml.v3 | Robust docker-compose.yaml parsing |
| lipgloss | Terminal output styling (colors, bold) |
| Prometheus SDK | Metrics exposition for observability |
| client-go | Kubernetes API interaction |
- Go >= 1.25.0
- Docker daemon running OR Kubernetes cluster access (kubectl configured)
- (Optional) Slack/Discord webhook URLs for notifications
go install github.com/esousa97/godriftdetector/cmd/godriftdetector@latest
godriftdetector --help# Clone the repository
git clone https://github.com/esousa97/godriftdetector.git
cd godriftdetector
# Build the binary
go build -o godriftdetector ./cmd/godriftdetector
# Run
./godriftdetector --helpdocker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/docker-compose.yaml:/docker-compose.yaml \
-e LOCAL_CONFIG_DIR=/ \
ghcr.io/esousa97/godriftdetector:latest# Create a docker-compose.yaml
cat > docker-compose.yaml << 'EOF'
version: '3.8'
services:
app:
image: nginx:alpine
ports:
- "80:80"
environment:
APP_ENV: production
LOG_LEVEL: info
EOF
# Start containers
docker-compose up -d
# Run drift detection
LOCAL_CONFIG_DIR=. ./godriftdetector --json# Create a k8s-manifest.yaml
cat > k8s-manifest.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: app
image: nginx:alpine
ports:
- containerPort: 80
env:
- name: APP_ENV
value: production
EOF
# Run drift detection
./godriftdetector --provider k8s --namespace default --jsonexport GIT_REPO_URL="https://github.com/your-org/config-repo.git"
export GIT_USERNAME="your-token"
export LOCAL_CONFIG_DIR="/tmp/config"
export SYNC_INTERVAL="5m"
export WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK"
./godriftdetector| Variable | Type | Default | Description |
|---|---|---|---|
GIT_REPO_URL |
String | "" |
HTTPS/SSH URL of Git repository containing docker-compose.yaml or k8s-manifest.yaml |
GIT_USERNAME |
String | "" |
Username/Token for HTTPS Git access |
GIT_PASSWORD |
String | "" |
Password/Token for HTTPS Git access |
LOCAL_CONFIG_DIR |
String | "./config-repo" |
Local directory for Git clone/cache |
SYNC_INTERVAL |
Duration | "5m" |
Polling frequency (e.g., 10m, 30s, 5m) |
WEBHOOK_URL |
String | "" |
Webhook URL for Slack/Discord notifications |
godriftdetector [flags]
Flags:
-json Generate drift report in JSON format and exit
-provider string Infrastructure provider: 'docker' or 'k8s' (default "docker")
-namespace string Kubernetes namespace (only with --provider=k8s) (default "default")Generate a single JSON report without starting the daemon:
LOCAL_CONFIG_DIR=. ./godriftdetector --json > drift-report.jsonOutput:
{
"Drifts": [
{
"ServiceName": "app",
"Type": "ENV_MISMATCH",
"Message": "Environment variable 'LOG_LEVEL' outdated. Expected: 'inf***', Actual: 'deb***'.",
"Desired": "inf***",
"Actual": "deb***"
},
{
"ServiceName": "app",
"Type": "ENV_INJECTED",
"Message": "Undeclared environment variable found running: 'PATH=/usr/bin'.",
"Actual": "/usr/bin"
}
]
}Run continuous monitoring with automatic Git sync:
export LOCAL_CONFIG_DIR="./config"
export SYNC_INTERVAL="5m"
export WEBHOOK_URL="https://hooks.slack.com/services/..."
./godriftdetectorOutput:
Initiating GoDriftDetector Agent (Interval: 5m0s, Provider: docker)
Exposing metrics at http://localhost:9090/metrics
--- Verification Cycle: 2026-04-14T22:30:29-03:00 ---
DRIFT DETECTED!
[ ENV_MISMATCH ] Environment variable 'ENVIRONMENT' outdated. Expected: 'production', Actual: 'staging'
[ ENV_INJECTED ] Undeclared environment variable found: 'EXTRA_VAR=injected_value'
Alert sent successfully to webhook.
--- Verification Cycle: 2026-04-14T22:35:29-03:00 ---
System in compliance.
./godriftdetector --provider k8s --namespace production
# Outputs Pods running in production namespace and compares with k8s-manifest.yaml# In your CI pipeline
if ! ./godriftdetector --provider docker --json | jq -e '.Drifts | length == 0' > /dev/null; then
echo "Infrastructure drift detected!"
exit 1
fi# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Verbose output
go test -v ./...# Start test Docker containers
docker run -d --name test-nginx -p 8080:80 nginx:alpine
docker run -d --name test-redis -p 6379:6379 redis:7-alpine
# Create docker-compose.yaml
cat > docker-compose.yaml << 'EOF'
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "8080:80"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
EOF
# Run detection
LOCAL_CONFIG_DIR=. ./godriftdetector --json
# Cleanup
docker rm -f test-nginx test-redis# Create test compose file with environment variables
cat > docker-compose.yaml << 'EOF'
version: '3.8'
services:
myapp:
image: nginx:alpine
environment:
APP_ENV: production
DB_PASSWORD: secret123
API_TOKEN: mytoken
EOF
# Start container with DIFFERENT values
docker run -d --name myapp \
-e APP_ENV=staging \
-e DB_PASSWORD=different_secret \
-e API_TOKEN=different_token \
-e EXTRA_VAR=injected_value \
nginx:alpine
# Run detection to see ENV_MISMATCH and ENV_INJECTED
LOCAL_CONFIG_DIR=. ./godriftdetector --json
# Cleanup
docker rm -f myapp# Start daemon
./godriftdetector &
DAEMON_PID=$!
# Wait for metrics server to start
sleep 2
# Query metrics endpoint
curl http://localhost:9090/metrics | grep drift_
# Expected output:
# drift_detected_total 3
# drift_by_service{service="app",type="ENV_MISMATCH"} 1
# drift_by_service{service="app",type="ENV_INJECTED"} 2
# last_scan_timestamp 1713139967
# Stop daemon
kill $DAEMON_PIDGoDriftDetector follows a clean architecture with clear separation of concerns:
godriftdetector/
βββ cmd/
β βββ godriftdetector/
β βββ main.go # CLI entry point and daemon orchestration
βββ internal/
β βββ domain/
β β βββ container.go # ContainerState, DesiredState, ServiceConfig
β β βββ drift.go # Drift types and ComparisonResult
β β βββ comparator.go # Core drift comparison logic
β β βββ provider.go # Provider interfaces (InfrastructureProvider, DesiredStateReader)
β βββ infra/
β βββ docker.go # DockerProvider implementation
β βββ kubernetes.go # KubernetesProvider implementation
β βββ compose.go # Docker Compose YAML parser
β βββ k8s_manifest.go # Kubernetes manifest parser
β βββ git.go # Git repository synchronization
β βββ webhook.go # Slack/Discord webhook notifications
β βββ metrics.go # Prometheus metrics exposition
βββ docker-compose.yaml # Example configuration
- Provider Pattern:
InfrastructureProviderinterface for pluggable providers - Adapter Pattern:
ComposeReaderandK8sManifestReaderadapt configuration files - Observer Pattern: Webhook notifications on drift detection
- Singleton Pattern: Prometheus metrics registry
| Type | Scenario | Example |
|---|---|---|
| MISSING | Service declared but not running | Service 'db' (postgres:15) not running |
| SHADOW_IT | Container running but not declared | Container abc123 (redis:latest) not declared |
| PORT_MISMATCH | Desired port not found on container | Port 443:443 not found on nginx |
| IMAGE_MISMATCH | Container image differs from config | Config: nginx:1.20, Container: nginx:1.21 |
| ENV_MISMATCH | Environment variable value differs | APP_ENV: Expected 'prod***', Actual 'stag***' |
| ENV_INJECTED | Undeclared environment variable running | EXTRA_VAR=malicious_value |
- Reads from: Docker daemon via Docker SDK
- Configuration file:
docker-compose.yaml - Extracts: Container ID, Image, Ports, Environment Variables
Usage:
./godriftdetector --provider docker- Reads from: Kubernetes API (kubectl configured)
- Configuration file:
k8s-manifest.yaml - Extracts: Pod name, Container image, Ports, ConfigMaps, Secrets
Usage:
./godriftdetector --provider k8s --namespace production
# With custom kubeconfig
export KUBECONFIG=/path/to/kubeconfig.yaml
./godriftdetector --provider k8sThe agent exposes metrics on port 9090 in Prometheus format:
curl http://localhost:9090/metricsAvailable Metrics:
| Metric | Type | Labels | Description |
|---|---|---|---|
drift_detected_total |
Gauge | - | Total drifts found in last scan |
drift_by_service |
Gauge | service, type | Count of drifts per service and type |
last_scan_timestamp |
Gauge | - | Unix timestamp of last successful scan |
Create a dashboard with these PromQL queries:
# Total drifts
drift_detected_total
# Drifts by service
sum by (service) (drift_by_service)
# Drifts by type
sum by (type) (drift_by_service)
# Time since last scan
time() - last_scan_timestamp
All events are logged to stdout with timestamps and color coding:
- π’ Green: Successful operations and compliance
- π Orange: Warnings and configuration issues
- π΄ Red: Drifts detected and errors
# Repository structure
config-repo/
βββ docker-compose.yaml
βββ nginx.conf
βββ environment.prod
# Setup
export GIT_REPO_URL="https://github.com/org/config-repo.git"
export LOCAL_CONFIG_DIR="/var/lib/godriftdetector/config"
export SYNC_INTERVAL="10m"
export WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
# Run daemon
./godriftdetector
# Expected flow:
# 1. Clone/pull latest from Git
# 2. Parse docker-compose.yaml
# 3. List running Docker containers
# 4. Compare states
# 5. Expose metrics
# 6. Send Slack alerts if drifts found
# 7. Repeat every 10 minutes# Monitor multiple namespaces separately
for ns in default production staging; do
echo "Auditing namespace: $ns"
./godriftdetector --provider k8s --namespace $ns --json | \
jq '.Drifts | length' >> drift-count-$ns.txt
done# .github/workflows/drift-check.yml
name: Infrastructure Drift Check
on:
push:
paths:
- 'docker-compose.yaml'
- 'k8s-manifest.yaml'
jobs:
drift-detection:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.26'
- name: Build GoDriftDetector
run: go build -o godriftdetector ./cmd/godriftdetector
- name: Run Drift Detection
run: |
LOCAL_CONFIG_DIR=. ./godriftdetector --json | \
jq -e '.Drifts | length == 0' || exit 1
- name: Notify on Drift
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-Type: application/json' \
-d '{"text":"Infrastructure drift detected in commit"}'cat > docker-compose.yaml << 'EOF'
version: '3.8'
services:
backend:
image: myapp:1.0
environment:
DATABASE_URL: postgres://db:5432/myapp
API_KEY: supersecret_xyz
LOG_LEVEL: info
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: dbsecret_abc
EOF
# Start services
docker-compose up -d
# Test with modified environment
docker rm -f backend
docker run -d --name backend \
-e DATABASE_URL=postgres://db:5432/different \
-e API_KEY=different_secret \
-e LOG_LEVEL=debug \
-e INJECTED_VAR=malicious \
myapp:1.0
# Detect drifts
LOCAL_CONFIG_DIR=. ./godriftdetector --json
# Shows ENV_MISMATCH for DATABASE_URL, API_KEY, LOG_LEVEL
# Shows ENV_INJECTED for INJECTED_VAR
# Masks values: API_KEY='sup***'- Container downtime detection (MISSING)
- Shadow IT detection (SHADOW_IT)
- Port and image version mismatch detection
- Remote Git synchronization (GitOps)
- Webhook alerts (Slack/Discord)
- JSON audit reports
- Environment variable drift detection with masking
- Kubernetes provider support
- Prometheus metrics exposition
See CONTRIBUTING.md for details on running tests, linting, and opening PRs.
