If you work on macOS or Windows, you have spent the whole course running containers inside a Linux virtual machine without ever seeing it. Docker Desktop is that VM, plus a graphical interface and a handful of extras. This lesson opens the box: what is inside, why that explains why bind mounts are slow, what it genuinely adds, what it costs — including the small print of the license — and what alternatives you have on each system.

Contents

  1. What Docker Desktop actually is
  2. The architecture from the inside
  3. Why bind mounts are slow
  4. The VM and its resources
  5. The virtual disk that grows and never shrinks
  6. Windows: WSL 2 versus Hyper-V
  7. macOS: Apple Silicon, Rosetta and VirtioFS
  8. What the interface brings
  9. Docker Scout, one-click Kubernetes and extensions
  10. docker init, docker debug and dev containers
  11. Licensing and cost
  12. Alternatives to the desktop app
  13. Recommendation by profile

  1. What Docker Desktop actually is

Docker Desktop is not Docker. It is a bundle that contains, among other things:

Component What for
A lightweight Linux VM Running the kernel that containers need
Docker Engine inside that VM The same old dockerd (01-03)
A native docker client on the host The binary you run in your terminal
Plugins: compose, buildx, scout, debug, init CLI subcommands
Graphical interface Panels for containers, images, volumes and builds
Bundled Kubernetes A single-node cluster, optional
Extension manager Third-party tools inside the dashboard
Port forwarding and file system sharing So that localhost:8080 and your folders work

The key point is in the first row: containers are a Linux kernel feature — namespaces and cgroups, exactly as you saw in 05-07. macOS and Windows do not have that kernel, so one has to be supplied. On Linux, Docker Desktop is optional precisely because the kernel is already there: docker talks directly to dockerd with no VM in between.

  1. The architecture from the inside

graph TB
    subgraph mac["macOS / Windows (host)"]
        CLI["docker client + docker compose"]
        GUI["Docker Desktop graphical interface"]
        FS["Your folders: ~/aurora-libros"]
    end
    subgraph vm["Linux VM (Apple Virtualization / WSL 2)"]
        D["dockerd"]
        CTD["containerd + runc"]
        C1["aurora-api"]
        C2["aurora-db"]
        SHARE["File sharing layer<br/>(VirtioFS / 9p)"]
    end
    CLI -->|"host socket"| D
    GUI --> D
    D --> CTD --> C1
    CTD --> C2
    FS <-->|"bind mount: crosses the boundary"| SHARE
    SHARE <--> C1

Anything that crosses the boundary between the host and the VM has a cost. And two things cross it constantly: the files of your bind mounts and the network packets of your published ports.

  1. Why bind mounts are slow

When you mount ./src:/app/src on Linux, nothing special happens: the container sees the very same host file system through the mount namespace. On macOS or Windows, every open(), every stat() and every read() travels from the container to the VM, from the VM to the host, and back.

Scenario Relative cost Reason
Files inside the VM (named volume) 1× (native) Nothing crosses
Bind mount with VirtioFS (recent macOS) 2-5× Efficient protocol, but it still crosses
Bind mount with 9p / gRPC-FUSE (old) 10-50× An enormous number of calls per operation
Bind mount from /mnt/c/... in WSL 2 10-30× It crosses the NTFS-to-Linux translator
Files on the WSL file system 1× It is already in Linux

There you have the reason behind a recommendation you will see everywhere and that you now understand: never put node_modules in a bind mount. Installing the aurora-api dependencies with the folder mounted from macOS can take minutes; with a named volume, seconds.

# compose.override.yaml — the pattern that saves your performance
services:
  api:
    volumes:
      - ./src:/app/src          # your code: few reads, changes often
      - node_modules:/app/node_modules   # thousands of files: inside the VM
volumes:
  node_modules:

Measure it yourself, which is more convincing than any table:

# Inside a bind mount from the host
docker run --rm -v "$PWD":/test -w /test alpine \
  sh -c 'time (for i in $(seq 1 2000); do echo x > f$i; done)'

# Inside a named volume (it lives in the VM)
docker run --rm -v test-vol:/test -w /test alpine \
  sh -c 'time (for i in $(seq 1 2000); do echo x > f$i; done)'

  1. The VM and its resources

The VM has a fixed size that you assigned to it, and everything your containers run competes inside it. When a docker compose up of Aurora Libros is slow for no obvious reason, this is the first place to look.

Setting Where Recommendation
CPUs Settings → Resources Half of the host's cores
Memory Settings → Resources 4-8 GB for the Aurora Libros stack
Swap Settings → Resources 1-2 GB; more does not pay off
Virtual disk size Settings → Resources 60 GB or more if you build a lot
Backend Settings → General WSL 2 on Windows, VZ on macOS
docker info --format 'CPUs: {{.NCPU}}  Memory: {{.MemTotal}}'
docker system df -v          # where the disk is really going
docker stats --no-stream     # which container is eating the VM right now

A frequent and confusing symptom: PostgreSQL dies with exit code 137 while importing the nine titles of the catalog. It is not a database failure, it is the OOM killer of the VM's kernel. If the VM has 2 GB and the containers ask for 3, somebody dies; raise it and the problem disappears.

  1. The virtual disk that grows and never shrinks

The VM's disk is a sparse file on your host — Docker.raw on macOS, a .vhdx on Windows — that grows when you write and does not shrink when you delete. You can have 8 GB of images according to Docker and 90 GB taken up on your laptop.

docker system df
# TYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE
# Images          34      6        18.2GB    12.6GB (69%)
# Build Cache     412     0        21.4GB    21.4GB

du -sh ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw   # macOS

Reclaiming space takes two steps, and the order matters:

# 1. Free space inside the VM
docker builder prune -f              # the BuildKit cache is usually the biggest chunk
docker image prune -a -f             # images with no container attached
docker system prune -a --volumes     # CAREFUL! this deletes unused volumes

# 2. Give the space back to the host
#    macOS and Windows: the "Clean / Purge data" button in Settings → Resources
#    Windows with WSL 2, from PowerShell:
#    wsl --shutdown ; Optimize-VHD -Path <path>.vhdx -Mode Full

The warning on the third command is serious: --volumes will take aurora-data with it if no container happens to be using it at that moment. The nine titles of the catalog vanish without a question being asked. Run docker volume ls first and, in general, prefer selective prune commands.

  1. Windows: WSL 2 versus Hyper-V

WSL 2 backend Hyper-V backend (legacy)
Foundation Windows Subsystem for Linux A full Hyper-V VM
Memory Dynamic: it grows and gives back Fixed, reserved at boot
Startup Seconds Tens of seconds
Windows editions Home included Pro/Enterprise only
I/O performance Very good inside WSL Uniform and mediocre
Distro integration Yes: docker from Ubuntu-WSL No
Recommendation The default Only if WSL 2 is not viable

Distro integration is what makes daily work comfortable: in Settings → Resources → WSL Integration you enable your Ubuntu, and from its terminal the docker command works without installing anything inside it.

And the rule that will buy you the most performance on Windows: keep your code on the Linux file system, not on the Windows one.

# BAD: the project in C:\Users\... seen from WSL
cd /mnt/c/Users/your-user/aurora-libros && docker compose up   # painfully slow

# GOOD: the project inside the distribution
cd ~/aurora-libros && docker compose up                          # native

The difference is not a nuance: an npm install for aurora-api can go from three minutes to twenty seconds just by moving the folder. If you need to edit from Windows, VS Code with the WSL extension opens the project remotely without taking the files out of Linux.

  1. macOS: Apple Silicon, Rosetta and VirtioFS

On a Mac with Apple Silicon, the VM is arm64. Your images are built and run natively on arm64, and everything is fast... until an image turns up that only exists for amd64.

docker run --rm alpine uname -m
# aarch64            ← native, all good

docker run --rm --platform linux/amd64 alpine uname -m
# x86_64             ← emulated: it works, but it costs
# WARNING: The requested image's platform (linux/amd64) does not match
#          the detected host platform (linux/arm64/v8)
Option How it works Approximate cost
Native arm64 image No translation 1×
Rosetta for Linux/x86 Instruction translation accelerated by Apple 1.2-2×
QEMU (without Rosetta) Software emulation 3-10×, and it sometimes fails

Rosetta is enabled in Settings → General → Use Rosetta for x86/amd64 emulation and it improves the experience a lot, but it is not magic: some binaries fail, some CPU extensions are missing and some workloads remain slow.

That is why the multi-architecture work from module 5 was not academic. Your ghcr.io/auroralibros/aurora-api:2.0.0 is published for linux/amd64 and linux/arm64, so the Mac pulls the arm64 variant and the production server pulls the amd64 one, with no emulation on either side. That is the correct solution to the problem.

As for files, macOS uses VirtioFS with Apple's virtualization framework: it is noticeably faster than the old gRPC-FUSE, although it still does not reach native speed. Check that it is enabled in Settings → General → Choose file sharing implementation.

  1. What the interface brings

The GUI does nothing the CLI cannot do, but there are tasks where seeing is faster than typing.

Panel What it is genuinely useful for
Containers Seeing the whole Aurora Libros stack grouped by Compose project; opening logs, a terminal and stats with one click
Images Seeing sizes at a glance and deleting what is left over without remembering the prune commands
Volumes Checking what aurora-data takes up and browsing its files, which from the CLI requires a helper container
Builds Build history with duration per step and cache hits
Dev Environments / Extensions Optional extras, of varying usefulness

The Builds panel is the most underrated: it tells you which step of the aurora-api Dockerfile is costing you the seconds and whether the cache hit or missed, which is exactly what you optimized blind in 05-04.

  1. Docker Scout, one-click Kubernetes and extensions

Docker Scout comes built in and analyzes local images:

docker scout quickview ghcr.io/auroralibros/aurora-api:2.0.0
docker scout cves ghcr.io/auroralibros/aurora-api:2.0.0 --only-severity critical,high
docker scout recommendations ghcr.io/auroralibros/aurora-api:2.0.0

It is convenient for a quick review on the desktop, but it does not replace scanning in the pipeline: what decides whether an image gets published is the Trivy step in your GitHub Actions (06-02), which runs without human intervention and fails the build. Scout is the early check; the pipeline is the gate.

One-click Kubernetes enables a single-node cluster inside the same VM.

Good for Not good for
Checking that your manifests apply without errors Anything resembling production
Learning kubectl without setting anything up Testing high availability or rescheduling
Debugging a Deployment or a Service Measuring performance or real autoscaling
Checking a basic Ingress Testing the provider's StorageClass or CNI

Compared with kind, minikube or k3d (06-04), its advantage is that it is already there; its disadvantage is that it consumes memory from the same VM, and you cannot have several clusters or pick a version comfortably. To experiment with several versions or simulate several nodes, kind is still better.

Extensions add third-party tools to the dashboard (log viewers, database clients, disk managers). They are useful with one precaution: they are third-party code with access to your daemon. Install only the ones you need, and only from known sources.

  1. docker init, docker debug and dev containers

cd ~/aurora-libros/aurora-api
docker init
# ? What application platform does your project use? Node
# ? What version of Node do you want to use? 22
# ? Which port does your server listen on? 8080
# Created: .dockerignore, Dockerfile, compose.yaml, README.Docker.md

docker init generates reasonable scaffolding: multi-stage, an unprivileged user and a .dockerignore. It is an excellent starting point and a poor finishing point: it does not pin the base image by digest, and it adds neither the three probes from 06-01 nor graceful shutdown. Use it to get going and apply what you know on top.

docker debug solves the classic problem of minimal images: when aurora-api:2.0.0 has neither sh nor curl, docker exec is of no use to you.

docker debug aurora-api
# Attaches a shell with tools (curl, vim, ps, ss...) WITHOUT modifying the image

It installs nothing in the container and does not change the image: it mounts a set of utilities in a separate layer. It is the convenient alternative to the netshoot container you will see in 07-04, and it is a paid-subscription feature.

Finally, VS Code dev containers: a .devcontainer/devcontainer.json describes the complete environment — image, extensions, ports, commands — and the editor runs inside the container.

{
  "name": "aurora-api",
  "dockerComposeFile": ["../compose.yaml", "../compose.override.yaml"],
  "service": "api",
  "workspaceFolder": "/app",
  "forwardPorts": [8080],
  "customizations": { "vscode": { "extensions": ["dbaeumer.vscode-eslint"] } }
}

With this, somebody new to Aurora Libros clones the repository, opens VS Code and has Node 22, PostgreSQL 16 and Redis 7 running. It is the definitive answer to the fifteen manual onboarding steps the course started with in 01-07.

  1. Licensing and cost

Docker Desktop is proprietary software with a commercial license, even though the Docker Engine it carries inside is open source. Under the terms in force in 2026:

Use Condition
Personal Free
Education and teaching Free
Non-profit open source projects Free
Small companies Free below a certain employee and revenue threshold
Companies above that threshold Paid subscription per user

The threshold has changed more than once since 2021, and the specific wording — what counts as an employee, whether it includes contractors, what happens within a group of companies — determines whether your organization has to pay.

Practical warning: do not install Docker Desktop on a corporate machine assuming it is free. Check the terms in force on the official website at the moment you install it and validate it with your company's legal or procurement department. There have been license audits with retroactive invoices, and the responsibility does not fall on whoever installed it, but on the company. If your company decides not to pay, the good news is that there are fully functional alternatives and none of them affects your images, which are standard OCI.

  1. Alternatives to the desktop app

Tool Systems License Graphical interface Strong at Weak at
Native Docker Engine Linux Apache 2.0 No Native performance, zero cost Linux only, no GUI
Rancher Desktop mac, Win, Linux Apache 2.0 Yes Built-in K8s (k3s), choice of runtime Less polished, heavier
Podman Desktop mac, Win, Linux Apache 2.0 Yes Daemonless, rootless, good K8s support Compatibility details (07-05)
Colima mac, Linux MIT No Lightweight, brew install, very simple No GUI, small community
OrbStack macOS Proprietary (free for personal use) Yes The fastest and lightest on a Mac macOS only, paid for companies
lima mac, Linux Apache 2.0 No Custom Linux VMs, the basis of Colima Requires more configuration
# Colima: a minimal alternative on macOS, with the usual Docker CLI
brew install colima docker docker-compose
colima start --cpu 4 --memory 8 --vm-type vz --mount-type virtiofs
docker context ls        # colima shows up as a context (that thing from 07-01!)
docker compose up -d     # Aurora Libros comes up just the same

Notice the detail: all these alternatives integrate through Docker contexts, the mechanism from the previous lesson. And on all of them your images and your compose.yaml work without changes, because nothing you have built during the course depends on Docker Desktop.

  1. Recommendation by profile

Profile Recommendation Reason
Learning, on a personal laptop Docker Desktop Free, everything integrated, less friction
Developer on Linux Native Docker Engine No VM, no license, maximum performance
Developer on a Mac, company pays Docker Desktop or OrbStack Integrated; OrbStack if performance matters
Developer on a Mac, no license Colima or Podman Desktop Free and sufficient
Developer on Windows Docker Desktop with WSL 2 By far the best integrated option
Company avoiding proprietary software Podman Desktop or Rancher Desktop Apache 2.0, no thresholds to watch
Server or CI Never Docker Desktop It is a desktop tool; use Engine

The last row admits no nuance: on a server or a continuous integration runner you install Docker Engine, never Docker Desktop.

Common Mistakes and Tips

  • Working from /mnt/c/... in WSL 2. It is the number one cause of slowness on Windows. Move the project to ~/ inside the distribution.
  • Bind-mounting node_modules. Thousands of small files crossing the boundary. Use a named volume for that path.
  • Being surprised by exit code 137. It is the VM's OOM killer. Raise the memory in Resources or set per-service limits.
  • Believing that deleting images frees space on your laptop. The virtual disk does not shrink by itself: it has to be compacted or purged separately.
  • Running docker system prune -a --volumes without looking. It takes aurora-data with it, and the nine titles along with it. Check docker volume ls first.
  • Installing it at the company without checking the license. The terms change and audits happen. Verify, and consult legal or procurement.
  • Using Docker Desktop's Kubernetes as if it were production. It is a single node: it tests neither availability, nor rescheduling, nor the real CNI.
  • Tip: enable Rosetta on Apple Silicon, but solve the problem at the root by publishing multi-architecture images as you did in 05-05.
  • Tip: check the Builds panel when a docker compose build gets slow. It will point at the guilty step in seconds.
  • Tip: keep a .devcontainer/devcontainer.json in the repository. It turns onboarding into "clone and open".

Exercises

Exercise 1 — Measure the boundary. Design and run a measurement that compares the time it takes to create 3,000 small files in three locations: a bind mount from a folder on your host, a named volume and the container's internal file system. Note the three times, work out the ratio and explain the result using the architecture from §2. If you are on Linux, explain why the three numbers look alike.

Exercise 2 — Space audit and recovery. Find out how much Docker takes up according to its own accounting and how much the virtual disk really takes up on your host. Explain the difference. Then free up space safely, leaving the aurora-data volume untouched, and check the result in both measurements. State which command would have destroyed the data and why.

Exercise 3 — Choosing a desktop tool. Aurora Libros grows to 14 developers: 6 on macOS with Apple Silicon, 5 on Windows and 3 on Linux. The company is above the free license threshold. Procurement asks whether 14 subscriptions have to be paid for. Prepare a recommendation with: what is actually needed on each system, which alternative you propose for each group, what is lost with each alternative, and which concrete questions have to be asked of legal before deciding.

Solutions

Solution 1.

mkdir -p /tmp/bind-test && docker volume create test-vol >/dev/null
CMD='time (for i in $(seq 1 3000); do echo x > /dest/f$i; done)'

echo "== Bind mount from the host =="
docker run --rm -v /tmp/bind-test:/dest alpine sh -c "$CMD"

echo "== Named volume (inside the VM) =="
docker run --rm -v test-vol:/dest alpine sh -c "$CMD"

echo "== The container's writable layer =="
docker run --rm alpine sh -c "mkdir /dest && $CMD"

rm -rf /tmp/bind-test && docker volume rm test-vol

Typical results on a Mac with Apple Silicon and VirtioFS: the bind mount around 8-12 s, the named volume 0.6-1.2 s, the container layer 0.5-1 s. The bind mount ratio is around 10×. The explanation is the one from §2: every open(), write() and close() of the 3,000 files goes through the sharing layer between the VM and the host, and that is 9,000 round trips; the named volume lives inside the VM's disk and crosses nothing, just like the overlay2 writable layer (05-07).

On Linux the three numbers come out almost identical — tenths of a second — because there is no boundary to cross: the bind mount is the same file system seen from a different mount namespace. That is exactly why a colleague on Linux cannot reproduce your slowness problem, and why the solution is not "get a better laptop" but move the files to the right side of the boundary.

Solution 2.

docker system df                     # internal accounting
du -sh ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw   # macOS
# wsl --shutdown ; (Get-Item <path>.vhdx).Length                            # Windows

The difference is that the virtual disk is a sparse file that grows as you write and never shrinks when you delete: when you remove an image, the blocks become free inside the VM but the file keeps reserving that size on your laptop.

docker volume ls                       # confirm that aurora-data exists
docker builder prune -f                # BuildKit cache: the bulkiest thing
docker image prune -a -f               # images with no container attached
docker container prune -f              # stopped containers
docker system df                       # check the improvement
# Afterwards: Settings → Resources → Clean/Purge data, or compact the .vhdx
docker volume ls | grep aurora-data    # still there

The destructive command is docker system prune -a --volumes: the --volumes option includes volumes with no container attached at all, and if aurora-db happens to be stopped at that moment, aurora-data gets caught in the sweep and the nine titles of the catalog disappear without any per-volume confirmation. As a safety net, run docker run --rm -v aurora-data:/v -v "$PWD":/b alpine tar czf /b/aurora-data.tgz -C /v . before any aggressive cleanup — exactly the backup pattern from 05-02.

Solution 3. Recommendation by group:

Group Proposal What is lost
3 on Linux Native Docker Engine Nothing relevant: they need neither a VM nor a GUI
6 on macOS Colima (or OrbStack if speed is the priority) The integrated GUI, local Scout, docker debug
5 on Windows Docker Desktop or Podman Desktop / Rancher Desktop With the alternatives, some WSL integration

Analysis: of the 14 licenses, 3 are unnecessary straight away because on Linux Docker Desktop adds nothing. On macOS, Colima covers the whole case with the usual CLI and the same contexts; what you lose are desktop features the team probably uses very little, and it is worth asking before deciding for them. Windows is where WSL 2 integration has the most real value, so there it may be worth paying for a few licenses instead of forcing an alternative. A reasonable middle way: 5 licenses for Windows and free alternatives on macOS and Linux, to be reviewed in six months.

Questions for legal and procurement before deciding anything: what exactly do the terms in force say about the threshold, and do we count total employees or only developers?; do external contractors and subsidiaries count within the group?; is there already a framework agreement with Docker, or a subscription contract for another product that covers this? Add a fourth, internal question, which is often the one that settles the debate: which specific Docker Desktop features does the team genuinely use every week? If the answer is "starting Compose and looking at logs", the decision makes itself.

Conclusion

You now know what that VM you had been using without seeing it is. Docker Desktop is not Docker: it is a Linux virtual machine with dockerd inside, plus a GUI, plugins and extras, and it exists because containers are a Linux kernel feature that macOS and Windows do not have. On Linux it is optional for exactly that reason.

Everything else follows from that architecture. You understand why bind mounts are between 2 and 30 times slower depending on the system and the protocol, why node_modules must live in a named volume, and why on WSL 2 moving the project from /mnt/c/... to ~/ can turn three minutes into twenty seconds. You know how to size the VM, how to diagnose exit code 137 for what it is — the OOM killer, not a PostgreSQL bug — and how to reclaim space at both levels, with the warning that --volumes takes aurora-data without asking.

You know the terrain of each system: WSL 2 versus Hyper-V and distro integration on Windows; Apple Silicon, Rosetta and VirtioFS on macOS, with the realization that the multi-architecture work from 05-05 is the root solution to the emulation problem. And you know what the tool genuinely brings: the Builds panel with duration per step, Scout as an early check that does not replace pipeline scanning, one-click Kubernetes with its honest limits, docker init as a good starting point and a poor finishing point, docker debug for images with no shell, and dev containers as the definitive answer to the fifteen onboarding steps from 01-07.

And you take away what is not technical: it is proprietary software with license thresholds that have changed several times, so you check them on the official website and validate them with legal or procurement before installing it at the company. If the answer is that nobody is paying, you have the table of alternatives — native Engine, Rancher, Podman Desktop, Colima, OrbStack, lima — all integrated through contexts and all running your images without changing a line, because nothing you have built depends on Docker Desktop.

In the next lesson we leave the desktop to equip the workflow: the third-party tools and plugins that surround Docker in a real project. How CLI extensibility works — you will build your own docker aurora — what is worth having in interfaces, inspection, security, private registries, development and alternative builders, and what criteria you use to decide whether to adopt a tool or leave it out.

Docker: From Beginner to Advanced

Module 1: Introduction to Docker

Module 2: Working with Docker Images

Module 3: Docker Containers

Module 4: Docker Compose

Module 5: Advanced Docker Concepts

Module 6: Docker in Production

Module 7: Docker Ecosystem and Tools

© Copyright 2026. All rights reserved