Docker Demystified - Part 1: Docker Desktop and Resource Management

Introduction

You’ve been using Docker for years. You build images, run containers, spin up Redis and Postgres for local development. But have you ever wondered what actually happens when you type docker run? This guide peels back the layers.

The Biggest Misconception

Docker did not invent containers. When someone says “Docker container,” they’re actually conflating a company’s product with a Linux kernel feature. Containers are a Linux kernel feature. Docker is a company that packaged and popularized that feature.

Why “Docker” Became Synonymous with “Containers”

This is historical, not technical. Docker:

  • Arrived at the right time (2013, cloud adoption exploding)
  • Solved a real developer pain (“works on my machine”)
  • Made containers usable (simple CLI, Dockerfile, image registries)
  • Built strong branding

So the word stuck. Just like “Xerox” for photocopy and “Google” for search.


Series Overview

Key Takeaways

  1. Containers = Linux kernel feature (namespaces + cgroups + filesystem isolation)
  2. Docker = User experience layer that made containers accessible
  3. OCI standards ensure portability across all container tools
  4. Multiple layers: CLI → dockerd → containerd → runc → container
  5. Production uses containerd + runc on all major clouds

Mental Model

"Container" = Linux process with isolation (namespaces + cgroups)
"Docker"    = Company + tooling that made containers accessible
"OCI"       = Standards that ensure interoperability
"Runtime"   = Either the process spawner (runc) or lifecycle manager (containerd)

Understanding Docker Desktop

Important clarification: You don’t need Docker Desktop to run containers. On Linux, you can install Docker Engine directly. However, Docker Desktop is the most popular and widely-used containerization tool globally. It’s an all-in-one packaged solution that provides a seamless experience—GUI, CLI and automatic updates—making it the best starting point for beginners learning containers.


Why Docker Desktop Needs a VM (Why Linux Doesn’t)

The fundamental truth: Containers are a Linux kernel feature. They rely on kernel primitives like namespaces, cgroups, and capabilities that simply don’t exist in macOS or Windows kernels.

This is the most important mental shift.

A container is:

  • Not a VM
  • Not special
  • Not persistent infrastructure

It is a regular Linux process with:

  • isolated namespaces
  • restricted resources
  • a different root filesystem

That’s why:

  • Containers start in milliseconds
  • ps on the host can see them
  • Crashing the app kills the container

macOS and Windows (Laptops)

On your Mac or Windows laptop, Docker Desktop must run a lightweight Linux VM using a hypervisor:

  • macOS: Uses Apple’s Hypervisor.framework (built into macOS)
  • Windows: Uses Hyper-V or WSL 2 (Windows Subsystem for Linux)

This VM is hidden from you—you don’t see it in your applications list—but it’s there, consuming memory and CPU even when idle. Every container you run actually executes inside this Linux VM, not directly on your host OS.

Why this matters:

  • Docker Desktop on macOS/Windows has two layers of resource allocation: first you allocate resources to the VM, then containers share those VM resources
  • The VM itself consumes 1-2 GB baseline memory before any containers run
  • You’re running a full Linux kernel in the background

Pure Linux (Laptops/Servers)

On a native Linux system, there’s no VM needed:

  • Docker Engine runs directly on your Linux kernel
  • Containers use the same kernel as your host system
  • No hypervisor overhead, no hidden VM
  • Direct access to kernel features (namespaces, cgroups)

The efficiency gain:

  • No baseline memory for a VM (save 1-2 GB)
  • No CPU overhead from virtualization
  • Containers start faster (no VM layer)
  • True “bare metal” container performance

This is why production environments run Linux—you get the full performance benefits of containers without the VM overhead.

Architecture Comparison

macOS/Windows with Docker Desktop:

┌─────────────────────────────────────────────────────────────────┐
│  macOS/Windows Host                                             │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  docker CLI (native binary)                               │  │
│  └─────────────────────────┬─────────────────────────────────┘  │
│                            │ socket                             │
│                            ▼                                    │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  Hypervisor (Hypervisor.framework / Hyper-V / WSL2)      │  │
│  │  ┌─────────────────────────────────────────────────────┐  │  │
│  │  │  Lightweight Linux VM                               │  │  │
│  │  │  ┌───────────────────────────────────────────────┐  │  │  │
│  │  │  │  dockerd → containerd → runc                  │  │  │  │
│  │  │  │                └── your containers            │  │  │  │
│  │  │  └───────────────────────────────────────────────┘  │  │  │
│  │  └─────────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Native Linux:

┌─────────────────────────────────────────┐
│  Linux Host                             │
│  ┌───────────────────────────────────┐  │
│  │  docker CLI                       │  │
│  │       ↓                           │  │
│  │  dockerd → containerd → runc      │  │
│  │                └── containers     │  │
│  │                (native processes) │  │
│  └───────────────────────────────────┘  │
│  ✅ No VM overhead                      │
│  ✅ Native performance                  │
└─────────────────────────────────────────┘

macOS Client vs Daemon Split

Docker Desktop on macOS only installs the client tools (docker, docker-compose) as native binaries. The daemons (dockerd, containerd, runc) are Linux executables that live inside the hidden Docker Desktop Linux VM, so you’ll never find them running as macOS processes.

Exploring the container runtimes

  • Shell into the VM with:

    docker run -it --rm --privileged --pid=host justincormack/nsenter1
    

    Once inside the container shell, run runc --version to inspect the container runtime; it prints something like:

    runc version 1.3.4
    commit: v1.3.4-0-gd6d73eb8
    spec: 1.2.1
    go: go1.24.11
    libseccomp: 2.5.4
    

    What that command actually does

    • --privileged removes most isolation and gives access to host devices (here the linux VM), mounts, and capabilities.
    • --pid=host shares the host PID namespace so you can see every Linux process inside the VM.
    • justincormack/nsenter1 is a helper image created by a Docker engineer specifically to enter Docker Desktop’s VM. Result: you get access to the Linux VM rather than inside an ordinary sandboxed container.

    Back on macOS also, you can confirm which runtime the daemon registered via docker info | grep -i runtime, and docker inspect <container-id> --format '{{.HostConfig.Runtime}}' reports runc, so you see the same runtime name without needing root privileges.

Quick Comparison

CommandWhere it runsWhat you see on macOS
dockermacOS (client)Works
docker-composemacOS (client)Works
dockerdInside the Linux VMNot found
containerdInside the Linux VMNot found
runcInside the Linux VMNot found

My Setup: Docker Desktop on Mac Mini

I’m running Docker Desktop on an M1 Mac Mini with the following resource allocation. I’ve allocated the following resources to Docker Desktop:

  • 5 CPU cores out of my Mac Mini’s available cores
  • ~10 GB memory (9.7 GB to be precise)
  • 3 GB swap space

Docker Desktop Resource Settings

This allocation means that all my containers combined can use up to these limits. But here’s what most developers don’t realize: Docker Desktop itself consumes resources even when no containers are running.

What’s Actually Running?

Let me show you what’s happening on my system right now using btop (a resource monitor for the terminal):

Docker Desktop Resource Usage in btop

I have couple of containers running, Docker Desktop consumes resources for:

  • Docker Desktop app - The GUI and management layer (~200-400 MB)
  • dockerd - The Docker daemon (~100-200 MB)
  • containerd - The container runtime (~50-100 MB)
  • The Linux VM - The hidden virtual machine (~1-2 GB baseline)

On my Mac Mini, Docker Desktop uses approximately 1.5-2.5 GB of RAM running some small containers. On a machine with 16 GB RAM, that’s a significant chunk of resources!


The Critical Difference: Resource Limits

# Without limits - can consume all 5 CPUs and 9.7 GB memory
docker run -d --name my-redis1 redis

# With limits - restricted to 2 CPUs and 512 MB
docker run -d --name my-redis2 -m 512m --cpus=2 redis

Without limits, a single container can consume all resources allocated to my Docker Desktop (5 CPUs and 9.7 GB) during a traffic spike, starving other containers.

Note: This is generally fine for local development where you’re not simulating production traffic. These examples are primarily to understand Docker’s resource management internals and build good habits for production deployments.


The Magic Under the Hood: Linux cgroups

You might wonder: “How does Docker actually enforce these limits?” The answer is elegant—Docker leverages a Linux kernel feature called cgroups (control groups).

Remember, on my Mac Mini, Docker runs containers inside a hidden Linux VM. Inside that VM, the Linux kernel uses cgroups to enforce the limits I specify:

My docker run command on Mac:
  docker run -d --name my-redis2 -m 512m --cpus=2 redis

What happens inside the Linux VM:
  ├── Create cgroup for this container
  ├── Set memory.limit_in_bytes = 536870912 (512 MB)
  ├── Set cpu.cfs_quota_us = 200000 (2 CPUs)
  └── Launch container within this cgroup

What Happens When Limits Are Exceeded

  • CPU: Throttled to the limit—container runs slower but continues
  • Memory: OOM killed (exit code 137)—container crashes immediately

Memory limits are critical; CPU limits are forgiving.


Real-Time Monitoring with docker stats

# Watch all containers in real-time
docker stats

# Output example:
CONTAINER ID   NAME        CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O   PIDS
8a75cdb95255   my-redis2   0.38%     10.34MiB / 512MiB     2.02%     872B / 126B   0B / 0B     6
e945fa9239d7   my-redis1   0.37%     10.85MiB / 9.704GiB   0.11%     998B / 126B   0B / 0B     6

This shows me:

  • CPU % - What percentage of available CPU the container is using
  • MEM USAGE / LIMIT - Current memory vs. the limit I set
  • MEM % - Percentage of the limit being used

If I see a container consistently hitting 90%+ of its memory limit, I know I need to either:

  1. Increase the limit, or
  2. Investigate why it needs so much memory

Detailed Container Inspection

# Quick check without watching (good for scripts and automation)
docker stats --no-stream

# See the actual cgroup limits for a container
docker inspect my-redis1 --format='{{.HostConfig.Memory}}'
docker inspect my-redis2 --format='{{.HostConfig.Memory}}'
# Output: 2147483648 (2 GB in bytes)

docker inspect my-redis1 --format='{{.HostConfig.NanoCpus}}'
docker inspect my-redis2 --format='{{.HostConfig.NanoCpus}}'
# Output: 2000000000 (2 CPUs in nanosecond units)

Performance Considerations

File Sharing Overhead

When you mount local directories on macOS/Windows:

docker run -v /Users/you/code:/app nginx

The file path crosses multiple layers:

  1. macOS/Windows filesystem → 2. VM filesystem sharing (VirtioFS/gRPC-FUSE) → 3. Linux VM → 4. Container mount

This can be 2-10x slower than native Linux file I/O.

Workarounds:

# Use named volumes (data stays in VM - much faster)
docker run -v mydata:/app/data nginx

# Use :cached flag on macOS (trades consistency for speed)
docker run -v /Users/you/code:/app:cached nginx

Docker Desktop Features

Beyond running containers, Docker Desktop provides:

  • GUI Dashboard - Visual management, logs, resource monitoring
  • Built-in Kubernetes - Single-node K8s cluster for local development
  • Docker Scout - Integrated vulnerability scanning
  • Extensions - Add functionality (disk analyzers, etc.)
  • Automatic updates - Stay current with latest releases

Alternatives to Docker Desktop

Podman Desktop

  • Free, no licensing restrictions
  • Rootless containers
  • Similar GUI experience

Lima + nerdctl (macOS)

  • Lightweight, CLI-focused
  • Uses containerd directly
  • Less resource overhead

Comments