_CORE
AI & Agentic Systems Core Information Systems Cloud & Platform Engineering Data Platform & Integration Security & Compliance QA, Testing & Observability IoT, Automation & Robotics Mobile & Digital Banking & Finance Insurance Public Administration Defense & Security Healthcare Energy & Utilities Telco & Media Manufacturing Logistics & E-commerce Retail & Loyalty
References Technologies Blog Know-how Tools
About Collaboration Careers
CS EN
Let's talk

Container Security — Best Practices for Production

08. 05. 2017 4 min read CORE SYSTEMSai
Container Security — Best Practices for Production

“Containers are isolated, so they’re secure.” That’s what we thought — until a penetration tester escalated from a container to root on the host in half an hour. Containers are not VMs, and the security model is fundamentally different.

A Container Is Not a Sandbox

A Docker container shares the kernel with the host. Namespaces and cgroups provide process, network stack, and filesystem isolation — but it’s not hardware virtualization. A kernel exploit inside a container = a kernel exploit on the host. This is the fundamental difference from VMs, and it must be accounted for.

That doesn’t mean containers are insecure. It means you need to address security at every layer — from the base image through the build pipeline to runtime configuration.

Base Image — Minimize the Attack Surface

Every package in an image is a potential vulnerability. An Ubuntu base image has hundreds of packages your application doesn’t need — and each one may have a CVE. Rule number one: use a minimal base image.

# ❌ Bad — full Ubuntu, 188 MB, hundreds of packages
FROM ubuntu:16.04

# ✅ Better — Alpine, 5 MB, minimal packages
FROM alpine:3.6

# ✅ Best — distroless, runtime only
FROM gcr.io/distroless/java:latest

Alpine Linux is a good compromise — 5 MB, musl libc, apk package manager. For maximum security, consider distroless images from Google: no shell, no package manager, no utilities. An attacker who gets into the container doesn’t even have ls.

Multi-Stage Builds — Separate Build and Runtime

Build tools (gcc, npm, maven) have no business in a production image. Multi-stage builds separate compilation from the final image.

FROM golang:1.9 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

FROM alpine:3.6
RUN adduser -D -u 1001 appuser
COPY --from=builder /app/server /server
USER appuser
EXPOSE 8080
CMD ["/server"]

The resulting image contains just the binary and Alpine. No Go toolchain, no source code, no build dependencies. Smaller image = smaller attack surface = faster deploy.

Don’t Run as Root

A surprising number of Docker images run as root. If the application doesn’t need a privileged port or access to system resources, always add a USER directive. Root inside a container can, under certain circumstances, escalate to root on the host.

In Kubernetes, use securityContext at the pod level:

securityContext:
  runAsNonRoot: true
  runAsUser: 1001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]

readOnlyRootFilesystem prevents writes to the container’s filesystem — an attacker cannot persist malware. drop ALL capabilities removes Linux capabilities the application doesn’t need.

Image Scanning — Find CVEs Before the Attacker Does

Every image should pass through a vulnerability scanner before being deployed to production. Tools like Clair, Trivy, or Anchore analyze image layers and compare packages against CVE databases.

We scan at two points: in the CI pipeline (build time) and in the registry (continuous scan). Build-time scanning blocks deploys with critical CVEs. Continuous scanning catches newly discovered vulnerabilities in images already running in production.

The key is setting a policy: what’s acceptable and what isn’t. Zero tolerance for critical CVEs in the base image. Medium severity with a remediation plan within 30 days. Low severity tracked but not blocking deploy.

Supply Chain — Trust, but Verify

docker pull node:latest — do you know exactly what you’re downloading? Who created that image? Was it modified? Docker Content Trust (Notary) enables image signing. Enable it and accept only signed images.

Use specific tags, not :latest. Better yet: pin to a digest. A tag can be overwritten; a digest is an immutable hash. And run your own registry (Harbor, GitLab Registry) instead of pulling directly from Docker Hub.

Runtime Protection

Build-time security isn’t enough. At runtime, you need anomaly detection. Seccomp profiles restrict system calls — a container that normally makes HTTP requests has no reason to call ptrace or mount. AppArmor or SELinux profiles add another layer of mandatory access control.

Network policies in Kubernetes act as a firewall between pods. Default deny — no pod communicates with another unless you explicitly allow it. A payment microservice has no reason to talk to the CMS.

Secrets Management

Never bake secrets into an image. Not as an environment variable in the Dockerfile, not as a file in the build context. Secrets belong in Kubernetes Secrets (ideally backed by an external store like HashiCorp Vault), mounted as volumes, rotated automatically.

Scan image layer history — docker history shows all layers including deleted files. A secret added and then deleted in the next RUN command is still in the previous layer.

Security as a Continuous Process

Container security isn’t a one-time audit. It’s a continuous process integrated into the entire lifecycle — from the Dockerfile through the CI pipeline to runtime monitoring. Start with the basics: non-root, minimal images, CI scanning. Gradually add seccomp, network policies, runtime detection. Each layer makes the attacker’s job harder.

dockersecuritycontainersdevsecops
Share:

CORE SYSTEMS

Stavíme core systémy a AI agenty, které drží provoz. 15 let zkušeností s enterprise IT.

Need help with implementation?

Our experts can help with design, implementation, and operations. From architecture to production.

Contact us