Found out something today that should have been obvious but wasn’t: Docker Desktop on Ubuntu is genuinely slower than native Docker Engine. Not slightly slower. Architecturally slower. The kind of slower where you’re running Linux containers inside a Linux VM inside your Linux host for no reason other than a graphical dashboard.
If you’re on Ubuntu and using Docker Desktop, you’re burning performance for a GUI. Here’s the breakdown.
The core problem: a VM inside your native OS
Docker Engine — the headless, command-line version — runs directly on your Ubuntu kernel. It uses native Linux primitives: namespaces for isolation, cgroups for resource limits. Containers are just processes on your host machine. Near-zero overhead. Your SSD reads and writes at full speed. RAM and CPU are allocated dynamically with no artificial caps.
Docker Desktop, even on Linux, spins up a QEMU virtual machine. Every container runs inside that VM. On macOS and Windows, this makes sense — those operating systems don’t have a Linux kernel, so a VM is the only way to run Linux containers. But on Ubuntu, you already have a Linux kernel. Docker Desktop is running a Linux VM inside Linux just so you can get a settings panel.
This is the architectural decision that makes everything else worse.
Where it hurts: file I/O and bind mounts
If you do any local development with bind mounts — the -v flag that maps a host directory into a container — you will feel this immediately. With Docker Engine, that mount is a direct filesystem path. With Docker Desktop, every file read and write crosses a virtualized filesystem layer called virtiofs. The container has to ask the VM, the VM has to talk to the host, and the host has to actually do the I/O.
For I/O-heavy workloads — npm install on a large project, database indexing, anything that touches thousands of small files — the difference is brutal. What takes 8 seconds natively can take 30+ seconds through Docker Desktop’s VM layer. If you’ve ever wondered why your Docker builds felt sluggish on perfectly good hardware, this is probably why.
Resource constraints you didn’t ask for
Docker Engine gives containers access to 100% of your system resources, dynamically. A container uses what it needs and releases what it doesn’t.
Docker Desktop caps resources based on whatever you allocated to its VM in the settings GUI. By default, that’s usually 2 CPUs and 2–4 GB of RAM. If your build is choking, you have to open the Docker Desktop GUI, find the settings panel, and manually bump the limits. Meanwhile, that allocated RAM is reserved for the VM whether containers are using it or not.
Networking gets weird
Docker Engine integrates seamlessly with Ubuntu’s network stack. Ports map directly. iptables rules apply cleanly. Containers using --network=host are literally on your host network.
Docker Desktop sets up its own isolated network bridge inside its user namespace. Routing traffic to localhost, managing custom iptables, or letting containers interact with host network adapters becomes convoluted. It’s not broken — it’s just an unnecessary layer of indirection that adds debugging friction every time something doesn’t route the way you expect.
The comparison
| Docker Engine | Docker Desktop (on Ubuntu) | |
|---|---|---|
| How it runs | Natively on your host kernel | Inside a QEMU VM |
| File I/O | Native SSD speed | Routed through virtiofs layer |
| RAM/CPU | Dynamic, near-zero overhead | Capped by VM allocation, constant baseline drain |
| Networking | Direct host integration | Isolated bridge, user namespace |
| Interface | CLI (+ optional Portainer/Lazydocker) | GUI dashboard |
| Install size | ~200 MB | ~1 GB+ with VM image |
What to do about it
If you’re on Ubuntu and don’t absolutely depend on Docker Desktop’s graphical dashboard, rip it out and install Docker Engine.
Step 1: Remove Docker Desktop
sudo apt-get purge docker-desktop
Step 2: Clean up leftover data
rm -r ~/.docker/desktop
rm ~/.docker/features.json
rm -f ~/.config/systemd/user/docker-desktop.service
rm -f ~/.config/systemd/user/default.target.wants/docker-desktop.service
Step 3: Fix the Docker context
Docker Desktop changes your CLI context to point at its VM. Switch it back:
docker context use default
Step 4: Install Docker Engine
Follow the official instructions at docs.docker.com/engine/install/ubuntu. The short version:
# Set up the repository
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Run without sudo
sudo usermod -aG docker $USER
Log out and back in for the group change to take effect.
Step 5: If you still want a dashboard
Spin up Portainer as a container on your now-native Docker Engine:
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts
Then hit https://localhost:9443 in your browser. You get a full visual dashboard with zero VM overhead.
Alternatively, try Lazydocker for a terminal-based TUI that’s even lighter.
The bottom line
Docker Desktop was built for macOS and Windows where a VM is unavoidable. When they brought it to Linux, they kept the same architecture so the GUI and extensions would work identically across platforms. Fair enough for consistency. But on Ubuntu, you’re paying a real performance tax — slower I/O, memory overhead, networking friction — for a graphical interface you can replace with a single container.
Install Docker Engine. Add Portainer if you need visuals. Your builds will thank you.