Last December I wrote about container security fundamentals — slim images, dropped capabilities, network policies. That post got more traction than I expected. I got a fair amount of “we do none of this” in my inbox. So here’s where things stand eight months later, both in the ecosystem and at the fintech startup.
PodSecurityPolicy is real now
When I wrote the first post, PodSecurityPolicy existed, but almost nobody used it. That changed fast. We rolled it out at the fintech startup in Q1, and it caught issues immediately. A dev tried to deploy a container with hostNetwork: true for a quick port-forwarding hack. PSP rejected it. Before PSP, that would have gone through and sat there until someone noticed — or didn’t.
The enforcement model is rough. You define policies, bind them via RBAC, and hope the interaction between multiple policies does what you think it does. We spent a solid week debugging why certain pods wouldn’t schedule. But the alternative — trusting every deploy not to escalate privileges — is worse.
We moved past Alpine
In the 2017 post I showed an Alpine-based final image. We’ve since moved most Go services to scratch or distroless. No shell. No package manager. No /etc/passwd. An attacker who gets code execution inside one of these containers gets almost nothing. They can’t spawn a shell because there isn’t one.
The tradeoff is debugging. You can’t kubectl exec into a scratch container and poke around. We handle this with ephemeral debug containers and better structured logging. Worth it.
Image signing went from nice-to-have to required
Docker Content Trust plus Notary. We gate all production pulls on signature verification now. This came out of a near-miss: a CI pipeline pulled from an internal registry without verifying the image hadn’t been tampered with. Nobody tampered with it. But nobody would have known if they had.
export DOCKER_CONTENT_TRUST=1
docker trust sign registry.internal/api:2.4.1
Simple. We should have done it a year ago.
Network policies still matter more than service meshes
Istio is getting a lot of attention. We’re still not running it. NetworkPolicy with Calico does what we need: default-deny, explicit allows, and it doesn’t add another control plane to babysit. When Istio matures, maybe. For now, we keep it simple.
The thing nobody talks about
Secrets. We switched from Kubernetes Secrets to Vault-backed dynamic credentials for database access. Short-lived tokens that rotate automatically. The ops overhead was real — Vault isn’t a simple system — but the security posture improvement was immediate. No more long-lived database passwords sitting in etcd.
Where we are
Eight months of tightening. Every service runs non-root, read-only filesystem, all capabilities dropped. PSP enforces it cluster-wide. Images are signed. Network policies are default-deny. Secrets come from Vault.
None of it’s glamorous. Most of it’s YAML and policy files. But the gap between “we know what to do” and “we actually do it everywhere” is where breaches live. We’re closing that gap, one deploy at a time.