The Runtime Theory
mediumApplicationDSA#containers#migration#deployment#docker

How would you migrate a monolith to containers?

Tests whether you know containerization is a deployment change, not an architecture change — deterministic builds, statelessness, health checks, and canary cutover without the microservices trap.

The Runtime Theory Team2 min readasked at netflix · spotify · google · microsoft

This question is testing whether you know that containerization is a packaging and deployment change, not an architecture change — and whether you understand what the container actually adds and what it doesn't. A strong answer starts with the sequence, names the isolation reality, and resists the "everything into microservices" trap.

The mental model: containers change deployment, not design. A container is a process with kernel namespaces, cgroups, and an overlay filesystem. It shares the host kernel, so it gives you process-level isolation, not VM isolation: namespaces change what the process sees; cgroups change what it gets — cpu.max is a hard cap, memory.max ends in the OOM killer, which is the same mechanism that kills processes on the host. Migrating a monolith to containers doesn't break it into services; it gives the existing monolith a reproducible build, a pinned runtime, and an immutable artifact.

The sequence. First, make the build deterministic: a Dockerfile with layer order that caches dependencies — copy package manifests before source, multi-stage builds so the compiler never ships. Base image choice matters: node:20 is ~360 MB compressed vs. ~50 MB alpine, and musl vs. glibc changes ABI behavior for native modules. Second, make the app stateless: cgroups enforce memory limits, and container-local files die with the container — move logs, sessions, and uploads to stdout or external storage. Third, add health checks: a readiness probe that checks real dependencies, not "process is alive." Fourth, harden the runtime: run as non-root, drop capabilities with --cap-drop ALL and add back only what's needed, keep the default seccomp profile — containers are process isolation, not security isolation, and a --privileged container is effectively root on the host.

The cutover is the hard part. Don't flip all at once. Run the containerized monolith alongside the old deployment, split traffic at the load balancer, and canary by percentage. The first goal is parity: same image, same config, same traffic, identical behavior — before any restructuring.

Tradeoffs and edge cases. The stranglehold trap: teams that use the migration to decompose the monolith do two risky changes at once, packaging and architecture, and when something breaks they can't tell which one did it. Decompose later, once the container path is boring. Watch for cgroup CPU throttling: a container "using 100% CPU" that's throttled at half a core is a quota problem, not a busy problem — check cpu.stat for nr_throttled. And memory: the JVM sees host memory unless you set container-aware limits like -XX:MaxRAMPercentage. If the interviewer asks about VMs: VMs for untrusted workloads and hard isolation; containers when you trust the code and want density and fast startup.

This answer walks

Follow-ups they'll push on

  1. 01What isolation do containers actually provide, and where does it stop?
  2. 02Why is decomposing the monolith during the migration a two-risky-changes trap?
  3. 03What does CPU throttling look like in cgroup v2, and how do you diagnose it?

More interviews in this topic

One dispatch a week

The trace behind each question, the tradeoff that explains it, and one technical dispatch per week — no noise.

One technical dispatch per week. No noise.