Containerization
- Containerization
Isolation built from kernel features, not a VM
Linux containers are isolated environments built from kernel features — primarily namespaces and cgroups — rather than full virtual machines. This page covers the Linux primitives that make containers possible, container runtimes, and Docker.
⚠️ The security takeaway up front: a container shares the host kernel. The isolation is namespaces (what a process can see) plus cgroups (what it can use) — not a hardware boundary. A kernel exploit or a misconfigured container (
--privileged, host network, root user, the Docker socket mounted in) can cross it. Treat container isolation as a strong default, not an impenetrable wall.
ulimits
ulimits (user limits) constrain the resources available to a process and its children. They protect the system from runaway processes or resource exhaustion.
Types of limits
Each resource has a soft limit (current enforced limit, can be raised by the user up to the hard limit) and a hard limit (ceiling; only root can raise it).
ulimit — shell built-in
ulimit -a # show all current limits
ulimit -aH # show all hard limits
ulimit -aS # show all soft limits
# Common limits
ulimit -n # max open file descriptors (default often 1024)
ulimit -u # max user processes
ulimit -m # max memory (KB)
ulimit -s # max stack size (KB)
ulimit -t # max CPU time (seconds)
ulimit -f # max file size (KB)
ulimit -c # max core dump size (0 = no core dumps)
# Set a limit (for current shell and children)
ulimit -n 65536 # increase open file descriptors to 64K
ulimit -c unlimited # allow unlimited core dumps
Persistent limits — /etc/security/limits.conf
# /etc/security/limits.conf format:
# domain type item value
* soft nofile 65536
* hard nofile 131072
alice soft nproc 2048
alice hard nproc 4096
@devs soft memlock unlimited
Files in /etc/security/limits.d/ take precedence over the main file. These take effect for new PAM sessions (login, SSH).
cat /etc/security/limits.conf # main limits config
ls /etc/security/limits.d/ # per-application overrides
Viewing limits for a running process
cat /proc/<PID>/limits # all limits for a specific process
# Example:
cat /proc/1/limits # limits for PID 1 (systemd)
systemd service limits
Services managed by systemd can have limits set in their unit files:
[Service]
LimitNOFILE=65536
LimitNPROC=4096
LimitMEMLOCK=infinity
Worked example — fix “too many open files” error:
# Check current limit
ulimit -n
# Raise for this session
ulimit -n 65536
# Check what a process is using
lsof -p $(pgrep nginx) | wc -l
# Set permanently in /etc/security/limits.d/nginx.conf:
# nginx soft nofile 65536
# nginx hard nofile 131072
cgroups
Control Groups (cgroups) are a Linux kernel feature that limits, measures, and isolates the resource usage (CPU, memory, disk I/O, network) of groups of processes. They are the foundation of containers.
cgroups v1 vs v2
Most modern distributions use cgroups v2 (unified hierarchy). cgroups v1 used separate hierarchies per controller.
# Check which version is in use
mount | grep cgroup
ls /sys/fs/cgroup/ # cgroupfs mount point
cat /proc/cgroups # v1: list of controllers and their state
cgroups v2 structure
In cgroups v2, the hierarchy is a single tree under /sys/fs/cgroup/:
ls /sys/fs/cgroup/ # root cgroup
cat /sys/fs/cgroup/cgroup.controllers # available controllers
cat /sys/fs/cgroup/cgroup.subtree_control # enabled controllers
# Create a cgroup
mkdir /sys/fs/cgroup/mygroup
echo "+memory +cpu" > /sys/fs/cgroup/cgroup.subtree_control
# Move a process into a cgroup
echo <PID> > /sys/fs/cgroup/mygroup/cgroup.procs
# Set memory limit (200 MB)
echo $((200 * 1024 * 1024)) > /sys/fs/cgroup/mygroup/memory.max
# Set CPU weight (relative scheduling weight, default 100)
echo 50 > /sys/fs/cgroup/mygroup/cpu.weight
# Set CPU quota: 20ms out of every 100ms (20% of one CPU)
echo "20000 100000" > /sys/fs/cgroup/mygroup/cpu.max
Viewing cgroup membership
# See which cgroup a process belongs to
cat /proc/<PID>/cgroup
# systemd assigns each service to a cgroup
systemctl status nginx # shows cgroup path
systemd-cgtop # live resource usage by cgroup
systemd and cgroups
systemd uses cgroups to manage service isolation. Each service runs in its own cgroup slice:
systemd-cgls # cgroup tree
systemd-cgtop # live resource view
systemctl set-property nginx.service MemoryMax=512M # set limit
systemctl set-property nginx.service CPUQuota=50% # CPU quota
Container Runtime
A container runtime creates and manages containers. It sets up namespaces (isolation), cgroups (resource limits), and mounts to provide a contained environment.
Linux namespaces
Namespaces are the isolation mechanism. Each namespace type isolates a different resource:
| Namespace | Flag | Isolates |
|---|---|---|
mnt |
CLONE_NEWNS |
Filesystem mount points |
pid |
CLONE_NEWPID |
Process IDs (PID 1 inside container) |
net |
CLONE_NEWNET |
Network interfaces, routes, ports |
uts |
CLONE_NEWUTS |
Hostname and domain name |
ipc |
CLONE_NEWIPC |
SysV IPC, POSIX message queues |
user |
CLONE_NEWUSER |
User and group IDs |
cgroup |
CLONE_NEWCGROUP |
cgroup root |
time |
CLONE_NEWTIME |
Boot and monotonic clocks |
# View namespaces for a process
ls -la /proc/<PID>/ns/
# Run a command in a new network namespace
ip netns add testns
ip netns exec testns bash
# Inside: own lo interface, no external network by default
ip netns del testns
# Inspect namespaces with lsns
lsns # list all namespaces
lsns -t net # network namespaces only
OCI and container runtimes
The Open Container Initiative (OCI) defines standards for container images and runtimes.
| Runtime | Description |
|---|---|
runc |
Reference OCI runtime; used by Docker and containerd |
containerd |
High-level runtime; manages images, snapshots, and networking |
crun |
Lightweight OCI runtime written in C |
kata-runtime |
Lightweight VMs instead of pure Linux namespaces |
podman |
Daemonless container engine; Docker-compatible CLI |
# runc directly
runc list # list running containers
runc state <container-id> # inspect container state
# containerd
ctr images list # list images
ctr containers list # list containers
nerdctl run -it ubuntu bash # Docker-compatible CLI for containerd
Docker
Docker is the most widely used container platform. It packages the container runtime (containerd + runc), image management, networking, and volumes into an integrated system.
Docker architecture
- Docker daemon (
dockerd) — background service managing containers - Docker CLI (
docker) — client that communicates with the daemon - Docker Hub — public registry for images
- Image — read-only template (layered filesystem)
- Container — running instance of an image
Images
docker pull ubuntu:22.04 # download an image
docker pull nginx:latest # latest nginx
docker images # list local images
docker image ls # same
docker rmi nginx # remove an image
docker image prune # remove unused images
docker inspect ubuntu:22.04 # detailed image metadata
docker history nginx # show image layers
docker search nginx # search Docker Hub
Running containers
# Basic run
docker run ubuntu echo "hello" # run and exit
docker run -it ubuntu bash # interactive terminal
docker run -d nginx # detached (background)
docker run --name webserver nginx # named container
docker run --rm ubuntu echo "temp" # auto-remove when done
# Resource limits
docker run -m 512m nginx # limit to 512 MB memory
docker run --cpus="1.5" nginx # limit to 1.5 CPUs
# Port mapping
docker run -p 8080:80 nginx # host:8080 → container:80
docker run -p 127.0.0.1:8080:80 nginx # bind to localhost only
# Volume mounts
docker run -v /host/path:/container/path nginx # bind mount
docker run -v myvolume:/data nginx # named volume
# Environment variables
docker run -e MYSQL_ROOT_PASSWORD=secret mysql
docker run --env-file .env myapp
# Network
docker run --network mynet nginx # connect to named network
docker run --network host nginx # use host network (no isolation)
Managing running containers
docker ps # list running containers
docker ps -a # all containers (including stopped)
docker stop webserver # graceful stop (SIGTERM)
docker kill webserver # immediate stop (SIGKILL)
docker rm webserver # remove stopped container
docker rm -f webserver # force remove running container
docker restart webserver # restart
docker exec -it webserver bash # shell in running container
docker exec webserver nginx -t # run command in container
docker logs webserver # view container logs
docker logs -f webserver # follow logs
docker logs --tail 50 webserver # last 50 lines
docker stats # live resource usage for all containers
docker top webserver # processes in a container
docker inspect webserver # detailed container metadata
Building images with Dockerfile
# Example Dockerfile
FROM ubuntu:22.04
# Install dependencies
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy application files
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY . .
# Create non-root user
RUN useradd -m appuser
USER appuser
# Expose port
EXPOSE 8000
# Default command
CMD ["python3", "app.py"]
docker build -t myapp:1.0 . # build image from Dockerfile
docker build -t myapp:1.0 -f Dockerfile.prod . # use specific Dockerfile
docker build --no-cache -t myapp . # force rebuild all layers
docker tag myapp:1.0 registry/myapp:1.0 # tag for registry
docker push registry/myapp:1.0 # push to registry
Docker networks and volumes
# Networks
docker network ls # list networks
docker network create mynet # create bridge network
docker network inspect mynet # inspect network
docker network connect mynet webserver # connect container to network
# Volumes
docker volume ls # list volumes
docker volume create mydata # create named volume
docker volume inspect mydata # inspect volume
docker volume rm mydata # remove volume
docker volume prune # remove all unused volumes
Docker Compose
Docker Compose defines multi-container applications in a compose.yaml file:
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- app
app:
build: .
environment:
- DATABASE_URL=postgresql://db/mydb
depends_on:
- db
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=mydb
- POSTGRES_PASSWORD=secret
volumes:
pgdata:
docker compose up -d # start all services in background
docker compose up --build # rebuild images before starting
docker compose down # stop and remove containers
docker compose down -v # also remove volumes
docker compose ps # list service containers
docker compose logs -f # follow all service logs
docker compose logs -f web # follow web service logs
docker compose exec app bash # shell in app container
docker compose restart web # restart a service
Cleanup
docker system prune # remove stopped containers, unused images
docker system prune -a # also remove all unused images
docker system prune -af # force, no prompt
docker system df # disk usage by Docker
Key takeaways
- A container is not a VM — it’s a process isolated by namespaces (mnt, pid, net, uts, ipc, user, cgroup, time) and constrained by cgroups (CPU, memory, I/O), all on the shared host kernel.
- cgroups v2 is the modern unified hierarchy under
/sys/fs/cgroup/; systemd places every service in its own slice, andulimit/limits.confset per-process resource ceilings. - The runtime stack is layered and standardized by the OCI:
runc/crun(low-level) undercontainerd(high-level) under Docker/nerdctl/Podman. Podman is daemonless and rootless-friendly. - Docker fundamentals: images are layered, read-only templates (
Dockerfile→docker build); containers are running instances;-pmaps ports,-vmounts data,-m/--cpusapply cgroup limits; Compose declares multi-container apps. - Harden by default: run as a non-root
USER, drop capabilities, avoid--privilegedand--network host, never mount the Docker socket into untrusted containers, and scan images for CVEs before shipping.
References
- Docker documentation. https://docs.docker.com/
- Open Container Initiative (OCI) specifications. https://opencontainers.org/
- Linux kernel — Control Group v2 documentation. https://docs.kernel.org/admin-guide/cgroup-v2.html
namespaces(7)— Linux namespaces overview. https://man7.org/linux/man-pages/man7/namespaces.7.html- NIST SP 800-190 — Application Container Security Guide. https://csrc.nist.gov/pubs/sp/800/190/final
- Docker — security best practices. https://docs.docker.com/develop/security-best-practices/
Related course pages: DevSecOps Fundamentals · Vulnerability Management · SIEM, SOC, and Threat Detection
🛠️ Maintenance note: the tagged image versions in the examples (
ubuntu:22.04,postgres:15, etc.) age out — bump them to current LTS/stable each term (Ubuntu 24.04 is current). Docker Compose v2 (docker compose,compose.yaml) has fully replaced the legacydocker-compose/version:key. NIST SP 800-190 predates cgroups v2’s ubiquity, so pair it with current Docker/Kubernetes hardening guidance.