Migration from Docker Desktop to Colima
A quickstart guide to getting up and running with Colima for your Docker needs.
Goal
This quickstart explains how to replace Docker Desktop with Colima for Docker Compose projects.
Why
Docker Desktop’s licensing changes in August 2021 prompted the search for an alternative.
Colima supports Docker Compose and Buildx through the Docker CLI plugins. Additional features include:
- Direct access to the Linux VM.
- More dynamic memory utilization, less memory used when less resources are needed (at least that is my observation).
- Multiple VM instances through named profiles.
Installation
This guide uses Homebrew, a common installation method on macOS. Other installation options are also available.
Install Homebrew
Install Homebrew if it is not already installed on your system.
brew.sh
provides the following command. Consider downloading and inspecting the install.sh script before running it.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Install Rosetta
To use Rosetta for amd64 containers on Apple Silicon, install it on the host and enable it in Colima’s configuration as shown below.
softwareupdate --install-rosettaInstall Colima, Docker, Docker Compose, and Docker Buildx
Install Colima and the Docker CLI tools, then configure Compose and Buildx as Docker CLI plugins.
brew install colima
brew install docker
brew install docker-compose
brew install docker-buildxmkdir -p "$HOME/.docker/cli-plugins"
ln -sfn "$(brew --prefix)/opt/docker-compose/bin/docker-compose" "$HOME/.docker/cli-plugins/docker-compose"
ln -sfn "$(brew --prefix)/opt/docker-buildx/bin/docker-buildx" "$HOME/.docker/cli-plugins/docker-buildx"Verify that the CLI can find both plugins:
docker compose version
docker buildx versionShell Autocomplete
If desired, add autocomplete support for Colima.
Zsh
Extend fpath to include a custom directory for completion functions while preserving its existing entries.
Add the following to .zshrc, before any existing call to compinit.
If your shell configuration already initializes completion, keep that initialization instead of adding another call.
fpath=("$HOME/.zsh/functions" "${fpath[@]}")
autoload -Uz compinit
compinitRun the following commands once to create the directory and generate the Colima completion script:
mkdir -p "$HOME/.zsh/functions"
colima completion zsh > "$HOME/.zsh/functions/_colima"Start a new shell to load the completions.
Other Shells
Bash and Fish are also supported.
Configuration
Configure an existing profile with colima start --edit, or edit its configuration file directly.
With the default configuration directory, the default profile uses ~/.colima/default/colima.yaml, and named profiles use ~/.colima/<profile-name>/colima.yaml.
My preference is updating the colima template their the CLI to launch an editor. Validation is done on save, and invalid changes are rejected. So it is best to make small, incremental changes.
colima templateThe template is stored at ~/.colima/_templates/default.yaml. Changes to the template apply to new profiles; they do not update existing profiles.
CPU
Choose a CPU allocation that is equal or less to the number of cores on your machine. Personally, I match the number of cores on my machine.
The following example allocates 10 cores; adjust it to suit your hardware and workload.
cpu: 10Disk
The default disk size for container data is 100 GiB. Colima can increase an existing disk on startup, but it cannot shrink it in place.
Starting with Colima v0.9.0, container data is stored on a separate disk and preserved across ordinary VM deletion and recreation.
Pulled images and other container data are restored when the profile starts again. colima delete --data also deletes that data disk.
Reducing its size requires recreating the data disk and backing up any data that needs to be retained.
disk: 100Memory
Increase the default 2 GiB allocation if your workload needs more memory, while leaving enough physical memory for macOS and other applications.
For heavier workloads, 32 GiB on a 48 or 64 GiB machine, or 16 GiB on a 24 or 32 GiB machine, may be useful starting points. Smaller workloads may need much less. Monitor memory pressure and adjust the allocation as necessary.
Early observations during this migration suggested that Colima used less host memory than Docker Desktop under lighter workloads. Actual memory use depends on the workload, VM configuration, and software versions; this observation does not establish a general advantage or justify allocating nearly all physical memory to the VM.
memory: 32Network
Keep network.address disabled unless you need a directly reachable VM IP address. This setting does not disable the VM’s networking or the usual forwarding of published container ports to localhost.
Some VPN configurations can conflict with the networking used to provide a reachable VM address.
Depending on the setup, a DNS conflict on port 53 may prevent the VPN from starting while Colima is running.
If this occurs, try disabling network.address and restarting Colima. DNS conflicts can also occur with Docker Desktop, although troubleshooting differs.
network:
address: falseThe Testcontainers examples below show how to select localhost or the VM’s reachable IP as the host for published container ports. Due to VPN and testcontainers, I have found keeping address false to be easier.
Virtualization
On Apple Silicon, use Apple’s Virtualization framework (vz), especially when running amd64 containers with Rosetta.
This backend requires macOS 13 or newer.
vmType: vzEnable Rosetta for amd64 containers on Apple Silicon when using vz:
rosetta: trueMount Type
Use virtiofs with vz; it is already the default mount type for this backend.
During this migration, some provisioning scripts encountered mount directories before their contents became visible.
That timing issue was not observed with virtiofs in the tested setup, but mount readiness should still be considered when writing provisioning scripts.
mountType: virtiofsMounts
Additional mounts may not be necessary, but the Linux VM does not automatically inherit certificates trusted by the macOS Keychain. If a private registry uses a certificate signed by an internal CA, or a self-signed certificate, add the appropriate trust certificate to the VM.
-
If you add a mount point, the default mount point (one for
~) must be explicitly added. -
Create
~/.colima/certs, place the required PEM-encoded.crtcertificates there, and add a read-only mount:
mounts:
- location: ~/
writable: true
- location: ~/.colima/certs
mountPoint: /certs
writable: falseDo not use ~ in YAML by itself, as that is treated as null.
Either quote as in "~" or add a slash like above, ~/.
- Install the certificates into the Linux VM’s trust store during provisioning:
provision:
- mode: system
script: |
if ls /certs/*.crt >/dev/null 2>&1; then
install -m 644 /certs/*.crt /usr/local/share/ca-certificates/
update-ca-certificates --verbose > /tmp/update-ca-certificates.log 2>&1
fiProvisioning scripts run on startup, so they should be safe to run repeatedly.
If certificate files are not yet visible when provisioning runs, add bounded retry logic before installing them.
A delay of about two seconds was observed in the tested setup, but timing can vary. The script above skips installation if no .crt files are visible.
Troubleshooting
Make small configuration edits and check for YAML validation errors when saving. If an edit is rejected, correct the syntax and confirm that the intended changes were saved.
To inspect the VM’s startup logs, connect with colima ssh and run journalctl inside the VM.
Testcontainers
Testcontainers needs the location of Colima’s Docker socket. For the default profile in the default configuration directory, add the following to your shell’s configuration:
DOCKER_HOST points to the socket on the macOS host.
TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE specifies the socket path inside the VM for containers such as Ryuk.
export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sockAdjust your URI according to the colima profile name being used.
Use colima status to obtain the actual docker.sock location.
If you left network addressing enabled address: true you will also need to set the TESTCONTAINERS_HOST_OVERRIDE.
Set the host override according to how your tests reach published container ports.
export TESTCONTAINERS_HOST_OVERRIDE="$(colima ls -j | jq -r 'select(.name == "default") | .address')"Run this command after Colima starts. Update the profile name if you use a named profile, and refresh the variable if the VM’s address changes.
If jq is not currently available, install with brew install jq.
Without addressing, localhost is used.
So when address: false either unset TESTCONTAINERS_HOST_OVERRIDE or set it to localhost.
Personally, I set it localhost for reminder of what is needed.
export TESTCONTAINERS_HOST_OVERRIDE=localhostFor complete details, check out the testcontainers official documentation .
All Set
You are now ready to use Colima for your Docker and Docker Compose workloads.
On Demand
Start Colima and use the Docker CLI as before:
colima startColima normally activates its Docker context on startup. An existing DOCKER_HOST setting takes precedence over that context, so ensure it points to the intended profile if you have set it.
When you are done, use colima stop. To restart the VM, use colima restart.
As a Service
If Homebrew was used, you can leverage homebrew to also run colima as a service.
brew services start colimaUse brew services list to check the status of the service.
The start, stop, and restart are the commands to learn, use brew serives --help for details.
Try It Out
My Kafka Streams Dashboards project is redesigned to work with Colima. While continuing to work with Docker Desktop. There really isn’t much to it, provided the docker compose and docker buildx plugins are properly installed. I just updated my gradle build script to handle multiple docker paths.
While just moving to docker would work too, the build is more robust with the above approach.
val dockerExe = listOf(
"/opt/homebrew/bin/docker",
"/usr/local/bin/docker",
"docker"
).firstOrNull {
it == "docker" || file(it).exists()
} ?: throw GradleException("Docker executable not found")