klimax is now marina. Same tool, new name since v0.3. Existing install? Migrate with marina migrate →
Documentation menu

marina as a Docker host

marina runs a Docker daemon inside its VM to host kind clusters. That daemon is an ordinary one — you can point your Mac at it and use it for anything, with no Docker Desktop installed.

Point Docker at it

Two ways, and they conflict — pick one. A Docker context persists across shells and is the better default:

$ marina docker-context        # create and switch to the "marina" context
$ docker ps                    # now talks to the VM
$ marina docker-context --unset # switch back to "default"

Or set DOCKER_HOST for the current shell only:

$ eval $(marina docker-env)
$ eval $(marina docker-env --unset)
⚠

DOCKER_HOST overrides the active context. If you set both you will eventually confuse yourself about which daemon you are talking to — use one mechanism.

Either way the socket is a Unix socket at ~/.marina.docker.sock, forwarded by Lima. Nothing listens on a TCP port, so nothing on your network can reach the daemon.

Docker Compose

docker compose is a client-side plugin, not something the daemon provides. The marina VM ships it, so marina shell docker compose version works — but that says nothing about your Mac, where the docker CLI looks for plugins in ~/.docker/cli-plugins/. Installing Compose with Homebrew does not put it there:

$ brew install docker-compose
$ docker compose version
docker: unknown command: docker compose   # the binary exists, the plugin link does not

Link it once:

$ mkdir -p ~/.docker/cli-plugins
$ ln -sfn /opt/homebrew/opt/docker-compose/bin/docker-compose \
        ~/.docker/cli-plugins/docker-compose
$ docker compose version

Homebrew suggests an alternative that picks up future plugins automatically — add to ~/.docker/config.json:

{
  "cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"]
}
🔌

This is not specific to marina — the same applies to any Docker daemon you point a Homebrew-installed CLI at. It is worth knowing here because Docker Desktop bundles its own CLI with the plugins already wired, so people moving away from it meet this on their first docker compose up.

Published ports on localhost

This is the part worth configuring deliberately. Set:

network:
  disablePortMirroring: false

and a container's published ports appear on your Mac's loopback, exactly as they would with Docker Desktop:

$ docker run -d -p 8080:80 nginx:alpine
$ curl http://localhost:8080/   # 200 — no port-forward, no VM IP

Lima's hostagent watches for listening ports inside the VM and mirrors each one to 127.0.0.1 on the host as it appears. You publish a port the way you always have, and it lands where you expect.

🔌

Why this is not the default. marina defaults to disablePortMirroring: true so it can coexist with other Lima-based tools that manage kind clusters — Rancher Desktop, kind-on-lima, Colima. Two VMs both mirroring an API server on port 7001 to 127.0.0.1 break each other. If marina is your only Lima VM, or you are using it primarily as a Docker host, turning mirroring on is the friendlier choice.

The trade-off is small and worth knowing: with mirroring on, cluster kubeconfigs use 127.0.0.1, which is stable across VM restarts. With it off, they use the VM's lima0 IP, which macOS assigns and can change — so kubeconfigs need refreshing after a restart. Turning mirroring on actually removes that chore.

⚠

This is a VM-level setting: it applies when the instance is created. Changing it needs marina destroy && marina up, which deletes your clusters. Decide before you build a lab on it.

Bind mounts

The daemon runs inside the VM and resolves -v sources there. A path your Mac has but the VM does not will not fail — the guest creates an empty directory and the container sees nothing in it. Share the directory first:

vm:
  mounts:
    - location: "~/projects"
      writable: true

Each entry appears at the same absolute path inside the VM, which is what makes docker run -v ~/projects/app:/app resolve. See Sharing host directories.

Registry mirrors and the docker CLI

marina runs pull-through caches for Docker Hub, quay.io, gcr.io and Artifact Registry, and all of them serve the docker CLI, not just cluster pulls. A docker pull or docker compose up is answered from the local cache on the second fetch, and Docker Hub rate limits stop biting.

Cluster pulls and CLI pulls reach the caches through separate config trees, because two different daemons are involved:

ConsumerReadsWritten by
docker pull / docker compose/etc/docker/certs.d/<host>/hosts.tomlmarina up, in the VM
kind cluster nodes/etc/containerd/certs.d/<host>/hosts.tomlmarina cluster create, per node

Both use containerd's hosts.toml format — dockerd calls containerd's loader for it — but neither daemon reads the other's directory. Despite the name, certs.d is a general per-registry configuration directory: it held only TLS material when Docker introduced it, and hosts.toml was added to the same layout later to declare mirrors.

Check what a pull will do:

$ marina shell sudo ls /etc/docker/certs.d
docker.io
gcr.io
quay.io
us-central1-docker.pkg.dev
us-docker.pkg.dev
🧩

This needs Docker's containerd image store, the default since Docker 28 and what the marina VM uses. On the older graphdriver store, dockerd resolves registries with its own client and can only mirror Docker Hub — so marina also writes a Hub mirror into daemon.json, which covers that case.

A mirror is only ever an accelerator: hosts.toml records the real registry as a fallback, so if a cache is down the pull goes direct rather than failing.

⚠️

If you write these files yourself, the endpoint must be 127.0.0.1:<port>, not the mirror's container name. dockerd runs in the VM's own network namespace, where registry-dockerio does not resolve — and a mirror dockerd cannot reach is skipped silently, so pulls keep working while nothing is ever cached.

What you get over a bare Docker daemon

FeatureWhat it means here
Pull-through registry mirrorsDocker Hub, quay.io, gcr.io and Artifact Registry are cached locally, for the docker CLI as well as for clusters. Re-pulls are local and Docker Hub rate limits stop biting — see above.
Persistent image storevm.imageDisk keeps images on their own disk, so marina destroy does not throw away your image cache.
Rosetta for amd64vm.rosetta: true runs amd64 images on Apple Silicon.
Proxy and CA supportnetwork.proxy and vm.caCerts make it work on a corporate network — see Behind a corporate proxy.
Kubernetes when you want itThe same daemon hosts kind clusters. A Docker host today, a multi-cluster lab tomorrow, no second VM.

Alongside Docker Desktop, OrbStack or Colima

They can all be installed at once — each provides its own Docker context, and you switch between them:

$ docker context ls
NAME              DESCRIPTION                       DOCKER ENDPOINT
default           Current DOCKER_HOST based ...     unix:///var/run/docker.sock
marina *                                            unix:///Users/you/.marina.docker.sock

What they cannot all do at once is publish to the same port on 127.0.0.1. If you run marina with disablePortMirroring: false next to another tool, give your containers distinct host ports.

🧭

If you want a Docker daemon and nothing else, OrbStack or Colima start faster and are built for that. marina earns its place when you also want kind clusters with routable LoadBalancer IPs — see marina vs. other tools.

See also