Skip to main content

Command Palette

Search for a command to run...

Summary of the Book "Container Security" by Liz Rice

Updated
13 min read
Summary of the Book "Container Security" by Liz Rice
B

I love to grease knots and bolts of SDLC, nurture the underlying infra, rightly automate, monitor systems and enable the dev teams to achieve more with less.


Containers have become the default way to package, ship, and run modern applications. They make deployments repeatable, portable, and scalable. But containers are not magic security boxes.

A container is just a set of Linux processes with isolation features around them.

That is the central idea I took from Container Security by Liz Rice. The book explains what really happens under the hood when we run containers, and why understanding the Linux primitives behind containers is important for security.

This post is a refactored set of notes and takeaways from the book.


Why container security matters

Containers are often treated as lightweight virtual machines, but they are not the same thing.

A virtual machine runs its own operating system kernel. A container shares the host machine’s kernel with other containers. This makes containers lightweight and fast, but it also means that a container escape or host-level misconfiguration can have serious consequences.

Container security is not only about scanning images. It includes:

  • Securing the container image

  • Reducing privileges inside the container

  • Controlling access to the host

  • Limiting Linux capabilities

  • Isolating workloads with namespaces

  • Managing secrets safely

  • Securing registries

  • Protecting the runtime environment

  • Applying Kubernetes security controls

  • Monitoring containers after deployment

Security has to be applied across the full lifecycle: build, ship, deploy, and run.


Containers are Linux processes

One of the most important mental models is this:

A container is a process running on a host, isolated using Linux features.

The container runtime uses Linux kernel features such as:

  • Namespaces

  • Control groups

  • Capabilities

  • Seccomp

  • AppArmor or SELinux

  • Filesystem isolation

These features combine to create the feeling that the process is running in its own environment.

But because containers share the host kernel, the host remains a critical security boundary.


Namespaces provide isolation

Linux namespaces give a process its own view of system resources.

Common namespaces used by containers include:

Namespace Purpose
PID namespace Isolates process IDs
Network namespace Gives the container its own network stack
Mount namespace Provides an isolated filesystem view
UTS namespace Isolates hostname and domain name
IPC namespace Isolates inter-process communication
User namespace Maps users inside the container to different users on the host

For example, a process inside a container may think it is running as PID 1, but from the host’s perspective it is just another process.

This isolation is powerful, but it is not absolute. Misconfigured namespaces or unnecessary host access can weaken container security.


Control groups limit resource usage

Control groups, commonly called cgroups, are used to limit and account for resource usage.

They can control:

  • CPU usage

  • Memory usage

  • Disk I/O

  • Network usage

  • Process count

Without resource limits, a container can consume too many resources and affect other workloads on the same host.

For production workloads, it is important to define CPU and memory requests and limits, especially in Kubernetes.

Example:

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

This helps reduce the blast radius of a misbehaving or compromised container.


Avoid running containers as root

Running containers as root is one of the most common security mistakes.

Inside a container, the root user may look isolated, but depending on the runtime configuration, root inside the container can map to root on the host.

A better approach is to run applications as a non-root user.

Example Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

CMD ["node", "server.js"]

In Kubernetes, this can also be enforced with a security context:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  allowPrivilegeEscalation: false

Running as non-root does not solve every problem, but it significantly reduces risk.


Linux capabilities should be minimized

Traditional Linux root has many privileges. Linux capabilities split those privileges into smaller units.

For example, capabilities can allow a process to:

  • Change file ownership

  • Bind to privileged ports

  • Modify network settings

  • Load kernel modules

  • Bypass file permissions

Containers often receive more capabilities than they actually need.

A strong security practice is to drop all capabilities and only add back the ones required.

Example:

securityContext:
  capabilities:
    drop:
      - ALL

If an application truly needs a specific capability, add only that one.

securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE

The goal is to follow the principle of least privilege.


Privileged containers are dangerous

A privileged container has broad access to the host. It can access devices, modify kernel settings, and bypass many isolation mechanisms.

Privileged containers should be avoided unless absolutely required.

This is risky:

securityContext:
  privileged: true

A privileged container can become almost equivalent to host-level access. For most application workloads, there is no valid reason to run in privileged mode.

If privileged access is required for infrastructure components, it should be tightly controlled, reviewed, and isolated.


Seccomp helps reduce the kernel attack surface

Containers use the Linux kernel. Every system call made by a container reaches the host kernel.

Seccomp allows us to restrict which system calls a process can make.

A default seccomp profile blocks many dangerous or unnecessary system calls. In Kubernetes, seccomp can be enabled like this:

securityContext:
  seccompProfile:
    type: RuntimeDefault

This is a simple but valuable hardening step.

For most workloads, using the runtime default seccomp profile is better than running without one.


AppArmor and SELinux add another layer

AppArmor and SELinux are Linux security modules that provide mandatory access control.

They can restrict what a process can access, even if the process has normal Linux permissions.

For example, they can limit access to:

  • Files

  • Directories

  • Network resources

  • System capabilities

These tools are often managed at the host or platform level. In Kubernetes, AppArmor and SELinux can be used to enforce additional workload isolation.

They are not replacements for good container configuration, but they provide an important defense-in-depth layer.


Container images are part of the attack surface

A container image contains everything needed to run the application:

  • Base operating system packages

  • Language runtime

  • Application dependencies

  • Configuration files

  • Application code

If the image includes vulnerable packages, unnecessary tools, or secrets, those risks move into production.

Good image security practices include:

  • Use trusted base images

  • Prefer minimal images

  • Keep images updated

  • Remove unnecessary packages

  • Do not store secrets in images

  • Scan images for vulnerabilities

  • Pin image versions

  • Use multi-stage builds

Example multi-stage Dockerfile:

FROM golang:1.22-alpine AS builder

WORKDIR /src
COPY . .
RUN go build -o app .

FROM alpine:3.20

WORKDIR /app
COPY --from=builder /src/app .

RUN adduser -D appuser
USER appuser

CMD ["./app"]

This keeps build tools out of the final runtime image.


Do not use the latest tag in production

Using latest may look convenient, but it creates uncertainty.

This is risky:

image: nginx:latest

The image can change over time, and deployments may become unpredictable.

A better approach is to use a specific version:

image: nginx:1.27.0

An even stronger approach is to pin the image by digest:

image: nginx@sha256:<digest>

Pinning by digest ensures that the exact same image is deployed every time.


Image scanning is useful but not enough

Container image scanning helps identify known vulnerabilities in operating system packages and dependencies.

Common tools include:

  • Trivy

  • Grype

  • Clair

  • Docker Scout

  • Snyk

  • Anchore

Example using Trivy:

trivy image nginx:1.27.0

Image scanning should be part of CI/CD pipelines, but it is not a complete security solution.

Scanning tells us about known vulnerabilities. It does not always detect:

  • Runtime misbehavior

  • Stolen credentials

  • Logic bugs

  • Misconfigured permissions

  • Malicious activity after deployment

So scanning should be combined with runtime security, policy enforcement, and monitoring.


Registries must be secured

A container registry stores and distributes images. If the registry is compromised, attackers may push malicious images or tamper with existing ones.

Registry security should include:

  • Authentication

  • Authorization

  • Image signing

  • Vulnerability scanning

  • Audit logs

  • Network restrictions

  • Least-privilege access

  • Immutable tags where possible

Teams should control who can push production images and who can deploy them.


Image signing improves trust

Image signing helps verify that an image came from a trusted source and has not been modified.

Tools such as Cosign can sign and verify container images.

Example:

cosign sign <image>
cosign verify <image>

In Kubernetes, admission controllers can enforce policies that only allow signed images to run.

This helps protect the software supply chain.


Secrets should never be baked into images

Secrets must not be stored in Dockerfiles or committed into source code.

Avoid this:

ENV AWS_SECRET_ACCESS_KEY=example

Secrets can remain in image layers even if they are deleted in a later layer.

Better options include:

  • Kubernetes Secrets

  • External Secrets Operator

  • HashiCorp Vault

  • AWS Secrets Manager

  • Google Secret Manager

  • Azure Key Vault

Secrets should be injected at runtime and rotated regularly.


Kubernetes security matters

Kubernetes adds another layer to container security. Even if the container image is secure, a weak Kubernetes configuration can expose the workload.

Important Kubernetes security controls include:

  • RBAC

  • Network Policies

  • Pod Security Standards

  • Admission controllers

  • Security contexts

  • Resource limits

  • Secrets management

  • Audit logging

  • Runtime monitoring

A secure Kubernetes workload should avoid unnecessary privileges.

Example:

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  containers:
    - name: app
      image: nginx:1.27.0
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "250m"
          memory: "256Mi"
  securityContext:
    seccompProfile:
      type: RuntimeDefault

This configuration is much better than running a default container with root privileges.


Use read-only filesystems where possible

Many applications do not need to write to the root filesystem.

Using a read-only root filesystem makes it harder for attackers to modify files inside the container.

securityContext:
  readOnlyRootFilesystem: true

If the application needs temporary storage, mount a specific writable volume:

volumeMounts:
  - name: tmp
    mountPath: /tmp

volumes:
  - name: tmp
    emptyDir: {}

This limits where writes can happen.


Network policies reduce lateral movement

By default, many Kubernetes clusters allow pods to communicate freely with each other.

Network Policies allow us to restrict traffic between pods.

Example default-deny ingress policy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress

After applying a default-deny policy, specific allowed traffic can be defined.

This reduces the impact if one pod is compromised.


RBAC should follow least privilege

Kubernetes RBAC controls who can do what inside the cluster.

Avoid giving broad permissions such as cluster-admin unless absolutely necessary.

Instead, grant only the required permissions.

Example:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

Service accounts should also be scoped carefully. Applications should not have access to the Kubernetes API unless they need it.


Runtime security is essential

Security does not stop after deployment.

Runtime security focuses on detecting suspicious behavior while containers are running.

Examples of suspicious activity include:

  • Shell spawned inside a container

  • Unexpected outbound network connection

  • Sensitive file access

  • Privilege escalation attempt

  • Package manager execution in production

  • Unknown binary execution

  • Container writing to unexpected paths

Runtime security tools can detect and alert on these events.

Examples include:

  • Falco

  • Tetragon

  • Cilium

  • Sysdig Secure

  • Aqua Security

  • Prisma Cloud

Runtime monitoring is important because not every attack can be prevented during build time.


Security should shift left and run right

Container security should happen in multiple stages.

Build time

  • Use secure base images

  • Scan dependencies

  • Remove unnecessary tools

  • Avoid secrets in images

  • Sign images

Deployment time

  • Enforce policies

  • Verify image signatures

  • Restrict privileges

  • Apply resource limits

  • Use admission controllers

Runtime

  • Monitor behavior

  • Detect anomalies

  • Audit access

  • Rotate secrets

  • Patch and redeploy

Security is strongest when it is continuous.


Practical checklist

Here is a practical checklist for container security:

  • [ ] Use trusted and minimal base images

  • [ ] Avoid running containers as root

  • [ ] Drop unnecessary Linux capabilities

  • [ ] Avoid privileged containers

  • [ ] Use seccomp with RuntimeDefault

  • [ ] Enable AppArmor or SELinux where possible

  • [ ] Use read-only root filesystems

  • [ ] Do not bake secrets into images

  • [ ] Scan images in CI/CD

  • [ ] Pin image versions or digests

  • [ ] Sign and verify images

  • [ ] Secure container registries

  • [ ] Apply Kubernetes RBAC carefully

  • [ ] Use Network Policies

  • [ ] Set CPU and memory limits

  • [ ] Monitor runtime behavior

  • [ ] Keep hosts and runtimes updated

  • [ ] Audit cluster activity


Key takeaways from the book

My biggest takeaways from Container Security by Liz Rice are:

  1. Containers are not lightweight virtual machines.

  2. Containers are Linux processes using kernel isolation features.

  3. The host kernel is a critical security boundary.

  4. Running containers as root increases risk.

  5. Least privilege is one of the strongest security principles.

  6. Image scanning is useful, but it is only one part of security.

  7. Kubernetes security must be handled intentionally.

  8. Runtime monitoring is necessary for real-world environments.

  9. Supply chain security is now part of container security.

  10. Security should be applied throughout the container lifecycle.


Final thoughts

Container security is not a single tool or one-time task. It is a combination of good engineering practices, secure defaults, automation, and continuous monitoring.

Liz Rice’s book is valuable because it explains the fundamentals behind containers instead of only listing tools. Once we understand how containers work at the Linux level, the security recommendations make much more sense.

The main lesson is simple:

Do not assume containers are secure by default. Understand the layers, reduce privileges, and secure every stage from build to runtime.

For DevOps engineers, platform engineers, and Kubernetes users, this mindset is essential.