I run Docker on macOS without Docker Desktop. Colima runs the daemon in a VM, the Homebrew docker package is the client, and lazydocker is the UI when I want one. No account, no sign-in, no background app, and the whole thing starts and stops from the command line.

This post used to describe installing Docker.app and driving it with a pair of osascript wrappers. That worked in 2020. Colima is better, so this is how I do it now.

Install

sh
brew install colima docker docker-compose docker-buildx docker-credential-helper lazydocker

docker is just the client. Colima provides the daemon. Compose and Buildx are CLI plugins rather than separate binaries, and the credential helper keeps registry logins in the macOS keychain instead of a plaintext file.

Homebrew installs the plugins somewhere the Docker CLI doesn’t look by default, so point it at them in ~/.docker/config.json:

json
{
  "cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"],
  "credsStore": "osxkeychain"
}

/opt/homebrew is what brew --prefix prints on Apple Silicon. If yours prints something else, use that.

Without cliPluginsExtraDirs, docker compose and docker buildx fail as unknown commands while docker-compose still works, which is a confusing way to find out.

Configure the VM

Colima writes ~/.colima/default/colima.yaml on first start and then leaves it alone. Put the file in place before that first start, because several of its settings can’t be changed once the VM exists.

sh
mkdir -p ~/.colima/default
$EDITOR ~/.colima/default/colima.yaml

The settings I care about:

yaml
cpu: 4
memory: 8
disk: 60
arch: aarch64
runtime: docker
vmType: vz
mountType: virtiofs
autoActivate: true
kubernetes:
  enabled: false
mounts: []

vmType: vz uses Apple’s Virtualization framework instead of QEMU, which needs macOS 13 or newer. mountType: virtiofs is the fast mount driver and requires vz. autoActivate: true makes colima the active Docker context on start.

mounts: [] is deliberate and the empty list matters. An empty list mounts $HOME writable. mounts: null, or leaving the key out entirely, mounts nothing at all.

Then:

sh
colima start

colima status should report vz and virtiofs, and docker context ls should show colima as current.

Changing it later

CPU and memory are adjustable on an existing VM:

sh
colima stop
colima start --cpu 6 --memory 12

Disk can grow but never shrink. arch, runtime, vmType, and mountType are fixed for the life of the VM. Changing one of those means starting over:

sh
colima delete
colima start

lazydocker

sh
lazydocker

It reads the active docker context, so it finds Colima on its own. It needs a real terminal, which is worth knowing before you try to run it from a script. Its config lives at ~/Library/Application Support/lazydocker/config.yml and stays empty until you change something from inside the UI.

Colima doesn’t mount /tmp

Colima mounts $HOME. It does not mount /tmp.

sh
docker run -v /tmp/work:/data alpine ls /data

That prints nothing and exits 0. The bind mount silently resolves to an empty directory inside the VM, and Docker reports no error. I’ve lost time to this more than once. Keep bind mount sources under $HOME:

sh
docker run -v ~/work:/data alpine ls /data