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.