Containers, Illustrated
Watch one Node process acquire its filesystem, isolation, resource controls, and lifecycle as it becomes a container.
A container starts as an ordinary Linux process
What extra mechanisms turn node server.js into a container?
1systemd921sshd18472node server.jsSame process is scheduled by the host kernel.
Process: host PID 18472 runs node server.js
$ ps -o pid,ppid,cmd -p 18472 18472 1312 node server.js
A container is not a small VM. The runtime starts a host process with namespaces, cgroup membership, a root filesystem, and configured security restrictions.
Boundary: The exact setup belongs to the runtime and host configuration. Namespaces and cgroups are Linux mechanisms; their security properties depend on configuration.
Failure state: The process now sees the host’s corresponding namespace. Isolation is not an all-or-nothing property supplied by the image.
Namespaces change what the process can see
Why does PID 1 inside still have a different host PID?
Host view: host PID 18472
Illustrative: host PID 18472 ↔ namespace PID 1
The process can have distinct PID, mount, network, UTS, and IPC views. The host still schedules it. A namespace alters a selected view; it does not create a second kernel.
Boundary: User namespaces and their mappings are optional and vary by runtime configuration. Namespaces alone are not a complete security boundary.
Failure state: With host networking selected, the process shares the host network namespace; localhost and port binding behave differently.
A cgroup accounts for and can constrain resources
Does container isolation automatically reserve CPU and memory?
The container remains a container even if no runtime memory ceiling was configured.
Place: process joins its cgroup
Illustrative cgroup v2: memory.current < memory.max
The kernel charges work to the process’s cgroup. Limits can bound use; without a configured memory limit, a container is not magically assigned a fixed memory budget.
Boundary: Controllers and available limits depend on host cgroup configuration. An OOM event is a kernel decision, not a Docker retry policy.
Failure state: The container still exists, but this cgroup has no configured memory ceiling from the container runtime.
A source edit should invalidate only the work it touches
Why does a Dockerfile sometimes reinstall dependencies after a tiny code edit?
FROM node:22COPY package*.json ./RUN npm ciCOPY server.js ./Move COPY . . before install and a source edit can invalidate the expensive dependency step.
Base: FROM node:22 remains reusable
Illustrative Dockerfile: COPY package*.json . / RUN npm ci / COPY . .
Build cache reuses a step when its instruction and relevant inputs still match. Copying package files before source files lets a source-only edit reuse the dependency-install result.
Boundary: Modern builders can use mounts and optimizations beyond a one-instruction-one-layer sketch. The diagram shows a useful Dockerfile order, not a storage-driver implementation.
Failure state: A source edit now affects the dependency-install inputs and can trigger avoidable work.
An image is a template; a container adds runtime state
Why does a new container not keep the files from a previous one?
Removing web-a removes its container-local writes. It does not mutate the image or the other containers; an explicitly managed volume can persist.
Base: node:22 runtime files
$ docker image history demo-api:1.0 … COPY server.js /srv/server.js
Images contain ordered filesystem changes. Starting a container adds a writable layer and runtime configuration. A volume is a separately managed mount, not image content.
Boundary: “Immutable” describes image content after it is created. Registries, tags, layers, and storage drivers have their own implementation details.
Failure state: Container-local writable data is removed with the container. Data that must outlive it belongs in an explicit volume or external store.
docker run creates, configures, then starts
What does the runtime do between a command and a listening service?
docker run -p 8080:3000 demo-api:1.0Resolve imageuse local image or pull manifest and layers
Createrecord config and writable layer
Networkattach interface and assign address
Startexec node server.js as PID 1
Publishoptional host port → container port rule
Resolve image: use local image or pull manifest and layers
$ docker run -p 8080:3000 demo-api:1.0 Server listening on 3000
Docker resolves an image, creates a container, supplies a writable layer and networking, then starts the configured process. The container remains running only while its main process runs.
Boundary: Docker’s daemon and default bridge are common examples, not the universal container architecture. Other runtimes expose the OCI lifecycle differently.
Failure state: When the main process exits, the container stops. A background child process does not automatically keep the intended workload healthy.
Published traffic still crosses several boundaries
How does a client on host port 8080 reach node server.js on port 3000?
Client: connects to host :8080
Illustrative: host :8080 → container :3000
A published port creates a host-side forwarding path into the container network namespace. In a common Linux bridge setup, traffic crosses host rules and a veth attachment before reaching the process listener.
Boundary: Bridge, veth, NAT, and rootless forwarding details depend on the runtime/network driver; the path is a common Linux example.
Failure state: If the app listens only on 127.0.0.1 inside its namespace, the published path to its container address may fail.
Resource limits and lifecycle signals are real boundaries
What happens when the process needs more memory or receives a stop request?
A running container is a running process lifecycle. Graceful exit depends on the application handling the configured stop signal and its children.
Limit: memory.max constrains cgroup use
$ docker stop demo-api # runtime requests graceful termination
A cgroup can account for and limit resources. Container PID 1 receives lifecycle signals and must handle them appropriately; the runtime can later escalate if the process does not exit.
Boundary: Exact OOM selection, signal handling, and runtime stop behavior depend on kernel, runtime, and configured timeout.
Failure state: The process ignores or mishandles the graceful signal. After the configured grace period, the runtime may force termination.
Isolation and security controls address different risks
What can a compromised process still do if the container is poorly configured?
Namespace: separate selected views
Illustrative controls: cap-drop, read-only rootfs, restricted mounts
Namespaces limit what the process sees; capabilities, seccomp, Linux security modules, user mappings, and mount policy reduce what it may do. Host mounts and privileged mode can reopen dangerous paths.
Boundary: No single switch makes a container fully secure. The effective boundary depends on kernel, runtime, user privileges, filesystem mounts, and workload needs.
Failure state: A broad writable host mount gives the process access outside its intended filesystem boundary.
An image digest travels from build to deployment
What gets pushed and pulled when this app moves to another host?
Build: produce manifest, config, and layers
Illustrative image reference: demo-api:1.0@sha256:4ad7…91c2
The builder emits image metadata and content-addressed layers. A tag is a human-friendly pointer that can move; the digest identifies specific content. The runtime pulls the relevant manifest and layers before creating a container.
Boundary: Multi-platform indexes can point to platform-specific manifests. A pull selects content for the target platform rather than downloading every platform image.
Failure state: If a tag is repointed, two pulls at different times can resolve different content unless a digest is pinned.
Find the failing boundary before rebuilding the image
Why does the container show “running” while callers still fail?
Process: is PID 1 still running?
Illustrative checks: docker ps / docker logs / ss -ltn / cgroup events
A running PID 1 proves that a process exists. It does not prove that the app listens on the right address, that the port is published, or that the container has permission and memory to answer.
Boundary: Example commands and output vary by runtime and host network driver. Treat this as a diagnostic order, not a universal CLI transcript.
Failure state: The process is healthy but listens only inside its own loopback; traffic forwarded to the container address cannot reach that listener.
Containers and VMs isolate at different layers
Where does the kernel boundary sit in each model?
The layers are often combined; this comparison shows kernel ownership, not a blanket security rating.
Container: app process shares host kernel
Model comparison: host kernel versus guest kernel
A Linux container runs host-kernel-scheduled processes with isolated views. A VM runs a guest kernel over virtualized hardware. They are often stacked together in cloud environments.
Boundary: Strong isolation depends on implementation and configuration. This comparison is architectural, not a blanket security ranking.
Failure state: Broad host privileges can weaken the intended container boundary; the presence of a container label alone is not a security guarantee.