Hamburger Cross Icon
Kubernetes Guardrails - Lunar Policy for Deployment And Infrastructure

Kubernetes Guardrails

✓ Policy Stable Deployment And Infrastructure

Enforce Kubernetes best practices on plain manifests and rendered Helm charts, including manifest validation, resource limits, probes, PodDisruptionBudgets, autoscaling, topology spread, graceful shutdown, and NetworkPolicy egress to the instance-metadata endpoint.

Add k8s to your lunar-config.yml:
uses: github://earthly/lunar-lib/policies/k8s@v1.0.5

Included Guardrails

This policy includes 21 guardrails that enforce standards for your deployment and infrastructure.

Guardrail

valid

Validates that all Kubernetes manifests in the repository are syntactically correct and conform to K8s schema, and that every Helm chart renders. Invalid manifests will fail deployment.

kubernetes manifest validation yaml kubeconform helm template
View Guardrail
Guardrail

requests-and-limits

Ensures all containers have CPU and memory requests and limits defined. Missing resource specs can cause scheduling failures and noisy neighbor issues.

resources cpu limits memory limits requests qos
View Guardrail
Guardrail

probes

Requires liveness and readiness probes on all containers. Probes enable Kubernetes to detect unhealthy pods and route traffic correctly.

liveness probe readiness probe health checks pod health
View Guardrail
Guardrail

min-replicas

Enforces minimum replica counts on HorizontalPodAutoscalers. Ensures services maintain capacity during scaling events and failures.

hpa replicas high availability autoscaling
View Guardrail
Guardrail

pdb

Requires PodDisruptionBudgets for Deployments and StatefulSets. PDBs protect availability during voluntary disruptions like node drains.

pdb pod disruption budget availability node drain
View Guardrail
Guardrail

non-root

Requires containers to run as non-root users via securityContext. Running as root inside containers violates least privilege principles.

security context non-root runAsNonRoot least privilege
View Guardrail
Guardrail

host-users

Requires workload PodSpecs to set hostUsers: false. User Namespaces (GA in Kubernetes v1.36) map container UIDs to unprivileged host UIDs so that a container escape no longer hands the attacker root on the node. Complements non-root — that check drops privilege inside the container; this one isolates UIDs across the kernel boundary. Target cluster must be on K8s >=1.36; workloads that legitimately need host UIDs (e.g. log shippers reading host paths) can be opted out via include/exclude in lunar-config.yml.

user namespaces hostUsers container escape least privilege kernel boundary
View Guardrail
Guardrail

host-network

Fails when workload PodSpecs set hostNetwork: true. Sharing the host's network namespace bypasses NetworkPolicy enforcement, exposes node-local services to the pod, and gives a compromised container direct access to the node's interfaces (and any cleartext traffic on them). Legitimate hostNetwork users (CNI agents, node-local proxies, host-bound exporters) can opt out via include/exclude in lunar-config.yml.

hostNetwork network namespace NetworkPolicy bypass container escape least privilege
View Guardrail
Guardrail

host-pid

Fails when workload PodSpecs set hostPID: true. Sharing the host's PID namespace lets the container see, signal, and (with the right capabilities) attach to every process on the node, undermining process isolation and aiding lateral movement after a container compromise. Workloads that need it (e.g. node-level monitoring agents) can opt out via include/exclude in lunar-config.yml.

hostPID PID namespace process isolation container escape least privilege
View Guardrail
Guardrail

host-ipc

Fails when workload PodSpecs set hostIPC: true. Sharing the host's IPC namespace exposes node-wide System V IPC and POSIX shared memory to the container, which can be abused to read or tamper with other workloads' shared-memory state. Workloads that genuinely need host IPC can opt out via include/exclude in lunar-config.yml.

hostIPC IPC namespace shared memory container escape least privilege
View Guardrail
Guardrail

metadata-egress-blocked

Requires a NetworkPolicy to keep each workload's pods from reaching the cloud instance-metadata endpoint (169.254.169.254), where node credentials can be read.

NetworkPolicy egress instance metadata IMDS 169.254.169.254 credential theft
View Guardrail
Guardrail

min-kubectl-version

Ensures the kubectl version used in CI meets the minimum required version. Old kubectl clients may miss security fixes or lack support for current cluster APIs.

kubectl version ci/cd compatibility security
View Guardrail
Guardrail

topology-spread

Requires Deployments and StatefulSets that can run more than one replica (their autoscaler's maximum, else spec.replicas) to have a topologySpreadConstraint on topology_key (zone by default) that selects their pods, with maxSkew within topology_max_skew.

topology spread availability zones maxSkew pod placement high availability
View Guardrail
Guardrail

graceful-shutdown

Requires Deployments and StatefulSets to drain before stopping: a preStop hook on a container and a terminationGracePeriodSeconds of at least min_termination_grace_period_seconds.

graceful shutdown preStop terminationGracePeriodSeconds SIGTERM zero-downtime deploys
View Guardrail
Guardrail

pdb-budget

Fails a PodDisruptionBudget that allows no voluntary disruption, which blocks node drains: maxUnavailable 0, or a minAvailable (a percentage rounds up) that covers every pod of a workload at its smallest size, its autoscaler's minimum else spec.replicas. Also fails one with no selector, or with both minAvailable and maxUnavailable set.

pdb pod disruption budget node drain maxUnavailable minAvailable
View Guardrail
Guardrail

no-static-replicas

Fails a Deployment or StatefulSet that sets spec.replicas while an HPA or KEDA ScaledObject scales it: every apply resets the replica count the autoscaler chose.

hpa keda autoscaling spec.replicas argocd sync helm upgrade
View Guardrail
Guardrail

probes-distinct

Requires each container's liveness and readiness probes to check different endpoints, so a pod too busy to serve leaves rotation instead of being restarted.

liveness probe readiness probe health checks probe endpoints
View Guardrail
Guardrail

probe-timeouts

Requires every probe's timeoutSeconds to be at least min_probe_timeout_seconds and below its periodSeconds, so one slow check isn't still running when the next starts.

probe timeout timeoutSeconds periodSeconds health checks pod restarts
View Guardrail
Guardrail

allowed-registries

Requires every container and init container image to come from a registry in allowed_registries. Skipped until the list is set.

container registry image source supply chain security approved registries
View Guardrail
Guardrail

deprecated-api-versions

Fails manifests and rendered charts that use an apiVersion listed in deprecated_api_versions. Skipped until the list is set.

deprecated apis apiVersion kubernetes upgrade removed apis
View Guardrail
Guardrail

pod-annotations

Requires the pod template annotations in required_pod_annotations and fails those in forbidden_pod_annotations. Skipped until either list is set.

pod annotations required annotations forbidden annotations karpenter
View Guardrail

How Guardrails Fit into Lunar

Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.

Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.

Learn How Lunar Works →
1
↓ Integrations Gather Data
Collectors extract metadata from code, CI pipelines, tool outputs, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
✓ Guardrails Enforce Standards This Policy
Real-time feedback in PRs and AI workflows

Required Integrations

This policy evaluates data gathered by one or more of the following integration(s). Make sure to enable them in your lunar-config.yml.

Configuration

Configure this policy in your lunar-config.yml.

Inputs

Input Required Default Description
min_replicas Optional 3 Minimum replicas required for HPAs (default 3)
max_limit_to_request_ratio Optional 4 Maximum ratio of limits to requests for CPU/memory (default 4)
min_kubectl_version Optional 1.28 Minimum kubectl version required in CI (semver, e.g. "1.28")
metadata_ips Optional 169.254.169.254 Comma-separated metadata endpoint IPs that metadata-egress-blocked keeps pods from reaching on TCP port 80 (add fd00:ec2::254 for IPv6 EKS clusters)
topology_key Optional topology.kubernetes.io/zone Node label topology-spread requires a constraint on (default topology.kubernetes.io/zone)
topology_max_skew Optional 1 Largest maxSkew topology-spread accepts on that constraint (default 1)
topology_when_unsatisfiable Required — whenUnsatisfiable value topology-spread requires on that constraint (DoNotSchedule or ScheduleAnyway). Empty accepts either.
topology_require_match_label_keys Optional false Also require the constraint to set matchLabelKeys with pod-template-hash, so a rollout's skew counts only the new revision's pods (true/false)
min_termination_grace_period_seconds Optional 30 Smallest terminationGracePeriodSeconds graceful-shutdown accepts. An unset value counts as the Kubernetes default, 30 (default 30)
require_prestop Optional true Whether graceful-shutdown requires a preStop hook on at least one container (true/false)
min_probe_timeout_seconds Optional 1 Smallest probe timeoutSeconds probe-timeouts accepts. An unset value counts as the Kubernetes default, 1 (default 1)
allowed_registries Required — Comma-separated registries, or registry/path prefixes, images may come from (e.g. "ghcr.io/acme,123456789012.dkr.ecr.us-east-1.amazonaws.com"). An image with no registry counts as docker.io. Empty skips allowed-registries.
deprecated_api_versions Required — Comma-separated apiVersions deprecated-api-versions fails, each optionally narrowed to one kind (e.g. "policy/v1beta1,networking.k8s.io/v1beta1:Ingress"). Empty skips the check.
required_pod_annotations Required — Comma-separated annotation keys every workload's pod template must set. Empty requires none.
forbidden_pod_annotations Required — Comma-separated annotation keys no workload's pod template may set (e.g. "karpenter.sh/do-not-disrupt"). Empty forbids none.

Documentation

View on GitHub

Kubernetes Guardrails

Enforces Kubernetes best practices for production-ready workloads.

Overview

This policy validates Kubernetes manifests against industry best practices including resource limits, health probes, PodDisruptionBudgets, autoscaling, topology spread, graceful shutdown, and security configurations. It evaluates plain manifests and rendered Helm charts alike, so chart-based workloads are held to the same standard. It helps ensure your K8s workloads are production-ready and resilient.

Policies

This policy provides the following guardrails (use include to select a subset):

Policy Description Failure Meaning
valid Validates K8s manifest syntax, and that Helm charts render Manifest has YAML or schema errors, or a chart fails to render
requests-and-limits Checks CPU/memory requests and limits Container missing resource specs
probes Requires liveness and readiness probes Container missing health probes
min-replicas Enforces minimum HPA replicas HPA minReplicas below threshold
pdb Requires PodDisruptionBudgets Deployment/StatefulSet missing PDB
non-root Requires non-root security context Container may run as root
host-users Requires PodSpecs to set hostUsers: false (K8s ≥1.36 user namespaces) Container UIDs are not isolated from host UIDs — a container escape gives the attacker root on the node
host-network Forbids hostNetwork: true on PodSpecs Workload shares the host network namespace — bypasses NetworkPolicy and exposes node interfaces
host-pid Forbids hostPID: true on PodSpecs Workload shares the host PID namespace — can see, signal, and potentially attach to processes on the node
host-ipc Forbids hostIPC: true on PodSpecs Workload shares the host IPC namespace — can read or tamper with node-wide shared memory
metadata-egress-blocked Requires NetworkPolicy to block pod egress to the instance-metadata endpoint Workload's pods can reach 169.254.169.254, where node credentials can be read
min-kubectl-version Enforces minimum kubectl version in CI kubectl client used in CI is below threshold
topology-spread Requires a topologySpreadConstraint on topology_key (zone by default) for Deployments and StatefulSets that can run more than one replica (see Replica counts) Replicas can all land in one zone, so one zone failure takes the workload down
graceful-shutdown Requires a preStop hook and a long enough termination grace period on Deployments and StatefulSets Pods stop while requests are still in flight or still being routed to them
pdb-budget Requires every PodDisruptionBudget to allow at least one voluntary disruption at the smallest size of the workloads it covers (see Replica counts) The budget blocks node drains, so nodes can't be upgraded or patched
no-static-replicas Forbids spec.replicas on a workload an HPA or KEDA ScaledObject scales Every deploy resets the replica count the autoscaler chose
probes-distinct Requires liveness and readiness probes to check different endpoints An overloaded pod is restarted instead of taken out of rotation
probe-timeouts Requires probe timeouts of at least min_probe_timeout_seconds and below the probe period Slow responses fail probes, or a check is still running when the next one starts
allowed-registries Restricts container and init container images to allowed_registries (opt-in) Workload pulls an image from an unapproved registry
deprecated-api-versions Forbids the apiVersions in deprecated_api_versions (opt-in) Manifest uses an API that is deprecated or removed on the target clusters
pod-annotations Enforces required_pod_annotations and forbidden_pod_annotations on pod templates (opt-in) Pod template is missing a required annotation or sets a forbidden one

Helm charts

The k8s collector's helm sub-collector renders each chart and records the objects with a render key: the chart directory and the values files used. Every check covers those objects like plain manifests, with these differences:

  • A failure names the chart template and the values files, e.g. charts/api/templates/deployment.yaml [values-prod.yaml]: .... When the same finding shows up in several values sets of a chart, it's reported once and lists them.
  • A workload is never matched to a PodDisruptionBudget, autoscaler or NetworkPolicy from another values set of its own chart, since those are alternative deployments of the same release. Objects from the same render, from plain manifests and from other charts all match, so a plain-manifest workload can be covered by a chart's PodDisruptionBudget.
  • A chart that fails to render fails valid with helm's error, so it can't pass every other check by contributing nothing.
  • A chart that no helm_values line or helm_values_chains chain applies to is only checked by valid: its default render contributes no objects, because chart defaults (resources: {}, a disabled PodDisruptionBudget) are often left for whoever deploys the chart to fill in. When such charts are a component's only Kubernetes content, the workload checks skip and say that no values set applies, pointing at helm_values and helm_values_chains.

The checks added alongside chart rendering (topology-spread through pod-annotations) skip when the k8s collector is older than the fields they read, and when nothing in the repository is in their scope, such as no workload that can run more than one replica, rather than passing on nothing.

Replica counts

A workload that an HPA or KEDA ScaledObject scales gets its size from the autoscaler, matched by scaleTargetRef in the same namespace (and under the render rules above), since no-static-replicas asks for spec.replicas to be left out. pdb-budget uses its smallest size, the autoscaler's minimum (minReplicas / minReplicaCount, at least 1), and topology-spread its largest (maxReplicas / maxReplicaCount). A workload without one uses spec.replicas, or 1 when it's unset. Percentages round up against that count, as Kubernetes' disruption controller does: minAvailable: 50% of 3 pods leaves one free and passes, 67% of 3 covers all of them and fails, and only maxUnavailable: 0 or 0% blocks every drain. A budget that selects several workloads counts their pods together, as Kubernetes does.

Required Data

This policy reads from the following Component JSON paths:

Path Type Provided By
.k8s.manifests[] array k8s collector (k8s and helm sub-collectors)
.k8s.workloads[] array k8s collector (k8s and helm sub-collectors)
.k8s.hpas[] array k8s collector (k8s and helm sub-collectors)
.k8s.scaled_objects[] array k8s collector (k8s and helm sub-collectors)
.k8s.pdbs[] array k8s collector (k8s and helm sub-collectors)
.k8s.network_policies[] array k8s collector (k8s and helm sub-collectors)
.k8s.cicd.cmds[] array k8s collector (cicd sub-collector)

Note: Ensure the k8s collector is configured before enabling this policy.

Installation

Add to your lunar-config.yml:

collectors:
  - uses: github://earthly/lunar-lib/collectors/k8s@v1.0.0
    on: [kubernetes]
    include:
      - k8s   # Plain manifests
      - helm  # Helm charts, rendered
      - cicd  # kubectl commands in CI

policies:
  - uses: github://earthly/lunar-lib/policies/k8s@v1.0.0
    on: [kubernetes]
    enforcement: report-pr
    include:
      - valid
      - requests-and-limits
      - probes
      - min-replicas
      - pdb
      - non-root
      - host-users
      - host-network
      - host-pid
      - host-ipc
      - metadata-egress-blocked
      - min-kubectl-version
      - topology-spread
      - graceful-shutdown
      - pdb-budget
      - no-static-replicas
      - probes-distinct
      - probe-timeouts
      - allowed-registries
      - deprecated-api-versions
      - pod-annotations
    # with:
    #   min_replicas: "3"
    #   max_limit_to_request_ratio: "4"
    #   min_kubectl_version: "1.28"
    #   metadata_ips: "169.254.169.254,fd00:ec2::254"  # IPv6 EKS clusters
    #   topology_key: "topology.kubernetes.io/zone"
    #   topology_max_skew: "1"
    #   topology_when_unsatisfiable: "ScheduleAnyway"
    #   min_termination_grace_period_seconds: "60"
    #   min_probe_timeout_seconds: "2"
    #   allowed_registries: "ghcr.io/acme,123456789012.dkr.ecr.us-east-1.amazonaws.com"
    #   deprecated_api_versions: "policy/v1beta1,autoscaling/v2beta2"
    #   required_pod_annotations: "prometheus.io/scrape"
    #   forbidden_pod_annotations: "karpenter.sh/do-not-disrupt"

Examples

Passing Example

A compliant component with proper resource specs, probes, spread, shutdown handling, and security context:

{
  "k8s": {
    "manifests": [
      {"path": "deploy/deployment.yaml", "valid": true, "resources": [
        {"kind": "Deployment", "name": "payment-api", "namespace": "payments", "api_version": "apps/v1"}
      ]}
    ],
    "workloads": [
      {
        "kind": "Deployment",
        "name": "payment-api",
        "namespace": "payments",
        "path": "deploy/deployment.yaml",
        "replicas": 3,
        "replicas_set": true,
        "pod_labels": {"app": "payment-api"},
        "pod_annotations": {},
        "host_users": false,
        "host_network": false,
        "host_pid": false,
        "host_ipc": false,
        "termination_grace_period_seconds": 60,
        "topology_spread_constraints": [
          {"topology_key": "topology.kubernetes.io/zone", "max_skew": 1, "when_unsatisfiable": "ScheduleAnyway", "label_selector": {"matchLabels": {"app": "payment-api"}}, "match_label_keys": ["pod-template-hash"]}
        ],
        "containers": [
          {
            "name": "api",
            "image": "gcr.io/acme/payment-api:v1.2.3",
            "has_resources": true,
            "has_requests": true,
            "has_limits": true,
            "has_liveness_probe": true,
            "has_readiness_probe": true,
            "liveness_probe": {"handler": "httpGet", "port": 8080, "path": "/livez", "period_seconds": 10, "timeout_seconds": 2},
            "readiness_probe": {"handler": "httpGet", "port": 8080, "path": "/readyz", "period_seconds": 5, "timeout_seconds": 2},
            "has_prestop": true,
            "runs_as_non_root": true
          }
        ],
        "init_containers": []
      }
    ],
    "pdbs": [
      {"name": "payment-api-pdb", "namespace": "payments", "selector": {"matchLabels": {"app": "payment-api"}}, "min_available": null, "max_unavailable": 1}
    ],
    "network_policies": [
      {
        "name": "egress-no-metadata",
        "namespace": "payments",
        "pod_selector": {},
        "policy_types": ["Egress"],
        "egress": [{"to": [{"ipBlock": {"cidr": "0.0.0.0/0", "except": ["169.254.169.254/32"]}}]}]
      }
    ]
  }
}

Failing Example

A Helm chart rendered with values-prod.yaml, plus a second chart whose dependencies aren't vendored:

{
  "k8s": {
    "manifests": [
      {"path": "charts/my-app", "render": {"chart": "charts/my-app", "values": ["values.yaml", "values-prod.yaml"], "validated_only": false}, "valid": true, "resources": [
        {"kind": "Deployment", "name": "my-app", "namespace": "default", "api_version": "apps/v1"}
      ]},
      {"path": "charts/reports", "render": {"chart": "charts/reports", "values": ["values.yaml"], "validated_only": true}, "valid": false,
       "error": "helm template: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: postgresql"}
    ],
    "workloads": [
      {
        "kind": "Deployment",
        "name": "my-app",
        "namespace": "default",
        "path": "charts/my-app/templates/deployment.yaml",
        "render": {"chart": "charts/my-app", "values": ["values.yaml", "values-prod.yaml"]},
        "replicas": 2,
        "replicas_set": true,
        "pod_labels": {"app": "my-app"},
        "pod_annotations": {"karpenter.sh/do-not-disrupt": "true"},
        "host_users": true,
        "host_network": true,
        "host_pid": true,
        "host_ipc": true,
        "termination_grace_period_seconds": 30,
        "topology_spread_constraints": [],
        "containers": [
          {
            "name": "app",
            "image": "nginx:1.25",
            "has_resources": false,
            "has_requests": false,
            "has_limits": false,
            "has_liveness_probe": true,
            "has_readiness_probe": true,
            "liveness_probe": {"handler": "httpGet", "port": 8080, "path": "/healthz", "period_seconds": 10, "timeout_seconds": 10},
            "readiness_probe": {"handler": "httpGet", "port": 8080, "path": "/healthz", "period_seconds": 10, "timeout_seconds": 1},
            "has_prestop": false,
            "runs_as_non_root": false
          }
        ],
        "init_containers": []
      }
    ],
    "pdbs": [
      {"name": "my-app", "namespace": "default", "path": "charts/my-app/templates/pdb.yaml", "render": {"chart": "charts/my-app", "values": ["values.yaml", "values-prod.yaml"]},
       "selector": {"matchLabels": {"app": "my-app"}}, "min_available": 2, "max_unavailable": null}
    ],
    "hpas": [
      {"name": "my-app", "namespace": "default", "path": "charts/my-app/templates/hpa.yaml", "render": {"chart": "charts/my-app", "values": ["values.yaml", "values-prod.yaml"]},
       "target_workload": "my-app", "min_replicas": 2, "max_replicas": 6}
    ]
  }
}

Failure messages (allowed-registries with allowed_registries: "ghcr.io/acme", pod-annotations with forbidden_pod_annotations: "karpenter.sh/do-not-disrupt"):

  • charts/reports [values.yaml]: helm template: An error occurred while checking for chart dependencies. ... missing in charts/ directory: postgresql
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app container 'app' missing resource requests
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app container 'app' should set securityContext.runAsNonRoot: true
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app should not set spec.hostNetwork: true (workload shares the host network namespace)
  • charts/my-app/templates/hpa.yaml [values-prod.yaml]: HPA default/my-app has minReplicas=2, need at least 3
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app has no topologySpreadConstraint on topology.kubernetes.io/zone
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app has no container with a preStop hook
  • charts/my-app/templates/pdb.yaml [values-prod.yaml]: PodDisruptionBudget default/my-app allows no voluntary disruptions: minAvailable 2 covers Deployment my-app, which runs at least 2 replicas
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app sets spec.replicas: 2 while HorizontalPodAutoscaler my-app scales it
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app container 'app' livenessProbe and readinessProbe both check httpGet :8080/healthz
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app container 'app' livenessProbe timeoutSeconds 10 is not below periodSeconds 10
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app container 'app' image nginx:1.25 (docker.io/library/nginx) is not from an allowed registry
  • charts/my-app/templates/deployment.yaml [values-prod.yaml]: Deployment default/my-app pod template sets forbidden annotation karpenter.sh/do-not-disrupt

Remediation

When this policy fails, resolve it by:

  1. For valid failures: Fix YAML syntax errors or invalid K8s fields in the manifest. For a chart that fails to render, fix the error helm reports: vendor missing dependencies into the chart's charts/ directory (or set the collector's helm_dependency_build: true), and point helm_values at the values files the chart needs
  2. For requests-and-limits failures: Add resources.requests and resources.limits for CPU and memory
  3. For probes failures: Add livenessProbe and readinessProbe to each container
  4. For min-replicas failures: Increase spec.minReplicas in your HPA to meet the threshold
  5. For pdb failures: Create a PodDisruptionBudget that selects your workload's pods
  6. For non-root failures: Add securityContext.runAsNonRoot: true to the container or pod spec
  7. For host-users failures: Add spec.hostUsers: false to the PodSpec (or spec.template.spec.hostUsers: false for Deployments/StatefulSets/DaemonSets/Jobs/CronJobs). Requires Kubernetes ≥1.36 in the target cluster. Privileged workloads that must share the host user namespace (e.g. log shippers reading host paths, kubelets, container-runtime sidecars) can use include/exclude in lunar-config.yml to opt out.
  8. For host-network failures: Remove spec.hostNetwork: true from the PodSpec. Workloads that legitimately need the host network (CNI agents, node-local proxies, host-bound metrics exporters) can opt out via include/exclude in lunar-config.yml.
  9. For host-pid failures: Remove spec.hostPID: true from the PodSpec. Node-level monitoring agents that need a host-wide process view can opt out via include/exclude in lunar-config.yml.
  10. For host-ipc failures: Remove spec.hostIPC: true from the PodSpec. Workloads that genuinely need host IPC (rare — usually legacy shared-memory consumers) can opt out via include/exclude in lunar-config.yml.
  11. For metadata-egress-blocked failures: Select the workload with a NetworkPolicy that has Egress in policyTypes, namespace-wide (podSelector: {}) or per workload, and allow 0.0.0.0/0 with except: [169.254.169.254/32], or no egress at all. Policies are additive: any selecting policy with a rule that has no to, or an ipBlock without the except, reopens TCP 80 to the endpoint. Only networking.k8s.io NetworkPolicy in this repository is read, so exclude the check where the endpoint is blocked some other way (e.g. AdminNetworkPolicy), or where pods need it by design (GKE Workload Identity serves them through it). hostNetwork workloads are left to host-network.
  12. For min-kubectl-version failures: Upgrade the kubectl client in your CI pipeline (e.g., pin azure/setup-kubectl@v4 or setup-kubectl action to a newer version, or update the installed kubectl on self-hosted runners)
  13. For topology-spread failures: Add a topologySpreadConstraints entry with topologyKey set to topology_key, a labelSelector matching the pod template labels, and maxSkew within topology_max_skew
  14. For graceful-shutdown failures: Add a lifecycle.preStop hook (for example sleep: {seconds: 5}) so endpoints are removed before the process gets SIGTERM, make the application drain on SIGTERM, and raise terminationGracePeriodSeconds to cover the preStop delay plus the longest request
  15. For pdb-budget failures: Set maxUnavailable to 1 (or a percentage) instead of a minAvailable that equals the replica count, set only one of the two fields, and give the budget a selector
  16. For no-static-replicas failures: Stop rendering spec.replicas when an autoscaler manages the workload (in a chart, wrap it in {{- if not .Values.autoscaling.enabled }}), and set the floor on the autoscaler's minimum instead
  17. For probes-distinct failures: Give liveness and readiness their own endpoints: liveness should only report whether the process is stuck, readiness whether the pod can take traffic right now
  18. For probe-timeouts failures: Set timeoutSeconds explicitly, at or above min_probe_timeout_seconds and below periodSeconds
  19. For allowed-registries failures: Pull the image from an approved registry, mirroring it there first if needed
  20. For deprecated-api-versions failures: Move the resource to its current apiVersion (kubectl convert or the Kubernetes deprecation guide lists the replacement); in a chart, also check templates that pick the apiVersion from .Capabilities
  21. For pod-annotations failures: Add the missing annotation under spec.template.metadata.annotations, or remove the forbidden one

Consumers who want any of these surfaced without blocking can pin enforcement: report-pr at config time — but that's a consumer-side knob; the checks themselves just pass or fail.

Open Source

This policy is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.

View Repository

Common Use Cases

Explore how individual guardrails work with specific integrations.

+
Valid + Kubernetes Collector Validates that all Kubernetes manifests in the repository are syntactically...
→
+
Requests And Limits + Kubernetes Collector Ensures all containers have CPU and memory requests and limits defined. Missing...
→
+
Probes + Kubernetes Collector Requires liveness and readiness probes on all containers. Probes enable...
→
+
Min Replicas + Kubernetes Collector Enforces minimum replica counts on HorizontalPodAutoscalers. Ensures services...
→
+
Pdb + Kubernetes Collector Requires PodDisruptionBudgets for Deployments and StatefulSets. PDBs protect...
→
+
Non Root + Kubernetes Collector Requires containers to run as non-root users via securityContext. Running as...
→
+
Host Users + Kubernetes Collector Requires workload PodSpecs to set `hostUsers: false`. User Namespaces (GA in...
→
+
Host Network + Kubernetes Collector Fails when workload PodSpecs set `hostNetwork: true`. Sharing the host's network...
→
+
Host Pid + Kubernetes Collector Fails when workload PodSpecs set `hostPID: true`. Sharing the host's PID...
→
+
Host Ipc + Kubernetes Collector Fails when workload PodSpecs set `hostIPC: true`. Sharing the host's IPC...
→
+
Metadata Egress Blocked + Kubernetes Collector Requires a NetworkPolicy to keep each workload's pods from reaching the cloud...
→
+
Min Kubectl Version + Kubernetes Collector Ensures the kubectl version used in CI meets the minimum required version. Old...
→
+
Topology Spread + Kubernetes Collector Requires Deployments and StatefulSets that can run more than one replica (their...
→
+
Graceful Shutdown + Kubernetes Collector Requires Deployments and StatefulSets to drain before stopping: a preStop hook...
→
+
Pdb Budget + Kubernetes Collector Fails a PodDisruptionBudget that allows no voluntary disruption, which blocks...
→
+
No Static Replicas + Kubernetes Collector Fails a Deployment or StatefulSet that sets spec.replicas while an HPA or KEDA...
→
+
Probes Distinct + Kubernetes Collector Requires each container's liveness and readiness probes to check different...
→
+
Probe Timeouts + Kubernetes Collector Requires every probe's timeoutSeconds to be at least `min_probe_timeout_seconds`...
→
+
Allowed Registries + Kubernetes Collector Requires every container and init container image to come from a registry...
→
+
Deprecated Api Versions + Kubernetes Collector Fails manifests and rendered charts that use an apiVersion listed...
→
+
Pod Annotations + Kubernetes Collector Requires the pod template annotations in `required_pod_annotations` and fails...
→

Ready to Automate Your Standards?

See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 300+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Auto-ID My Guardrails
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo