Back to Blog
Guide11 min read2026-07-05

Securing Container Workloads: A Practical Checklist

A no-nonsense, layer-by-layer checklist for hardening containerized applications in production: images, runtime, network, secrets, and supply chain, with concrete commands you can run today.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Container security is not a single control you flip on. It is a stack of small decisions made across the image, the runtime, the orchestrator, and the supply chain. Miss one layer and the others rarely save you. This is the checklist I actually use when reviewing a workload before it ships.

The mental model: four blast radii

Think in terms of *blast radius*. If an attacker compromises one thing, what else do they reach?

  • Image — what code and dependencies are baked in.
  • Runtime — what the container process can do on the host.
  • Network — what the container can talk to.
  • Secrets & identity — what the container is allowed to authenticate as.

Work outward from the smallest blast radius. Each section below is a checklist you can copy into a PR template.

1. Build a minimal, trustworthy image

The single highest-leverage move is shrinking the image. Fewer packages means fewer CVEs and fewer tools an attacker can pivot with.

  • [ ] Use a minimal base: distroless, alpine, or a -slim variant.
  • [ ] Multi-stage builds so compilers and dev dependencies never reach the final image.
  • [ ] Pin base images by digest, not floating tags.
  • [ ] Run as a non-root user.
# Multi-stage: build deps in one stage, ship only the artifact
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=build /app /app
USER nonroot
EXPOSE 3000
CMD ["server.js"]

Scan before you ship. Trivy is the fastest way to catch known-vulnerable packages:

trivy image --severity HIGH,CRITICAL --exit-code 1 myorg/app:sha-abc123

Fail the build on CRITICAL. You will not patch everything, but a --exit-code 1 gate forces an explicit decision instead of silent drift.

2. Lock down the runtime

A root process inside a container is one misconfiguration away from being root on the host. The kernel is the shared boundary.

ControlWhy it matters
Non-root runAsUserLimits damage if the process is hijacked
readOnlyRootFilesystemStops attackers writing tools/payloads to disk
Drop all Linux capabilitiesRemoves NET_RAW, SYS_ADMIN, etc. by default
allowPrivilegeEscalation: falseBlocks setuid-based privilege gain
seccomp RuntimeDefaultFilters dangerous syscalls

In Kubernetes, this is a securityContext:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

For multi-tenant or untrusted code, go further with a sandboxed runtime. gVisor intercepts syscalls in user space so the host kernel is never directly exposed. This is exactly why PandaStack runs free-tier workloads inside gVisor sandboxes on isolated nodes — defence in depth for code we did not write.

  • [ ] No privileged: true containers. Ever, unless you can name the exact reason.
  • [ ] No host namespaces (hostNetwork, hostPID, hostIPC).
  • [ ] No mounting the Docker socket into a container — this is root-on-host by proxy.

That last one deserves emphasis. Many CI/build systems mount /var/run/docker.sock to build images. That is a privilege-escalation hole. Use a rootless builder instead — PandaStack builds with rootless BuildKit inside ephemeral Kubernetes Job pods, so there is no shared Docker daemon to hijack.

3. Constrain the network

By default, containers in a cluster can usually talk to each other freely. Assume east-west traffic is hostile.

  • [ ] Default-deny NetworkPolicy, then allow only what is needed.
  • [ ] Egress restrictions so a compromised pod cannot phone home.
  • [ ] TLS for all service-to-service traffic where feasible.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes: ["Ingress", "Egress"]

Then layer allow-rules on top. A default-deny baseline turns network access into an explicit allowlist rather than an implicit free-for-all.

4. Handle secrets like they are radioactive

  • [ ] Never bake secrets into images (they persist in layer history).
  • [ ] Inject at runtime via environment or a secrets manager.
  • [ ] Rotate credentials; short-lived tokens beat long-lived keys.
  • [ ] Scope database and cloud credentials to least privilege.

Check your own image history for accidental leaks:

docker history --no-trunc myorg/app:latest | grep -i -E 'key|token|secret|password'

A managed platform helps here: when PandaStack provisions a database it auto-wires DATABASE_URL as an injected environment variable rather than something you hardcode and commit.

5. Secure the supply chain

The attacks that hurt most in recent years targeted the pipeline, not the running container.

  • [ ] Generate an SBOM for every build (syft, trivy sbom).
  • [ ] Sign images with cosign and verify signatures at deploy time.
  • [ ] Pin dependencies with lockfiles; review dependency updates.
  • [ ] Use ephemeral, isolated build environments — no shared mutable build host.
# Sign on push
cosign sign --key cosign.key myorg/app:sha-abc123

# Verify before deploy
cosign verify --key cosign.pub myorg/app:sha-abc123

6. Observe and respond

Hardening reduces the odds of compromise; observability tells you when it happens anyway.

  • [ ] Centralized, immutable logs (an attacker who can edit logs erases their tracks).
  • [ ] Runtime anomaly detection (Falco is the common open-source choice).
  • [ ] Resource limits to blunt crypto-mining and noisy-neighbor abuse.
  • [ ] Alerting on privilege escalation and unexpected egress.
resources:
  limits:
    cpu: "1"
    memory: "512Mi"
  requests:
    cpu: "250m"
    memory: "256Mi"

The compressed checklist

If you read nothing else, do these eight things:

  1. 1Minimal base image, scanned, non-root.
  2. 2readOnlyRootFilesystem + drop all capabilities.
  3. 3No privileged containers, no host namespaces, no Docker socket.
  4. 4Default-deny network policy.
  5. 5Secrets injected at runtime, never baked in.
  6. 6SBOM + signed images, verified at deploy.
  7. 7Resource limits on every workload.
  8. 8Centralized logs + runtime alerting.

Most incidents I have seen trace back to one of these being skipped "just for now."

References

  • [NIST SP 800-190: Application Container Security Guide](https://csrc.nist.gov/pubs/sp/800/190/final)
  • [Kubernetes Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
  • [OWASP Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)
  • [Trivy documentation](https://trivy.dev/latest/)
  • [Sigstore cosign](https://docs.sigstore.dev/cosign/overview/)
  • [gVisor: What is gVisor?](https://gvisor.dev/docs/)

---

If you would rather inherit hardened defaults than assemble them yourself, PandaStack runs builds in rootless BuildKit, sandboxes untrusted workloads in gVisor, and injects secrets at runtime — on a free tier you can try today at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also