Skip to content

feat(engine): add engineProvider to run Lando on OrbStack, Rancher Desktop or Podman (macOS) - #510

Draft
jordan8037310 wants to merge 3 commits into
lando:mainfrom
jordan8037310:feat/engine-provider-upstream
Draft

jordan8037310 wants to merge 3 commits into
lando:mainfrom
jordan8037310:feat/engine-provider-upstream

Conversation

@jordan8037310

Copy link
Copy Markdown

Refs lando/lando#3388, #138. Builds on #508 and #509, so please review those first.

Lando 3 on macOS assumes Docker Desktop in several places:

  • it reads Docker.app's plist for the version
  • it starts the engine with docker desktop start or open /Applications/Docker.app
  • lando setup installs Docker Desktop
  • the landonet setup task requires Docker.app

The current OrbStack guide works around this with sudo ln -s /Applications/OrbStack.app /Applications/Docker.app.

This PR adds one small seam: an engine provider.

# ~/.lando/config.yml  (or LANDO_ENGINE_PROVIDER=orbstack)
engineProvider: orbstack   # auto (default) | docker-desktop | orbstack | rancher

Behavior

Provider Socket Start command
docker-desktop /var/run/docker.sock unchanged
orbstack ~/.orbstack/run/docker.sock orbctl start
rancher (dockerd/moby mode) ~/.rd/docker.sock rdctl start
podman (opt-in only, rootful machine) $TMPDIR/podman/podman-machine-default-api.sock podman machine start
  • Podman needs two small, generic provider flags:

    • unprivilegedPorts: Podman's port forwarder (gvproxy) runs without root, and macOS refuses ports below 1024 on 127.0.0.1 to non-root processes. The proxy therefore stays on loopback and uses 8000/4433 instead of falling back to binding every interface.
    • selinux: the Podman VM enforces SELinux, so the proxy's docker socket mount gets label=disable. Without it, Traefik is denied the socket and serves 404 for every app.
  • auto never picks Podman, because having the CLI installed doesn't mean a machine exists.

  • auto is the default and keeps today's behavior: Docker Desktop whenever /Applications/Docker.app exists. Otherwise it picks OrbStack, then Rancher Desktop. Existing users see no change.

  • An explicit provider supplies the socket to dockerode and, via fix(engine): use the configured socket for docker and docker compose too #509, to docker and docker compose as DOCKER_HOST.

  • The daemon starts the provider with its own CLI. open -a is not enough, because once the engine has been stopped (orbctl stop), re-opening the menu bar app does not restart it.

  • No Docker Desktop version check or update warning on other providers. They report desktop: 'skip'.

  • lando setup skips the Docker Desktop install on other providers.

  • The landonet setup task passes the provider to the daemon it creates. Before, that daemon fell back to auto, picked Docker Desktop, and relaunched it.

  • Linux, Windows and WSL are untouched.

Validation

  • New and updated specs:
    • test/get-engine-provider.spec.js (6 cases): auto resolution order, explicit providers, unknown provider error.
    • test/daemon.spec.js:
      • up() runs orbctl start for OrbStack
      • getVersions() returns skip on other providers without reading the plist
    • test/lando-setup-build-engine-darwin.spec.js: the install task exists for Docker Desktop, and is absent for OrbStack and Rancher.
    • test/lando-setup-landonet.spec.js: the setup daemon receives the configured provider. Mutation-checked: it fails on main.
  • npm test on Node 20: lint clean, all unit specs pass.

Manual validation (macOS 26, Apple Silicon)

Docker Desktop–free run, OrbStack 2.2.3:

  1. Quit Docker Desktop. Stop the engine with orbctl stop.
  2. In a drupal10 recipe app, run lando start with engineProvider: orbstack.
  3. Expected and observed:
    • Lando runs orbctl start itself.
    • The app is up in about 20 s.
    • Docker Desktop is never relaunched.
    • https://<app>.lndo.site returns 200.
    • lando drush status bootstraps.
  4. lando db-export / lando db-import round trip works.
  5. UID mapping, writable /app, host.lando.internal and SSH keys all work. OrbStack also provides /run/host-services/ssh-auth.sock.
  6. lando poweroff stops everything. The docker context and /var/run/docker.sock are unchanged.

Docker Desktop–free run, Rancher Desktop 1.24.0 (moby, VZ + virtiofs, Administrative Access off):

  1. Quit Docker Desktop. Shut down Rancher Desktop with rdctl shutdown.
  2. lando start with engineProvider: rancher runs rdctl start. The app serves through the proxy in about 25 s.
  3. UID mapping, host.lando.internal and SSH keys work. The /run/host-services/ssh-auth.sock agent socket does not exist on Rancher; that is a known gap, out of scope here.

Docker Desktop–free run, Podman 6.1.4 (rootful, applehv):

  • lando start boots the machine itself with podman machine start (46 s from stopped).
  • The drupal10 recipe serves HTTPS on :4433.
  • Drush, db-export/db-import, UID mapping, host.lando.internal and lando rebuild all pass.

Bind-mount benchmark, OrbStack vs Docker Desktop (same machine, alpine, median of 3; Docker Desktop was also running other containers):

Operation Docker Desktop OrbStack
find + tar of a large node_modules 32.2 s 11.5 s
Write 5,000 small files 6.7 s 1.6 s

Three-engine comparison (M3 Max; round-robin, median of 5, all engines under the same background load):

OrbStack Rancher Podman
Cold engine boot 1.7 s 22.1 s 10.3 s
Bind-mount read 10.8 s 23.8 s 39.6 s
Small-file write 1.7 s 3.5 s 3.4 s

Notes for reviewers

  • Engine side effects outside Lando:
    • On first start, OrbStack switches the docker context to orbstack and credsStore to osxkeychain.
    • Rancher Desktop switches the context to rancher-desktop whenever Docker Desktop's socket is missing.
    • Lando is unaffected because it now pins DOCKER_HOST, but a follow-up docs PR will cover it.
  • Possible follow-ups:
    • a generic provider that reads the active docker context
    • provider-specific SSH agent sockets
    • Colima

🤖 Generated with Claude Code

jordan8037310 and others added 3 commits October 15, 2026 01:32
engineConfig.socketPath (the lando/lando#3388 workaround for non Docker
Desktop engines) only reached dockerode. build-config strips DOCKER_* and
get-engine-config re-exported DOCKER_HOST only for a tcp host, so the
docker and docker compose subprocesses followed the active docker context
and could run against a different engine.

- export DOCKER_HOST=unix://<socketPath> for any non-default socket
- keep the configured engineConfig after first-run autosetup instead of
  rebuilding it from defaults, which reset it to /var/run/docker.sock

Refs lando/lando#3388

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS without /Applications/Docker.app (e.g. OrbStack or Colima only),
getVersions() returned desktop: null and is-compatible-version threw
"Cannot read properties of null (reading 'trim')". Report 'skip', as the
win32 branch already does, guard the semver check in up(), and treat an
unparseable version as incompatible instead of throwing.

Fixes lando#138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sktop or Podman (macOS)

Lando 3 on macOS assumes Docker Desktop: it reads Docker.app's plist for the
version, starts the engine with `docker desktop start` / `open Docker.app`,
installs Docker Desktop during `lando setup`, and requires Docker.app in the
landonet setup task.

Add one seam, utils/get-engine-provider.js, selected by the new
`engineProvider` config (auto | docker-desktop | orbstack | rancher | podman):

- auto (default) keeps today's behavior: Docker Desktop whenever
  /Applications/Docker.app exists, else OrbStack, else Rancher Desktop.
  Podman is opt-in only.
- an explicit provider supplies the socket (dockerode and DOCKER_HOST) and
  the start command (orbctl start / rdctl start / podman machine start);
  re-opening a stopped engine's app with `open -a` is a no-op, so each
  engine's own cli is used
- other providers skip the Docker Desktop version check, install, and the
  landonet Docker.app check; the landonet task's daemon now gets the
  provider instead of falling back to Docker Desktop and relaunching it
- providers can declare unprivilegedPorts (podman's gvproxy cannot bind
  < 1024 on 127.0.0.1 on macOS, so the proxy uses 8000/4433 on loopback)
  and selinux (the podman machine enforces selinux, so the proxy's docker
  socket mount gets label=disable)

Linux, Windows and WSL paths are unchanged.

Refs lando/lando#3388

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 15, 2026

Copy link
Copy Markdown

👷 Deploy request for lando-core pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit f6dcc8f

@github-actions

Copy link
Copy Markdown
Contributor

Privileged Leia tests skipped

The l337 and lando-v4 Leia suites verify authenticated SSH cloning and need the deploy key, so they do not run for fork pull requests.

After reviewing f6dcc8f74b90537e8879cf90961d4c5ca8705fc6, a maintainer can run them against it with:

gh workflow run pr-core-tests.yml --repo lando/core -f pull_request=510 -f commit_sha=f6dcc8f74b90537e8879cf90961d4c5ca8705fc6

or from the workflow page using the same inputs. This comment updates when new commits are pushed and the dispatch only accepts the current head commit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant