Cloud Security

Container Security Best Practices: Securing Docker and Kubernetes

Learn how to secure containerized applications from image to runtime. This guide covers Docker hardening, Kubernetes security, and container vulnerability management.

By InventiveHQ Team

Container security means enforcing least privilege at every stage of the lifecycle: scan and minimize images at build, sign and lock down the registry at ship, and constrain the runtime with non-root users, dropped capabilities, network policies, and a restricted Pod Security Standard at run. The failures that cause real incidents are boringly consistent — a base image with known CVEs, a container running as root, a wide-open pod network, or a mounted Docker socket — and every one of them is preventable with a control that costs nothing but discipline.

That's the summary an AI Overview will give you. What it can't show you is where the risk actually concentrates, or let you check your own Kubernetes manifest against those controls. Below is a ranked map of container attack surfaces (highest-leverage fixes first), a live manifest validator you can paste your YAML into, an animated view of the build → ship → run pipeline, and copy-ready hardening snippets for Docker and Kubernetes.


Where container risk actually concentrates

Not every control is equal. This table ranks the container attack surface by how often it shows up in real incidents versus how cheap the fix is — work top-down.

Attack surfaceReal-world likelihoodBlast radiusFix effortDo this
Running as rootVery high (default)Host takeover on escapeTrivialUSER in Dockerfile + runAsNonRoot: true
Vulnerable base imageVery highRCE via known CVELowMinimal/distroless image + CI scan gate
Mounted Docker socketMediumFull host rootTrivialNever mount docker.sock; use K8s API
No network policyVery high (default)Lateral movement across clusterLowDefault-deny + explicit allow rules
Privileged / extra capabilitiesMediumKernel-level escapeTrivialdrop: [ALL], allowPrivilegeEscalation: false
Secrets in env/ConfigMapHighCredential theftMediumExternal secrets operator / mounted volumes
Unsigned imagesMediumSupply-chain injectionMediumCosign signing + admission policy
Over-broad RBACHighCluster-wide privilegeMediumLeast-privilege Roles, no wildcard verbs
:latest mutable tagsHighUnpredictable / rollback-proof deploysTrivialPin version, then pin digest
No runtime detectionHighSlow breach discoveryHighFalco / cloud runtime protection

Which should you fix first? Start with the trivial-effort, high-likelihood rows — non-root, dropped capabilities, no :latest, no Docker socket. They cost minutes and remove the escape paths attackers rely on. Then layer image scanning and network policies. Runtime detection is valuable but is a detective control, not a preventive one — it belongs after the cheap preventive wins are in place.

Check your own manifest

Paste a Kubernetes Pod, Deployment, or DaemonSet manifest into the validator below. It checks against CIS Kubernetes Benchmark rules and flags exactly the issues in the table above — privileged containers, missing resource limits, root users, and permissive security contexts — before they reach your cluster.

Loading interactive tool...

The lifecycle: build, ship, run

Container security is a chain. A perfectly hardened runtime cannot save you from a backdoored image, and a signed image means nothing if it runs privileged. The three stages below each own a distinct set of controls — and each stage should reject work that fails the previous one's guarantees.

The build, ship, run container security pipeline Three sequential stages — Build hardens the image, Ship secures the registry, Run constrains the runtime — with a token flowing left to right and each stage gating the next. BUILD Secure the image Minimal base image Scan for CVEs (gate CI) Non-root USER Pin version + digest SHIP Secure the registry Private registry Cosign signatures Push/pull access control Verify at admission RUN Secure the runtime Restricted PSS Drop ALL capabilities Network policies Runtime detection Each stage gates the next — fail the scan, never ship; fail verification, never run

Why Container Security Is Different

Containers create unique security challenges:

  • Ephemeral workloads - Traditional security tools expect persistent servers
  • Shared kernel - Container isolation is weaker than VM isolation
  • Image sprawl - Thousands of images mean thousands of potential vulnerabilities
  • Complex orchestration - Kubernetes adds RBAC, networking, and secrets management
  • Fast deployment cycles - Security can't slow down CI/CD pipelines

The goal: secure containers without sacrificing the speed and agility they provide.


Build: Secure Your Images

Security starts at the build stage. Vulnerabilities baked into images deploy to production.

Use Minimal Base Images

Smaller images have fewer vulnerabilities:

Base ImageSizePackages
Ubuntu~77 MB~100
Alpine~5 MB~15
Distroless~2 MBRuntime only
Scratch0Nothing

Dockerfile example with distroless:

# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server

# Production stage - distroless
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]

Scan Images for Vulnerabilities

Integrate scanning into your CI/CD pipeline:

GitHub Actions with Trivy:

- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myapp:${{ github.sha }}'
    format: 'sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Fail build on critical vulnerabilities

Popular scanning tools:

  • Trivy - Fast, comprehensive, open source
  • Grype - Anchore's open source scanner
  • Snyk Container - Developer-friendly with fix suggestions
  • Clair - CoreOS/Quay scanner
  • AWS ECR scanning - Native for ECR users
Advertisement

Don't Run as Root

The default container user is root. Change it:

# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Set ownership
COPY --chown=appuser:appgroup . /app

# Switch to non-root user
USER appuser

CMD ["./app"]

Pin Image Versions

Avoid :latest tags—they create unpredictable deployments:

# Bad - unpredictable
FROM node:latest

# Good - pinned version
FROM node:20.10.0-alpine3.19

# Better - pinned digest
FROM node@sha256:abc123...

Remove Unnecessary Tools

Don't include shells, package managers, or debugging tools in production images:

# Multi-stage build keeps only the binary
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o /app/server

FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

Ship: Secure Your Registry

Your container registry is a critical asset—compromise it and attackers can inject malicious images.

Use Private Registries

Don't pull base images from public registries in production:

  • AWS ECR - Integrated with IAM
  • Azure Container Registry - Integrated with Entra ID
  • Google Artifact Registry - Integrated with IAM
  • Harbor - Self-hosted with vulnerability scanning

Enable Image Signing

Verify images are from trusted sources:

Cosign (Sigstore):

# Sign an image
cosign sign --key cosign.key myregistry/myapp:v1.0

# Verify before deployment
cosign verify --key cosign.pub myregistry/myapp:v1.0

Kubernetes admission with Cosign:

apiVersion: policy.sigstore.dev/v1alpha1
kind: ClusterImagePolicy
metadata:
  name: require-signatures
spec:
  images:
    - glob: "myregistry.com/**"
  authorities:
    - keyless:
        url: https://fulcio.sigstore.dev

Implement Registry Access Controls

Limit who can push and pull images:

# AWS ECR policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPush",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/ci-cd-role"
      },
      "Action": [
        "ecr:PutImage",
        "ecr:InitiateLayerUpload"
      ]
    }
  ]
}

Run: Secure Your Runtime

Even secure images can be exploited at runtime. Apply defense-in-depth.

Kubernetes Pod Security Standards

Enforce baseline security for all pods:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Restricted policy enforces:

  • Non-root containers
  • Read-only root filesystem
  • No privilege escalation
  • Drop all capabilities
  • Restricted volume types

Apply Security Contexts

Define security settings per pod or container:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:v1.0
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          cpu: "500m"
          memory: "128Mi"

Implement Network Policies

Default Kubernetes networking allows all pod-to-pod communication. Restrict it:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

Protect the Kubernetes API Server

The API server is the control plane—protect it:

  • Enable RBAC (Role-Based Access Control)
  • Use network policies to restrict API access
  • Enable audit logging
  • Rotate service account tokens
  • Disable anonymous authentication
# RBAC example - least privilege
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
  - kind: ServiceAccount
    name: monitoring-sa
    namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Secure Secrets Management

Don't store secrets in environment variables or ConfigMaps:

# Use external secrets operators
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: database-secret
  data:
    - secretKey: password
      remoteRef:
        key: prod/database
        property: password

Enable Runtime Protection

Detect and block malicious behavior at runtime:

Falco rules example:

- rule: Shell Spawned in Container
  desc: Detect shell spawned in a container
  condition: >
    spawned_process and
    container and
    shell_procs
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
     shell=%proc.name parent=%proc.pname)
  priority: WARNING

Cloud Provider Container Security

AWS (EKS)

  • ECR image scanning - Automatic vulnerability scanning
  • GuardDuty for EKS - Runtime threat detection
  • Pod Identity - IAM roles for service accounts
  • Security groups for pods - Network segmentation

Azure (AKS)

  • Microsoft Defender for Containers - Full lifecycle protection
  • Azure Policy for AKS - Enforce pod security standards
  • Workload identity - Azure AD for pods
  • Network policies - Azure CNI or Calico

GCP (GKE)

  • Binary Authorization - Require signed images
  • Container Threat Detection - Runtime monitoring
  • Workload Identity - GCP IAM for pods
  • GKE Autopilot - Hardened by default

Security Checklist

PracticePriority
Scan images for vulnerabilitiesCritical
Run containers as non-rootCritical
Use minimal base imagesHigh
Enable Pod Security StandardsHigh
Implement network policiesHigh
Sign and verify imagesHigh
Apply security contextsHigh
Protect secretsHigh
Enable runtime protectionMedium
Enable audit loggingMedium

Frequently Asked Questions

What's the biggest container security risk?

Vulnerable base images are the most common issue. Public images often contain known CVEs that attackers actively exploit. Always scan images before deployment and use minimal base images to reduce attack surface.

Should I use Docker or Kubernetes security features?

Both. Docker security (non-root users, capabilities, seccomp) applies at the container level. Kubernetes security (RBAC, network policies, pod security) applies at the orchestration level. They're complementary, not alternatives.

How do I handle container vulnerabilities in production?

Implement a vulnerability management process: scan images in CI/CD, block critical vulnerabilities from deploying, continuously scan running containers, and have a process for emergency patching. Not every vulnerability needs immediate action—prioritize by exploitability and exposure.

Is container isolation as strong as VM isolation?

No. Containers share the host kernel, so a kernel vulnerability can affect all containers. VMs have stronger isolation through hypervisor separation. For high-security workloads, consider gVisor, Kata Containers, or running containers in VMs.

How do I secure the Docker socket?

Never expose the Docker socket to containers—it's equivalent to root access to the host. If you need container management from within containers, use Kubernetes APIs instead, or carefully restrict access with tools like Docker socket proxies.


Take Action

  1. Audit your images - Scan all production images for vulnerabilities
  2. Enable Pod Security Standards - Apply the restricted profile to namespaces
  3. Implement network policies - Start with default deny, allow explicitly
  4. Secure your registry - Enable scanning, access controls, and image signing
  5. Add runtime protection - Deploy Falco or your cloud provider's container security

For more cloud security guidance, see our comprehensive guide: 30 Cloud Security Tips for 2026.

Frequently Asked Questions

What is the biggest container security risk?

Vulnerable base images and misconfiguration are the two dominant risks. Public images frequently ship with known CVEs, and permissive defaults (running as root, no network policy, mounted Docker socket) turn a single compromised container into host or cluster access. Scan every image before deploy and enforce a restricted Pod Security Standard so misconfiguration is rejected at admission, not discovered after a breach.

Is container isolation as strong as VM isolation?

No. Containers share the host kernel, so one kernel-level vulnerability can affect every container on the node, whereas VMs are separated by a hypervisor. For high-sensitivity or multi-tenant workloads, add a sandboxed runtime such as gVisor or Kata Containers, or run containers inside dedicated VMs.

How do I secure the Docker socket?

Never mount /var/run/docker.sock into a container. Access to the Docker socket is equivalent to unrestricted root on the host, because a container can then launch a new privileged container that mounts the host filesystem. If a workload genuinely needs to manage containers, use the Kubernetes API with scoped RBAC or a socket-proxy that whitelists specific API calls.

Should I run containers as non-root?

Yes, always. Set a numeric USER in the Dockerfile and enforce runAsNonRoot with a specific runAsUser in the pod securityContext, plus allowPrivilegeEscalation:false and capabilities drop ALL. Running as root means a container escape lands the attacker as root on the node.

What is the difference between Docker security and Kubernetes security?

They are complementary layers, not alternatives. Docker-level controls (non-root user, dropped capabilities, seccomp, read-only filesystem) harden the individual container. Kubernetes-level controls (RBAC, network policies, Pod Security Standards, admission control, secrets management) harden the orchestration around it. You need both to reach defense-in-depth.

Which container image scanner should I use?

Trivy is the common default because it is fast, free, and scans images, filesystems, and IaC in one tool. Grype is a strong open-source alternative, Snyk Container adds developer-friendly fix guidance, and cloud-native scanners (AWS ECR, Microsoft Defender, GCP) work well when you are already in that ecosystem. The scanner matters less than wiring it into CI with a failing exit code on Critical/High findings.

How do I stop unsigned or untrusted images from running?

Enforce image signing at admission. Sign images in CI with Cosign (Sigstore) and deploy an admission policy — Sigstore policy-controller, Kyverno, or cloud-native Binary Authorization on GKE — that rejects any image whose signature cannot be verified against your trusted key or keyless identity.

What does a restricted Pod Security Standard actually enforce?

The restricted profile blocks running as root, requires allowPrivilegeEscalation to be false, drops all Linux capabilities (allowing only NET_BIND_SERVICE back), requires a seccomp profile, and restricts volume types and host namespace access. Apply it with the pod-security.kubernetes.io/enforce=restricted namespace label so non-compliant pods are refused at creation.

Container SecurityDocker SecurityKubernetes SecurityDevSecOpsCloud SecurityCWPP