How a 3 AM Kubernetes incident led me to build StackGuard, an open-source tool that scans your infra config and scores it before it reaches your production server.

It was 3 AM on a Tuesday. My phone was buzzing.

Our API pods were getting OOMKilled in a loop. Users were seeing 502s. The Slack channel was lighting up. I SSHed into the cluster, ran kubectl describe pod, and there it was:

Last State: Terminated Reason: OOMKilled 

No resource limits. Someone had deployed a new service to production with no memory limits set. The container ate through the node's memory and took down three other pods with it.

The fix took two minutes. Adding resources.limits.memory: 512Mi to the deployment manifest. The incident took four hours.

That wasn't the first time something like this happened. A month before, a Terraform apply had gone through with an S3 bucket set to public-read. Nobody caught it in code review. Three weeks before that, a Dockerfile was running as root in production because nobody had added a USER directive.

Every single one of these was a known best practice. Every single one was missed because humans are bad at catching config mistakes in YAML files at 4 PM on a Friday.

The tools I tried

I looked at what was out there. There are great tools, don't get me wrong. But here's what I kept running into:

Scattered coverage. One tool does Terraform. Another does Kubernetes. A third does Dockerfiles. Now I need three tools in my CI pipeline, three sets of configuration, three different output formats.

No single number. Every tool gives me a list of findings. Some runs produce 47 warnings. Are we getting better or worse? Is this deploy safer than the last one? I had no way to answer that without reading every line.

Plugin SDKs and policy languages. I just wanted to write a rule that says "if this field has this value, that's bad." I didn't want to learn Rego or write a Go plugin.

I wanted one tool. One command. One score. One CI step. And rules that are just YAML.

So I built it

StackGuard is a single Go binary that scans your infrastructure config, Dockerfiles, Compose files, Kubernetes manifests, Helm charts, Terraform, GitHub Actions, Jenkinsfiles, Ansible playbooks, and nginx configs against a set of rules and gives you a production-readiness score out of 100.

$ stackguard scan . examples/dockerfile/Dockerfile ✗ A stage pins only a channel tag, with no digest DOCK_IMG_002 · line 1 ⚠ The final image sets no USER, so it runs as root DOCK_SEC_002 · line 8 Score: 81/100 1 error · 3 warnings · 0 info 

That's it. No daemon. No config file required. No cloud service. It reads your files, evaluates 563 built-in rules, and prints what's wrong.

The things I actually care about

A score, not a wall of text. Every error costs 10 points, every warning costs 3, every info costs 1. Your score is 100 minus the deductions. You can gate your CI on it: --min-score 80. Now you have a number that trends over time.

Rules are YAML, not code. Here's a real rule:

id: K8S_SEC_001 title: Container runs as root severity: ERROR category: security target: kubernetes message: The container is explicitly configured to run as UID 0 query: path: containers[*] where: path: sc.runAsUser equals: 0 

No Go. No Rego. No plugin SDK. Drop a YAML file in a directory and it loads on the next scan. If your org has a convention say, every Helm chart must set podDisruptionBudget you write a rule in 10 lines and it's enforced from that point on.

Nine targets, one command. I got tired of wiring up separate tools for each config format. StackGuard detects file types automatically. Point it at a repo and it figures out what's a Dockerfile, what's a Helm chart, and what's a Terraform module. One scan . covers the whole stack.

It fits in CI in two lines.

- name: StackGuard run: | stackguard scan . \\ --fail-on error --min-score 80 \\ --format sarif --output stackguard.sarif - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: stackguard.sarif 

The SARIF output means findings show up as annotations in your PR diff. Reviewers see the issue right next to the line, not buried in a CI log.

What it actually catches

Here are real categories of things StackGuard flags across different targets:

Security:

  • S3 buckets with public ACLs
  • Containers running as root
  • Secrets hardcoded in environment variables
  • SSH open to 0.0.0.0/0 in security groups
  • No network policies in Kubernetes

Reliability:

  • No resource limits on containers
  • No health checks in Dockerfiles
  • No PodDisruptionBudget for deployments
  • RDS instances without deletion protection
  • No retry configuration in CI workflows

Operational:

  • Unpinned image tags (:latest)
  • No .dockerignore file
  • Missing worker_connections in nginx
  • No log rotation configured
  • Jenkins pipelines without timeout

It's not trying to be a vulnerability scanner. It's checking the boring stuff that causes incidents at 3 AM the missing limits, the unpinned images, the defaults nobody changed.

How it works under the hood

The pipeline is straightforward:

  1. Discovery walks the paths you give it and classifies files by type.
  2. Parsers turn each file into a normalized resource tree. Rules never touch raw text.
  3. Engine evaluates compiled rules against those resources. Queries and regexes compile once before any file is read.
  4. Reporters emit findings in your chosen formats console, JSON, SARIF, HTML, or CSV.

A file that won't parse becomes a diagnostic, not a crash. One bad YAML file doesn't hide findings from the other 200 files in your repo.

The stuff I learned building it

Start with the output. I designed the terminal output first, then built backwards. If the output isn't useful at a glance, nobody will use the tool. The score was the first thing I implemented before the engine, before the parsers.

Embedded rules need to be opinionated. Early on I shipped with very conservative rules. Nobody complained, but nobody used it either. The tool became useful when I made the rules match what actually causes incidents, not what's theoretically incorrect.

YAML rules were the right call. I almost built a Go plugin system. I'm glad I didn't. The people who write infrastructure config are the same people who should write the rules for it. They know YAML. They don't necessarily know Go.

Scoring is surprisingly motivating. Teams that adopt StackGuard start treating the score like a game. "We were at 72 last sprint, we're at 84 now." That never happened with a flat list of warnings.

Install and try it

# Homebrew brew install nexutils/tap/stackguard # Go go install github.com/nexutils/StackGuard/cmd/stackguard@latest # Then just stackguard scan . 

Point it at any repo that has infra config. It takes about 2 seconds for a typical project.

The project is open source on GitHub under the Apache 2.0 license. We just shipped v0.2.0 with CSV output, a WASM playground, repo metadata in reports, and Homebrew/Scoop install.

If you've been bitten by a production incident that was caused by a config file someone didn't review carefully enough, I built this for you. Give it a try and let me know what you think.