When Docker is the wrong tool: kernel coupling, isolation needs, stateful I/O, and operational cost
I treat containerization as a tool with a cost, not a default. A container is just a Linux process with namespaces and cgroups around it, and it shares the host kernel. That one fact explains most of the cases where I'd say no. The benefits (reproducible packaging, fast startup, density, a uniform deploy artifact) have to outweigh the extra layer you now own: image builds, base-image patching, registry, runtime, networking, storage drivers and logging.
Kernel- or hardware-coupled workloads: anything needing custom kernel modules, a specific kernel version, DPDK or kernel-bypass networking, hard real-time guarantees, or heavy raw device access. The container cannot bring its own kernel, so you end up with --privileged and host mounts, which defeats the point.
Hostile multi-tenant or untrusted code: namespaces are not a security boundary equal to a hypervisor, because one kernel bug can mean escape. For running customer code I'd choose microVMs (Firecracker, Kata Containers) or a user-space kernel like gVisor, not plain Docker.
Stateful, I/O-sensitive systems without the operational maturity to run them: databases can run in containers, and many teams do it well. But if you have one host, bind mounts, no tested backup and restore, and no one who understands storage drivers and volume lifecycle, a managed database or a VM is safer. The risk is the operations, not the container itself.
Very simple deployments: a single static Go or Rust binary on a VM you already manage with systemd gets little from Docker. You add a daemon, an image pipeline and a CVE-patching burden for no real gain.
GUI, desktop and legacy Windows applications: Docker is built for headless Linux services. Windows containers work but are heavy, version-locked to the host OS, and poorly supported by most tooling.
Developer machines where file-sharing performance dominates: on Docker Desktop for macOS and Windows, containers run inside a Linux VM, and bind mounts crossing the VM boundary can make npm, Maven or Gradle workloads much slower than native.
Organizational constraints: no one owns image patching, no registry or scanning policy, regulated environments that mandate VM-level isolation, or Docker Desktop licensing that doesn't fit your company size.
On alternatives and trade-offs: if the real need is stronger isolation, I'd keep the container workflow but change the runtime (Kata, gVisor, Firecracker). If the need is just reproducible deploys without runtime overhead, a VM image built with Packer, or a static binary plus systemd, can be simpler. For databases I'd usually pick a managed service unless I have a platform team, tested backups and a real reason (cost, portability, data locality). I'd pick Docker back up the moment the app is stateless, has several environments or teams, or needs a consistent CI-to-prod artifact.
Common mistakes I see: treating containers as lightweight VMs and assuming VM-grade isolation; the opposite dogma that databases must never run in containers, when the real question is whether you can operate the storage; and containerizing by default, which leaves teams maintaining a Dockerfile, base-image upgrades and a registry for a service that did not need any of them.
Version-dependent points: Docker Desktop file-sharing performance has improved a lot in recent releases (VirtioFS is now the default on macOS), so older advice like 'bind mounts are unusably slow' may be outdated. Rootless mode and user-namespace remapping reduce, but do not remove, the shared-kernel risk. Docker Desktop's licensing terms (paid tier for larger companies) have also changed over time, so check current terms before standardizing on it. I'd also say that avoiding Docker is not always the same as avoiding containers, since Podman and containerd-based runtimes may fit better in some environments.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience