KubeLinter is an open-source static analysis tool from StackRox (Red Hat) that checks Kubernetes YAML files and Helm charts against a library of security and correctness best practices. KubeLinter evaluates objects for issues like containers running as root, missing read-only root file systems, absent resource limits, privileged containers, and missing network policies — producing detailed actionable findings before manifests ever reach a cluster. When KubeLinter's CI gate is silently disabled, when a Helm chart linting job stops running, or when the policy enforcement workflow breaks, non-compliant Kubernetes resources reach production undetected. Vigilmon gives you heartbeat monitoring for every KubeLinter pipeline stage that enforces your Kubernetes security baseline.
What You'll Build
- Heartbeat monitors for KubeLinter CI/CD pipeline gates on pull requests
- Heartbeat monitors for scheduled full-repository lint runs
- Heartbeat monitors for Helm chart-specific KubeLinter validation
- HTTP and SSL monitors for any API endpoints in the linting workflow
- Alerting runbook mapping KubeLinter pipeline failures to Vigilmon signals
Prerequisites
- KubeLinter installed (
brew install kubelinteror download from StackRox releases) - Kubernetes YAML manifests or Helm charts in a repository
- CI/CD pipeline (GitHub Actions, GitLab CI, or similar)
- A free account at vigilmon.online
Step 1: Understand the KubeLinter Pipeline Health Surface
KubeLinter is a CLI tool that runs inside CI/CD pipelines and has no persistent HTTP service. All health signals come from pipeline execution:
# Lint a single Kubernetes manifest
kube-linter lint deployment.yaml
# Expected: Pass or list of lint findings with severity
# Lint all YAML files in a directory
kube-linter lint k8s/
# Expected: Combined lint output, exit code 1 on findings
# Lint a Helm chart
kube-linter lint charts/myapp/
# Expected: Chart templates evaluated against all checks
# List all available checks
kube-linter checks list
# Expected: Table of check name, description, remediation
# Run with custom policy configuration
kube-linter lint --config .kube-linter.yaml k8s/
# Expected: Only checks defined in config file
# Output as JSON for programmatic processing
kube-linter lint --format json k8s/ | python3 -c "
import sys, json
results = json.load(sys.stdin)
for item in results.get('Items', []):
print(f\"{item['Object']['K8sObject']['Name']}: {item['Diagnostic']['Message']}\")
"
# Return codes
# 0 — no lint findings
# 1 — lint findings found
# Other — internal error (file not found, parse failure)
The key health signals to monitor:
- KubeLinter CI gate running on every pull request that touches YAML or Helm files
- Scheduled full-repository lint runs to catch drift in existing manifests
- Helm-specific validation pipelines
- Any notification or ticketing endpoints in the linting workflow
Step 2: Heartbeat Monitoring for KubeLinter CI Gates
Pull Request Manifest Gate
A GitHub Actions workflow that blocks merges when KubeLinter finds security or correctness issues:
# .github/workflows/kubelinter.yml
name: KubeLinter YAML analysis
on:
pull_request:
paths:
- 'k8s/**'
- '**.yaml'
- '**.yml'
jobs:
kubelinter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install KubeLinter
run: |
curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
tar xzf - -C /usr/local/bin kube-linter
- name: Lint Kubernetes manifests
run: kube-linter lint k8s/
- name: Lint Helm charts
run: |
for CHART in charts/*/; do
echo "Linting: ${CHART}"
kube-linter lint "${CHART}"
done
- name: Ping Vigilmon heartbeat
if: success()
run: curl -s https://vigilmon.online/heartbeat/abc123
Set up the heartbeat monitor in Vigilmon:
- Log in to Vigilmon → Add Monitor → Cron Heartbeat.
- Expected interval:
1500minutes (expected on every PR with active development; 1-day grace period). - Copy the heartbeat URL:
https://vigilmon.online/heartbeat/abc123. - Paste it as the final step in the workflow.
- Label:
KubeLinter PR gate — YAML manifests. - Click Save.
KubeLinter with Custom Policy Configuration
Teams often configure a .kube-linter.yaml policy file to customize which checks are enforced. Make sure the custom config is linted too:
# .kube-linter.yaml (checked into the repository)
customChecks: []
checks:
addAllBuiltIn: true
exclude:
- "unset-cpu-requirements" # tracked separately in VIG-XXXX
include:
- "privileged-container"
- "run-as-non-root"
- "read-only-root-filesystem"
- "no-read-only-root-fs-for-init-containers"
- "required-label-owner"
- "env-var-secret"
- "no-extensions-v1beta"
- "resource-requirements"
# .github/workflows/kubelinter-policy.yml
name: KubeLinter with custom policy
on:
pull_request:
paths:
- 'k8s/**'
- 'charts/**'
- '.kube-linter.yaml'
jobs:
kubelinter-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install KubeLinter
run: |
curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
tar xzf - -C /usr/local/bin kube-linter
- name: Lint with custom policy
run: kube-linter lint --config .kube-linter.yaml k8s/ charts/
- name: Validate policy file itself
run: kube-linter checks list --config .kube-linter.yaml
- name: Ping Vigilmon heartbeat
if: success()
run: curl -s https://vigilmon.online/heartbeat/def456
- Add Monitor → Cron Heartbeat.
- Expected interval:
1500minutes. - Label:
KubeLinter PR gate — custom policy. - Click Save.
Step 3: Scheduled Full-Repository Lint Run
A nightly job that runs KubeLinter across the entire repository, including manifests not recently modified:
# .github/workflows/kubelinter-nightly.yml
name: KubeLinter nightly full scan
on:
schedule:
- cron: '0 2 * * *'
jobs:
full-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install KubeLinter
run: |
curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
tar xzf - -C /usr/local/bin kube-linter
- name: Full repository lint
run: |
kube-linter lint --format json k8s/ charts/ > /tmp/lint-results.json 2>&1 || true
FINDING_COUNT=$(python3 -c "
import json
with open('/tmp/lint-results.json') as f:
results = json.load(f)
print(len(results.get('Items', [])))
" 2>/dev/null || echo "0")
echo "KubeLinter findings: ${FINDING_COUNT}"
# Fail if findings exceed a configured threshold
MAX_ALLOWED=0
if [ "${FINDING_COUNT}" -gt "${MAX_ALLOWED}" ]; then
echo "FAIL: ${FINDING_COUNT} lint findings exceed threshold of ${MAX_ALLOWED}"
cat /tmp/lint-results.json | python3 -c "
import sys, json
for item in json.load(sys.stdin).get('Items', []):
print(f\" {item['Object']['K8sObject']['Name']}: {item['Diagnostic']['Message']}\")
"
exit 1
fi
- name: Ping Vigilmon heartbeat
if: success()
run: curl -s https://vigilmon.online/heartbeat/ghi789
Set up the heartbeat monitor:
- Add Monitor → Cron Heartbeat.
- Expected interval:
1500minutes (nightly at 02:00 UTC, 60-minute grace period). - Label:
KubeLinter nightly full scan. - Click Save.
Step 4: Helm Chart KubeLinter Pipeline
A dedicated Helm chart linting pipeline that catches issues specific to Helm-templated output:
#!/bin/bash
# helm-kubelinter.sh — lint all Helm charts in the repository
CHARTS_DIR="${1:-charts/}"
VIGILMON_HB="https://vigilmon.online/heartbeat/jkl012"
FAILED=0
# Verify Helm and kube-linter are available
if ! command -v helm &>/dev/null; then
echo "ERROR: helm not found on PATH"
exit 1
fi
if ! command -v kube-linter &>/dev/null; then
echo "ERROR: kube-linter not found on PATH"
exit 1
fi
for CHART in "${CHARTS_DIR}"*/; do
CHART_NAME=$(basename "${CHART}")
echo "=== Linting Helm chart: ${CHART_NAME} ==="
# Update chart dependencies
helm dependency update "${CHART}" 2>/dev/null
# Run kube-linter directly on the chart directory
if ! kube-linter lint "${CHART}" --config .kube-linter.yaml; then
echo "FAIL: ${CHART_NAME} has KubeLinter findings"
FAILED=1
else
echo "OK: ${CHART_NAME} passed KubeLinter"
fi
# Also lint with non-default values (e.g., production values)
if [ -f "${CHART}/values.production.yaml" ]; then
echo "Linting ${CHART_NAME} with production values..."
TMPDIR=$(mktemp -d)
helm template "${CHART_NAME}-prod" "${CHART}" \
-f "${CHART}/values.production.yaml" \
--output-dir "${TMPDIR}" 2>/dev/null
if ! kube-linter lint "${TMPDIR}"; then
echo "FAIL: ${CHART_NAME} with production values has KubeLinter findings"
FAILED=1
fi
rm -rf "${TMPDIR}"
fi
done
if [ "${FAILED}" -eq 0 ]; then
curl -s "${VIGILMON_HB}"
fi
# .github/workflows/helm-kubelinter.yml
name: KubeLinter Helm chart validation
on:
push:
paths:
- 'charts/**'
schedule:
- cron: '0 4 * * *'
jobs:
helm-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Helm
run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- name: Install KubeLinter
run: |
curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
tar xzf - -C /usr/local/bin kube-linter
- name: Run Helm + KubeLinter
run: bash scripts/helm-kubelinter.sh charts/
- name: Ping Vigilmon heartbeat
if: success()
run: curl -s https://vigilmon.online/heartbeat/jkl012
- Add Monitor → Cron Heartbeat.
- Expected interval:
1500minutes (daily + on every Helm chart push). - Label:
KubeLinter Helm chart validation. - Click Save.
Step 5: Monitor Notification and Integration Endpoints
If the KubeLinter pipeline posts findings to a Slack webhook, a security dashboard, or a Jira project, monitor those endpoints:
# Verify Slack webhook for KubeLinter alerts
curl -s -o /dev/null -w "%{http_code}" \
-X POST https://hooks.slack.com/services/XXXXXX/YYYYYY/ZZZZZZ \
-H "Content-Type: application/json" \
-d '{"text": "KubeLinter webhook test"}'
# Expected: 200
# Verify security findings dashboard
curl -I https://security-dashboard.example.com/api/lint-results
# Expected: 200 or 401
# Verify OPA/Gatekeeper policy sync endpoint (if syncing KubeLinter findings)
curl -I https://policy-server.example.com/v1/policies
# Expected: 200 or 401
- Add Monitor → HTTP.
- URL:
https://security-dashboard.example.com/api/lint-results. - Expected status codes:
200, 401. - Label:
KubeLinter security dashboard API. - Click Save.
Add SSL certificate monitors:
- Add Monitor → SSL Certificate.
- Domain:
security-dashboard.example.com. - Alert when expiry is within: 30 days.
- Label:
KubeLinter dashboard TLS certificate. - Click Save.
Step 6: Policy Drift Detection
A periodic check that compares current KubeLinter findings against a saved baseline — detecting when newly added manifests introduce violations that weren't in the previous baseline:
#!/bin/bash
# kubelinter-drift-check.sh — run every 6 hours to detect policy drift
MANIFEST_DIRS="k8s/ charts/"
BASELINE="/var/lib/kubelinter/baseline-findings.txt"
VIGILMON_HB="https://vigilmon.online/heartbeat/mno345"
FAILED=0
# Get current findings sorted for stable comparison
CURRENT_FINDINGS=$(kube-linter lint --format json ${MANIFEST_DIRS} 2>/dev/null | \
python3 -c "
import sys, json
results = json.load(sys.stdin)
findings = []
for item in results.get('Items', []):
findings.append(f\"{item['Object']['K8sObject']['Name']}|{item['Check']}\")
for f in sorted(findings):
print(f)
" 2>/dev/null)
CURRENT_COUNT=$(echo "${CURRENT_FINDINGS}" | grep -c . 2>/dev/null || echo "0")
if [ -f "${BASELINE}" ]; then
BASELINE_COUNT=$(wc -l < "${BASELINE}" 2>/dev/null || echo "0")
NEW_FINDINGS=$(comm -23 \
<(echo "${CURRENT_FINDINGS}" | sort) \
<(sort "${BASELINE}"))
if [ -n "${NEW_FINDINGS}" ]; then
echo "DRIFT DETECTED: New KubeLinter findings since baseline:"
echo "${NEW_FINDINGS}"
FAILED=1
else
echo "OK: No new KubeLinter findings (${CURRENT_COUNT} total, baseline: ${BASELINE_COUNT})"
echo "${CURRENT_FINDINGS}" > "${BASELINE}"
fi
else
echo "No baseline — saving current state (${CURRENT_COUNT} findings)"
mkdir -p "$(dirname "${BASELINE}")"
echo "${CURRENT_FINDINGS}" > "${BASELINE}"
fi
if [ "${FAILED}" -eq 0 ]; then
curl -s "${VIGILMON_HB}"
fi
Schedule via cron:
# /etc/cron.d/kubelinter-drift
0 */6 * * * ci-user /opt/scripts/kubelinter-drift-check.sh >> /var/log/kubelinter-drift.log 2>&1
- Add Monitor → Cron Heartbeat.
- Expected interval:
420minutes (runs every 6 hours with a 60-minute grace period). - Label:
KubeLinter policy drift detector. - Click Save.
Step 7: Configure Alerting
In Vigilmon under Settings → Notifications, configure alert channels and response runbooks:
| Monitor | Trigger | Immediate action | |---|---|---| | PR manifest gate heartbeat | Missed ping | Check GitHub Actions for disabled workflow; confirm active PRs are touching YAML — distinguish no PRs from broken gate | | PR custom policy gate heartbeat | Missed ping | Check workflow for .kube-linter.yaml parse errors; verify policy file is valid | | Nightly full scan heartbeat | Missed ping | Check scheduled workflow; look for KubeLinter install failure or timeout on large repo | | Helm chart validation heartbeat | Missed ping | Check Helm dependency update step; look for chart template parse failures | | Drift detector heartbeat | Missed ping | Check cron job logs; verify KubeLinter binary is accessible for the cron user | | Security dashboard API | Non-200/401 | Findings are not being recorded; teams cannot review historical lint trends | | Dashboard TLS certificate | < 30 days | Rotate certificate; confirm CI pipelines can POST results after renewal |
Common KubeLinter Pipeline Failure Modes and What Vigilmon Catches
| Scenario | Vigilmon signal | |---|---| | KubeLinter CI step commented out in workflow | PR gate heartbeat goes silent; insecure manifests can merge | | KubeLinter binary not in CI runner image | Pipeline fails at install; PR gate heartbeat fires (fails before ping) | | .kube-linter.yaml policy file has syntax error | Custom policy gate heartbeat fires on every PR; no actual linting runs | | Nightly scan workflow accidentally disabled | Nightly heartbeat goes silent; existing manifest violations accumulate undetected | | Helm chart dependency update fails before linting | Helm validation heartbeat fires; chart changes ship without lint check | | New manifest added outside scanned path | PR gate passes vacuously; drift detector catches new violations in the next cycle | | Security dashboard URL rotated | Dashboard API monitor fires; CI pipelines fail to POST results | | Dashboard TLS certificate expires | SSL monitor fires; curl POST fails with TLS handshake error | | Policy drift from new deployment added without review | Drift detector heartbeat fires; new violations flagged before next release | | KubeLinter version mismatch in CI vs local | PR gate fires on new checks only available in newer version; pin version in CI |
KubeLinter enforces your Kubernetes security baseline in CI/CD — but only as long as its pipeline gates keep running and linting every change. Vigilmon's heartbeat monitors give you the external signal that confirms your KubeLinter gates are active on every pull request, your nightly full scans are running on schedule, and your Helm chart validation jobs haven't silently stopped. When a workflow file is edited to skip the linting step, or when a scheduled scan stops triggering, Vigilmon alerts you before non-compliant Kubernetes resources reach a cluster.
Start monitoring your KubeLinter pipelines in under 5 minutes — register free at vigilmon.online.