thelinuxvault guide

How to Set Up Linux Containers with Security in Mind

Linux containers have revolutionized software development and deployment, offering lightweight, portable, and efficient environments for running applications. Unlike virtual machines (VMs), containers share the host’s kernel, making them faster and more resource-efficient. However, this shared architecture also introduces unique security challenges: a compromised container could potentially escape to the host or other containers if not properly secured. In this guide, we’ll walk through **step-by-step best practices** to set up Linux containers with security as a core principle. From choosing the right tools to hardening the host, securing images, and monitoring for threats, we’ll cover everything you need to deploy containers safely in production.

Table of Contents

  1. Understanding Container Security Basics
  2. Choosing the Right Container Tooling
  3. Hardening the Host Environment
  4. Building Secure Container Images
  5. Runtime Security Best Practices
  6. Network Security for Containers
  7. Monitoring and Auditing
  8. Compliance and Governance
  9. Advanced Security: Beyond the Basics
  10. Conclusion
  11. References

1. Understanding Container Security Basics

Before diving into setup, it’s critical to grasp why container security differs from traditional VMs and where risks lie:

Containers vs. VMs: Key Differences

  • VMs run full operating systems with their own kernels, isolated by a hypervisor.
  • Containers share the host’s kernel and OS resources, using namespaces (isolate PID, network, mounts) and control groups (cgroups, limit CPU/memory) for isolation.

This shared kernel means containers have a smaller attack surface than VMs but are more dependent on the host’s security.

Common Container Security Risks

  • Image vulnerabilities: Malicious or outdated base images (e.g., with unpatched CVEs).
  • Misconfigurations: Overly permissive settings (e.g., privileged containers, unrestricted network access).
  • Runtime escapes: Exploiting kernel vulnerabilities to break out of container isolation.
  • Insecure defaults: Tools like Docker historically used rootful setups, increasing risk.

2. Choosing the Right Container Tooling

Not all container tools are created equal. Prioritize tools with built-in security features:

Docker

  • Pros: Mature ecosystem, wide adoption, rich documentation.
  • Cons: Traditionally uses a rootful daemon (though rootless Docker is now available).
  • Security Features: Supports seccomp, AppArmor, and user namespaces.

Podman

  • Pros: Daemonless (no persistent root process), rootless by default, OCI-compliant (drop-in Docker replacement).
  • Cons: Smaller ecosystem than Docker.
  • Security Features: Native rootless support, user namespaces, and built-in secret management.

LXC/LXD

  • Pros: System-level containers (closer to VMs), strong isolation via LXD daemon.
  • Cons: Less focused on application containers than Docker/Podman.
  • Security Features: AppArmor, SELinux, and resource limits via cgroups.

Recommendation: For most users, Podman is preferred for security due to its rootless, daemonless design. For Kubernetes environments, stick with Docker (via containerd) or CRI-O.

3. Hardening the Host Environment

The host OS is the foundation of container security. A compromised host puts all containers at risk.

Step 1: Use a Minimal, Secure Host OS

  • Choose lightweight distributions like Alpine Linux, Ubuntu Server Minimal, or Fedora CoreOS (for Kubernetes).
  • Avoid GUI tools or unnecessary packages to reduce attack surface.

Step 2: Keep the Host Kernel and Software Updated

  • Regularly patch the kernel (critical for mitigating container escape vulnerabilities like CVE-2022-0185).
  • Use tools like unattended-upgrades (Debian/Ubuntu) or dnf-automatic (Fedora/RHEL) for automated updates.

Step 3: Limit Host Privileges

  • Never run containers as root on the host. Use rootless containers (Podman or rootless Docker) instead.
  • Restrict host access: Disable password SSH (use SSH keys), limit sudo access, and use a firewall (e.g., ufw or firewalld).

Step 4: Enable Kernel Security Features

  • AppArmor/SELinux: Mandatory access control (MAC) systems to restrict container behavior.
    • For AppArmor (Debian/Ubuntu): Enable with aa-enforce /etc/apparmor.d/docker (Docker) or podman-default (Podman).
    • For SELinux (RHEL/CentOS): Set SELINUX=enforcing in /etc/selinux/config.
  • seccomp: Filter system calls (syscalls) to block dangerous operations (e.g., mount, ptrace).
  • cgroups v2: Enable for improved resource management and isolation (required for rootless Podman).

Step 5: Secure Host Filesystems

  • Mount /tmp and /var/lib/containers with noexec, nosuid, and nodev to prevent execution of malicious files.
  • Example /etc/fstab entry:
    tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0  

4. Building Secure Container Images

Most container breaches start with insecure images. Follow these practices to build hardened images:

Use Minimal Base Images

  • Avoid full OS images (e.g., ubuntu:latest). Instead, use:
    • Alpine Linux: Small (5MB), security-focused.
    • Distroless: No shell or package manager (e.g., gcr.io/distroless/python3).
    • Wolfi: Chainguard’s minimal, SBOM-enabled distro.

Multi-Stage Builds

Reduce image size and attack surface by discarding build tools in the final image. Example (Dockerfile):

# Build stage  
FROM golang:1.21-alpine AS builder  
WORKDIR /app  
COPY . .  
RUN go build -o myapp  

# Final stage (distroless)  
FROM gcr.io/distroless/static-debian12  
COPY --from=builder /app/myapp /  
USER nonroot:nonroot  # Run as non-root user  
ENTRYPOINT ["/myapp"]  

Scan Images for Vulnerabilities

Use tools to detect CVEs in images before deployment:

  • Trivy: Open-source scanner (supports Docker, Podman, and OCI images).
    trivy image myapp:latest  
  • Clair: Integrates with CI/CD pipelines (used by Quay, GitLab).
  • Snyk: Free tier for open-source projects, with actionable fixes.

Sign and Verify Images

Prevent tampering by signing images and verifying them before deployment:

  • cosign (Sigstore): Sign images with private keys or OIDC (e.g., GitHub/GitLab accounts).
    cosign sign --key mykey.pem myregistry.com/myapp:latest  
    cosign verify --key mykey.pem myregistry.com/myapp:latest  
  • Docker Content Trust (DCT): Built into Docker CLI (uses Notary).

Avoid Sensitive Data in Images

  • Never include secrets (API keys, passwords) in images. Use:
    • Podman Secrets or Docker Secrets (runtime-only).
    • Environment variables (with caution—prefer secrets managers like HashiCorp Vault).

5. Runtime Security Best Practices

Even secure images can be compromised at runtime. Harden how containers run:

Run Rootless Containers

Rootless containers map container UIDs to unprivileged host UIDs, preventing host kernel access.

  • Podman: Enabled by default.
  • Docker: Enable rootless mode with dockerd-rootless-setuptool.sh install.

Use Non-Root Users Inside Containers

Define a non-root user in your Dockerfile/Podmanfile:

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

Read-Only Filesystems

Make the container filesystem read-only to block malware from writing files:

podman run --read-only -v /tmp:/tmp:rw myapp:latest  # Allow write access only to /tmp  

Limit Capabilities

Containers inherit Linux capabilities (e.g., CAP_NET_RAW for packet sniffing). Drop all capabilities except essential ones:

podman run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp:latest  # Only allow binding to ports <1024  

Seccomp Profiles

Filter syscalls to block dangerous operations (e.g., mount, ptrace). Use custom profiles:

podman run --security-opt seccomp=my-seccomp-profile.json myapp:latest  

Example my-seccomp-profile.json (blocks execve):

{  
  "defaultAction": "SCMP_ACT_ALLOW",  
  "syscalls": [  
    { "name": "execve", "action": "SCMP_ACT_DENY" }  
  ]  
}  

Avoid Privileged Containers

Never use --privileged (gives full host capabilities). If you need host access, use granular flags:

# Bad: --privileged  
# Good: Limit to specific devices  
podman run --device /dev/sda1 --cap-add=SYS_ADMIN myapp:latest  

6. Network Security for Containers

Containers often communicate with each other and external services—secure these connections.

Use Isolated Bridge Networks

Avoid the default bridge network (shared by all containers). Create dedicated networks:

podman network create myapp-net  
podman run --network myapp-net --name app1 myapp:latest  
podman run --network myapp-net --name app2 mydb:latest  # App1 can reach App2 via hostname  

Limit Exposed Ports

Only expose necessary ports, and bind to specific interfaces (avoid 0.0.0.0):

podman run -p 127.0.0.1:8080:8080 myapp:latest  # Only accessible locally  

Encrypt Container Traffic

  • Internal Traffic: Use TLS for container-to-container communication (e.g., mutual TLS with Istio in Kubernetes).
  • External Traffic: Terminate TLS at the load balancer (e.g., Nginx, Traefik) or use --tls-verify for registry pulls.

Network Policies (Kubernetes)

For Kubernetes clusters, use network policies to restrict pod-to-pod communication:

# Block all inbound traffic except from frontend pods  
apiVersion: networking.k8s.io/v1  
kind: NetworkPolicy  
metadata:  
  name: default-deny  
spec:  
  podSelector: {}  
  policyTypes:  
  - Ingress  
  ingress:  
  - from:  
    - podSelector:  
        matchLabels:  
          app: frontend  

7. Monitoring and Auditing

Detect breaches early with proactive monitoring:

Runtime Threat Detection

  • Falco: Open-source runtime security tool that monitors syscalls and alerts on suspicious behavior (e.g., writing to /etc/passwd).
    # Falco rule to detect file writes to /etc  
    - rule: Write to etc  
      desc: Detect writes to /etc  
      condition: open_write and fd.directory == "/etc"  
      output: "File write to /etc (user=%user.name, process=%proc.name, file=%fd.name)"  
      priority: WARNING  

Audit Host and Container Logs

  • auditd: Monitor host kernel events (e.g., container starts/stops) with rules:
    auditctl -a exit,always -F arch=b64 -S execve -F euid=0  # Log root executions  
  • Container Logs: Collect logs with journald (systemd) or tools like Fluentd/Logstash (ELK Stack).

Metrics and Anomaly Detection

  • Prometheus + Grafana: Monitor container CPU, memory, and network usage for anomalies.
  • cAdvisor: Exposes container metrics for Prometheus.

8. Compliance and Governance

For enterprise environments, align with regulatory standards:

Compliance Standards

  • PCI-DSS: Requires network segmentation and encryption for cardholder data.
  • HIPAA: Mandates access controls and audit logs for healthcare data.
  • GDPR: Requires data minimization and breach notification.

Policy Engines

  • OPA Gatekeeper (Kubernetes): Enforce rules like “no privileged containers” or “images must be signed.”
  • Kyverno: Kubernetes-native policy engine with YAML-based rules.

Regular Audits

  • Conduct quarterly penetration testing of containers and hosts.
  • Use tools like kube-bench (CIS Benchmark for Kubernetes) or lynis (Linux security audit).

9. Advanced Security: Beyond the Basics

For high-security environments, use these additional layers:

User Namespaces

Isolate container UIDs from the host using user namespaces (enabled by default in Podman).

gVisor or Kata Containers

  • gVisor: Replaces the host kernel with a user-space kernel (sandboxed syscalls).
  • Kata Containers: Lightweight VMs that run containers (stronger isolation than standard containers).

seccomp-BPF

Write custom seccomp profiles to block specific syscalls (e.g., unshare for preventing namespace escapes).

Conclusion

Container security is a continuous process, not a one-time setup. By hardening the host, building secure images, enforcing runtime restrictions, and monitoring for threats, you can significantly reduce risk. Start with the basics—rootless containers, minimal images, and Falco—and layer in advanced tools as needed.

References