Draft
Conversation
Define explicit base classes (DaemonBackend, ContainerBackend, ComposeBackend, EngineBackend) that any container engine backend must implement. Extracted from existing Docker/Dockerode code. Includes all 14 Engine facade methods, router dispatch documentation, abstract class guards, and comprehensive JSDoc contracts. Part of the containerd/nerdctl engine initiative.
Create DockerDaemon, DockerContainer, DockerCompose classes that implement the backend interfaces by delegating to existing LandoDaemon, Landerode, and compose.js code. Uses getter/setter proxying for live property access. No existing files modified - full backward compatibility. Part of the containerd/nerdctl engine initiative.
ContainerdDaemon manages Lando's own isolated containerd + buildkitd instances. Handles lifecycle (up/down/isUp), PID management, socket health checks, stderr logging, and elevated (sudo) starts with PID discovery. Platform support: Linux/WSL native, macOS/Windows stubbed with helpful errors pending Lima VM integration. Part of the containerd/nerdctl engine initiative.
ContainerdContainer implements ContainerBackend by shelling out to nerdctl for all container operations. Includes JSONL parsing, label normalization (handles commas in values), proxy objects for getContainer/getNetwork, and the full Lando container filtering pipeline from Landerode.list(). Part of the containerd/nerdctl engine initiative.
NerdctlCompose extends ComposeBackend by delegating to the existing compose.js command builder and prepending nerdctl --address <socket> compose to every command array. Zero duplicated logic — just a thin transform layer. Part of the containerd/nerdctl engine initiative.
BackendManager factory creates the right Engine based on config.engine setting (auto | docker | containerd). Auto-detection prefers containerd if all binaries exist, falls back to Docker. New config defaults: engine, containerdBin, nerdctlBin, buildkitdBin, containerdSocket. All non-breaking (engine defaults to auto, overrides default to null). setup-engine-containerd.js provides standalone containerd wiring. Existing setup-engine.js and lando.js untouched. Part of the containerd/nerdctl engine initiative.
Utility modules for locating and downloading containerd stack binaries: - get-containerd-x.js, get-nerdctl-x.js, get-buildkit-x.js (binary resolution) - get-containerd-download-url.js (GitHub release URL construction) - setup-containerd-binaries.js (download + install missing binaries) Follows existing get-docker-x.js patterns. Supports linux/darwin, amd64/arm64. Part of the containerd/nerdctl engine initiative.
75 unit tests covering BackendManager, NerdctlCompose, ContainerdContainer (including parseLabels comma-in-value fix), and download URL generation. All passing. Documentation for the new engine config option at docs/config/engine.md. Part of the containerd/nerdctl engine initiative.
Replace setup-engine.js call with BackendManager.createEngine() in bootstrapEngine(). Engine selection now driven by config.engine setting (auto | docker | containerd). Old setup-engine.js call kept as commented reference. BackendManager exposed as lando.backendManager for plugins. Part of the containerd/nerdctl engine initiative.
Setup hook downloads containerd, buildkitd, and nerdctl from GitHub releases during 'lando setup'. Skips when engine=docker. Check hook warns when engine=containerd but binaries are missing. Part of the containerd/nerdctl engine initiative.
LimaManager class handles Lima VM lifecycle (create/start/stop/exec) for running containerd on macOS. ContainerdDaemon now creates and manages a Lima VM on darwin instead of throwing 'not implemented'. Exposes containerd socket at ~/.lima/lando/sock/containerd.sock. Part of the containerd/nerdctl engine initiative.
Engine.getCompatibility() now handles both Docker and containerd version formats. Adds supportedContainerdVersions config, engineBackend property, and containerd-aware dockerInstalled/ composeInstalled logic. Part of the containerd/nerdctl engine initiative.
Wire lando-setup-containerd-engine into pre-setup event and lando-setup-containerd-engine-check into pre-engine-autostart. Part of the containerd/nerdctl engine initiative.
31 test cases covering BackendManager, ContainerdDaemon lifecycle, ContainerdContainer operations, NerdctlCompose command generation, and full engine lifecycle. Tests requiring real containerd auto-skip. Part of the containerd/nerdctl engine initiative.
app-check-containerd-compat.js validates containerd/nerdctl/buildkit versions and reports warnings. lando-get-containerd-compat.js runs Engine.getCompatibility() for containerd backends. Part of the containerd/nerdctl engine initiative.
WslHelper handles WSL-specific concerns: custom containerd config to avoid Docker Desktop conflicts, socket permission management, and CRI plugin disabling. ContainerdDaemon auto-detects WSL and writes config before starting containerd. Part of the containerd/nerdctl engine initiative.
Register lando-get-containerd-compat in index.js and app-check-containerd-compat in app.js. Confirm engine.docker proxy works with ContainerdContainer (same interface, no gap). Part of the containerd/nerdctl engine initiative.
Populates lando.versions with containerd/nerdctl/buildkit versions when running on the containerd backend. Part of the containerd/nerdctl engine initiative.
Standalone bash script that exercises the full containerd engine path: start containerd, start buildkitd, compose up nginx, verify, cleanup. Part of the containerd/nerdctl engine initiative.
- Add explicit this.containerd property on ContainerdDaemon - Add engine, binary paths, and supportedContainerdVersions to index.js defaults for discoverability - Remove hardcoded fallback from Engine constructor Part of the containerd/nerdctl engine initiative.
✅ Deploy Preview for lando-core ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Member
Author
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for all 4 issues found in the latest run.
- ✅ Fixed: Compose wrapper missing default for
optsparameter- Added
|| {}default to datum.opts in both Docker and containerd compose wrappers to match original setup-engine.js behavior.
- Added
- ✅ Fixed: Warning function ignores version info argument
- Updated update-nerdctl-warning to accept and display version, update, and link parameters in the warning message.
- ✅ Fixed: Unbounded recursion in container list retry logic
- Added _retryCount parameter with a limit of 10 retries to prevent stack overflow in containerd-container list method.
- ✅ Fixed: Containerd
getVersionsreturnsfalsecausingsemver.cleancrash- Added filtering to remove false and 'skip' values from containerd versions before processing in getCompatibility.
Or push these changes by commenting:
@cursor push abd2ef0b38
Preview (abd2ef0b38)
diff --git a/lib/backend-manager.js b/lib/backend-manager.js
--- a/lib/backend-manager.js
+++ b/lib/backend-manager.js
@@ -54,7 +54,7 @@
* Returns a fully wired `Engine` instance ready for use by `lando.engine`.
*
* @param {string} [id='lando'] - The Lando instance identifier.
- * @returns {Engine} A configured Engine instance.
+ * @return {Engine} A configured Engine instance.
*/
createEngine(id = 'lando') {
const engineType = this.config.engine || 'auto';
@@ -80,7 +80,7 @@
* - Returns `new Engine(daemon, docker, compose, config)`
*
* @param {string} id - The Lando instance identifier.
- * @returns {Engine} A Docker-backed Engine instance.
+ * @return {Engine} A Docker-backed Engine instance.
* @private
*/
_createDockerEngine(id) {
@@ -104,7 +104,7 @@
);
const compose = (cmd, datum) => {
- const run = dockerCompose[cmd](datum.compose, datum.project, datum.opts);
+ const run = dockerCompose[cmd](datum.compose, datum.project, datum.opts || {});
return this.shell.sh([orchestratorBin].concat(run.cmd), run.opts);
};
@@ -124,7 +124,7 @@
* `{cmd, opts}` shell descriptor, then executes via `shell.sh([nerdctlBin, ...cmd], opts)`.
*
* @param {string} id - The Lando instance identifier.
- * @returns {Engine} A containerd-backed Engine instance.
+ * @return {Engine} A containerd-backed Engine instance.
* @private
*/
_createContainerdEngine(id) {
@@ -171,7 +171,7 @@
// as the Docker path. Gets {cmd, opts} from NerdctlCompose, then executes
// via shell.sh([nerdctlBin, ...cmd], opts).
const compose = (cmd, datum) => {
- const run = nerdctlCompose[cmd](datum.compose, datum.project, datum.opts);
+ const run = nerdctlCompose[cmd](datum.compose, datum.project, datum.opts || {});
return this.shell.sh([nerdctlBin].concat(run.cmd), run.opts);
};
@@ -193,7 +193,7 @@
* Logs which engine was selected.
*
* @param {string} id - The Lando instance identifier.
- * @returns {Engine} An Engine instance using the auto-detected backend.
+ * @return {Engine} An Engine instance using the auto-detected backend.
* @private
*/
_createAutoEngine(id) {
diff --git a/lib/backends/containerd/containerd-container.js b/lib/backends/containerd/containerd-container.js
--- a/lib/backends/containerd/containerd-container.js
+++ b/lib/backends/containerd/containerd-container.js
@@ -15,7 +15,7 @@
* Helper to determine if any file exists in an array of files.
*
* @param {Array<string>} files - Array of file paths to check.
- * @returns {boolean}
+ * @return {boolean}
* @private
*/
const srcExists = (files = []) => _.reduce(files, (exists, file) => fs.existsSync(file) || exists, false);
@@ -33,7 +33,7 @@
* - Labels whose values contain `,` within values that also contain `=`
*
* @param {string|Object} labels - Labels string from nerdctl or object from inspect.
- * @returns {Object} Docker-compatible labels object.
+ * @return {Object} Docker-compatible labels object.
* @private
*/
const parseLabels = labels => {
@@ -91,7 +91,7 @@
* - `Status` → status text
*
* @param {Object} nerdctlContainer - A parsed JSON line from `nerdctl ps --format json`.
- * @returns {Object} Docker API-compatible container object.
+ * @return {Object} Docker API-compatible container object.
* @private
*/
const normalizeContainer = nerdctlContainer => {
@@ -164,7 +164,7 @@
* "No such container", "no such object", "not found".
*
* @param {Error} err - The error to inspect.
- * @returns {boolean} `true` if the error indicates a missing resource.
+ * @return {boolean} `true` if the error indicates a missing resource.
* @private
*/
_isNotFoundError(err) {
@@ -184,7 +184,7 @@
* @param {Array<string>} args - nerdctl subcommand and arguments.
* @param {Object} [opts={}] - Additional options passed to `run-command`.
* @param {boolean} [opts.ignoreReturnCode=false] - Whether to suppress non-zero exit errors.
- * @returns {Promise<string>} The trimmed stdout from the command.
+ * @return {Promise<string>} The trimmed stdout from the command.
* @throws {Error} If the command exits non-zero and `ignoreReturnCode` is false.
* @private
*/
@@ -216,7 +216,7 @@
*
* @param {string} name - The name of the network to create.
* @param {Object} [opts={}] - Additional network creation options.
- * @returns {Promise<Object>} Network inspect data.
+ * @return {Promise<Object>} Network inspect data.
*/
async createNet(name, opts = {}) {
const args = ['network', 'create'];
@@ -254,7 +254,7 @@
* Docker-compatible JSON.
*
* @param {string} cid - A container identifier (hash, name, or short id).
- * @returns {Promise<Object>} Container inspect data.
+ * @return {Promise<Object>} Container inspect data.
* @throws {Error} If the container does not exist.
*/
async scan(cid) {
@@ -270,7 +270,7 @@
* to prevent race conditions when containers are removed between checks.
*
* @param {string} cid - A container identifier.
- * @returns {Promise<boolean>}
+ * @return {Promise<boolean>}
*/
async isRunning(cid) {
try {
@@ -303,9 +303,10 @@
* @param {string} [options.project] - Filter to a specific project name.
* @param {Array<string>} [options.filter] - Additional `key=value` filters.
* @param {string} [separator='_'] - Container name separator.
- * @returns {Promise<Array<Object>>} Array of Lando container descriptors.
+ * @param {number} [_retryCount=0] - Internal retry counter to prevent unbounded recursion.
+ * @return {Promise<Array<Object>>} Array of Lando container descriptors.
*/
- async list(options = {}, separator = '_') {
+ async list(options = {}, separator = '_', _retryCount = 0) {
// Get raw container list from nerdctl (JSONL: one JSON object per line)
let rawOutput;
try {
@@ -380,7 +381,10 @@
// If any container has been up for only a brief moment, retry
// (matches Landerode behavior to avoid transient states)
if (_.find(containers, container => container.status === 'Up Less than a second')) {
- return this.list(options, separator);
+ if (_retryCount < 10) {
+ return this.list(options, separator, _retryCount + 1);
+ }
+ this.debug('list retry limit reached, proceeding with transient container states');
}
// Add running status flag
@@ -401,7 +405,7 @@
* @param {Object} [opts={v: true, force: false}] - Removal options.
* @param {boolean} [opts.v=true] - Also remove associated anonymous volumes.
* @param {boolean} [opts.force=false] - Force-remove a running container.
- * @returns {Promise<void>}
+ * @return {Promise<void>}
*/
async remove(cid, opts = {v: true, force: false}) {
const args = ['rm'];
@@ -428,7 +432,7 @@
*
* @param {string} cid - A container identifier.
* @param {Object} [opts={}] - Stop options (e.g. `{t: 10}` for timeout in seconds).
- * @returns {Promise<void>}
+ * @return {Promise<void>}
*/
async stop(cid, opts = {}) {
const args = ['stop'];
@@ -458,7 +462,7 @@
* handle interface.
*
* @param {string} id - The network id or name.
- * @returns {Object} A network handle with `inspect()` and `remove()` methods.
+ * @return {Object} A network handle with `inspect()` and `remove()` methods.
*/
getNetwork(id) {
return {
@@ -467,7 +471,7 @@
/**
* Inspect the network and return its metadata.
- * @returns {Promise<Object>} Network inspect data.
+ * @return {Promise<Object>} Network inspect data.
*/
inspect: async () => {
const data = await this._nerdctl(['network', 'inspect', id]);
@@ -477,7 +481,7 @@
/**
* Remove the network.
- * @returns {Promise<void>}
+ * @return {Promise<void>}
*/
remove: async () => {
try {
@@ -498,7 +502,7 @@
*
* @param {Object} [opts={}] - Filter options.
* @param {Object} [opts.filters] - Filters object (e.g. `{name: ['mynet']}` or `{id: ['abc']}`).
- * @returns {Promise<Array<Object>>} Array of network objects.
+ * @return {Promise<Array<Object>>} Array of network objects.
*/
async listNetworks(opts = {}) {
let rawOutput;
@@ -565,7 +569,7 @@
* Dockerode Container handle interface.
*
* @param {string} cid - The container id or name.
- * @returns {Object} A container handle with `inspect()`, `remove()`, and `stop()` methods.
+ * @return {Object} A container handle with `inspect()`, `remove()`, and `stop()` methods.
*/
getContainer(cid) {
return {
@@ -574,21 +578,21 @@
/**
* Inspect the container and return its metadata.
- * @returns {Promise<Object>} Container inspect data.
+ * @return {Promise<Object>} Container inspect data.
*/
inspect: () => this.scan(cid),
/**
* Remove the container.
* @param {Object} [opts] - Removal options.
- * @returns {Promise<void>}
+ * @return {Promise<void>}
*/
remove: opts => this.remove(cid, opts),
/**
* Stop the container.
* @param {Object} [opts] - Stop options.
- * @returns {Promise<void>}
+ * @return {Promise<void>}
*/
stop: opts => this.stop(cid, opts),
};
diff --git a/lib/engine.js b/lib/engine.js
--- a/lib/engine.js
+++ b/lib/engine.js
@@ -194,7 +194,7 @@
const semver = require('semver');
// helper to normalize a supported versions object into comparison-ready format
- const normalize = (sv) => _(sv)
+ const normalize = sv => _(sv)
.map((data, name) => _.merge({}, data, {name}))
.map(data => ([data.name, {
satisfies: data.satisfies || `${data.min} - ${data.max}`,
@@ -207,12 +207,19 @@
return this.daemon.getVersions().then(versions => {
// Detect containerd backend: versions have containerd key instead of desktop/engine
- const isContainerd = versions.hasOwnProperty('containerd');
+ const isContainerd = Object.prototype.hasOwnProperty.call(versions, 'containerd');
let normalizedVersions;
if (isContainerd) {
// containerd format: {containerd, buildkit, nerdctl}
normalizedVersions = normalize(this.supportedContainerdVersions);
+
+ // Remove false values (binaries that couldn't be versioned)
+ Object.keys(versions).forEach(key => {
+ if (versions[key] === false || versions[key] === 'skip') {
+ delete versions[key];
+ }
+ });
} else {
// Docker format: {desktop, engine, compose}
normalizedVersions = normalize(supportedVersions);
diff --git a/messages/update-nerdctl-warning.js b/messages/update-nerdctl-warning.js
--- a/messages/update-nerdctl-warning.js
+++ b/messages/update-nerdctl-warning.js
@@ -1,13 +1,14 @@
'use strict';
// checks to see if a setting is disabled
-module.exports = () => ({
+module.exports = ({version, update, link} = {}) => ({
type: 'warning',
title: 'Recommend updating NERDCTL',
detail: [
- 'Looks like you might be falling a bit behind on nerdctl.',
+ `You have version ${version || 'unknown'} but we recommend updating to ${update || 'the latest version'}.`,
'In order to ensure the best stability and support we recommend you update',
'by running the hidden "lando setup" command.',
],
command: 'lando setup --skip-common-plugins',
+ url: link,
});…ing, prevent recursion, filter false versions
New hook downloads Lima, creates a containerd-enabled VM during 'lando setup' on macOS. Platform-guarded in index.js. Also fixes make-executable calls with absolute paths in both darwin and containerd setup hooks, corrects Lima arch mapping (arm64 → aarch64), and uses --plain for non-interactive VM creation. Part of the containerd/nerdctl engine initiative.
Create utils/get-containerd-config.js for TOML config generation on all platforms. Replaces WSL-specific config in wsl-helper.js. Fixes: state/root as top-level scalars (not TOML tables), disabled_plugins array for CRI, debug flag always-truthy bug. 17 new tests for config generation. Part of the containerd/nerdctl engine initiative.
Create utils/get-buildkit-config.js for BuildKit TOML config generation. Containerd worker with GC policy (reservedSpace), parallelism from CPU count, optional registry mirrors and debug mode. ContainerdDaemon now generates buildkit config, passes --config to buildkitd, and exposes pruneBuildCache() via buildctl. 21 new tests. Config uses correct BuildKit field names per current docs. Part of the containerd/nerdctl engine initiative.
- Add utils/setup-containerd-auth.js for Docker config path resolution, credential helper detection, and DOCKER_CONFIG env injection - Update NerdctlCompose._transform() to inject auth env into commands - Update ContainerdContainer._nerdctl() to pass auth env to nerdctl - Add registryAuth config option for custom Docker config paths - 23 new tests for auth config resolution and credential helpers
- Remove --internal flag from createNet (nerdctl doesn't support it) - Set daemon.compose = nerdctlBin so Engine.composeInstalled works - Add /usr/sbin:/sbin to compose function PATH for CNI plugins - Add TODO for v4 image builds still using Docker buildx - Update networking tests for --internal removal
The reset hook checks lando.config.orchestratorBin — when it points to docker-compose and the engine is containerd, composeInstalled is false and the hook replaces the entire engine with Docker's setup-engine. Fix: update lando.config.orchestratorBin to nerdctlBin when engine is containerd, so the reset hook sees compose as installed.
The app-reset-orchestrator hook replaces the engine with Docker's setup-engine when composeInstalled is false. Skip this for containerd since it manages its own compose backend via nerdctl.
app.js was always creating a fresh Docker engine via setup-engine.js, bypassing the BackendManager entirely. Now uses lando.engine when available (which contains the containerd compose function).
On WSL, ~/.docker/config.json may have credsStore: 'desktop.exe' from Docker Desktop for Windows, but the helper binary doesn't exist in WSL. This causes nerdctl to fail with 'unable to retrieve credentials'. Fix: detect missing cred helper at auth config time, create a sanitized config at ~/.lando/docker-config/ without the broken credsStore, and set DOCKER_CONFIG to point there.
nerdctl treats credential helper errors as fatal (unlike Docker which falls back to anonymous). Always remove credsStore from Docker config for nerdctl, creating a sanitized copy at ~/.lando/docker-config/. Also pass authConfig to NerdctlCompose so DOCKER_CONFIG propagates to nerdctl compose subprocesses.
Rootless nerdctl can't auto-allocate host ports. Add port rewriting that finds free ports and replaces bare specs like '127.0.0.1::80' with '127.0.0.1:FREE_PORT:80' in compose files before nerdctl runs. - utils/allocate-ports.js for port scanning and rewriting - backend-manager.js pre-processes compose files in rootless mode - 15 new tests for port allocation
Lando's router.js uses Bluebird-specific methods (.each, .tap, .map) on return values from docker.list(), docker.isRunning(), etc. Our async/await methods return native Promises. Wrap with a Proxy that converts all Promise returns to Bluebird.
Replace rootless containerd (UID namespace issues) with rootful: - Root-owned binaries at /usr/local/lib/lando/bin/ (containerd, shim, buildkitd, buildctl, runc) — prevents privilege escalation - User-owned nerdctl stays at ~/.lando/bin/ - lando-containerd.service systemd unit runs containerd as root - 'lando' group gets socket access (like docker group) - lando setup handles sudo, group creation, service install - Remove all rootless code (useRootless, _startRootless, port allocation rewriting, rootless detection)
nerdctl v2 refuses to work as non-root even with --address pointing to a rootful socket. Instead, use docker-compose (already installed by Lando) with DOCKER_HOST pointing to finch-daemon's Docker API socket. finch-daemon translates to containerd. Architecture: docker-compose → finch-daemon → containerd (rootful) - Replace NerdctlCompose with lib/compose.js (same as Docker path) - Set DOCKER_HOST=unix://~/.lando/run/finch.sock in compose env - Use existing docker-compose binary as orchestratorBin - NerdctlCompose class retained but no longer used for compose ops
Move containerd/finch sockets from ~/.lando/run/ (user-controlled) to /run/lando/ (root-owned) to prevent symlink attacks. Same pattern as Docker using /var/run/docker.sock. - Systemd RuntimeDirectory=lando creates /run/lando/ automatically - ExecStartPost sets lando group permissions on sockets - PID files stay in ~/.lando/run/ (user-level)
- Download finch-daemon binary during lando setup (root-owned) - Add ExecStartPost to systemd service that launches finch-daemon alongside containerd, with socket at /run/lando/finch.sock - Service task depends on finch-daemon being installed
… in status checks
- Replace daemon.up() with passive daemon.isUp() in hasRun() for build engine
hooks (linux, darwin, win32) and landonet hook. These were triggering full
retry loops with socket polling just to check installation status.
- WSL remains the exception: docker binaries only appear after Docker Desktop
starts on Windows, so a minimal daemon.up({max:1}) is still needed there.
- Short-circuit containerd up() and _waitForSocket() early if required binaries
(containerd, nerdctl) don't exist, avoiding futile retry loops on fresh installs.
- Fix double parse-setup-task() mutation: getSetupStatus() now extracts task
fields with inline defaults instead of calling parse-setup-task(), which
mutates/wraps the task object. setup() remains the sole caller.
The Docker build engine setup hooks run unconditionally, causing lando setup to try installing Docker even when engine=containerd. Add guard to skip Docker install on all platforms when containerd is the selected engine.
nerdctl refuses to work as non-root with rootful containerd. Replace
all nerdctl shell-outs with Dockerode API calls via finch-daemon socket.
- ContainerdContainer now uses Dockerode({socketPath: finchSocket})
- list(), scan(), isRunning(), remove(), stop() use Docker API
- createNet(), listNetworks() use Docker API
- getContainer/getNetwork proxies wrap Dockerode objects
- No more nerdctl dependency for any runtime operation
- nerdctl binary no longer required (only kept for version checks)
Architecture: Dockerode → finch-daemon socket → containerd (rootful)
- Landonet setup now depends on setup-containerd-service when engine is containerd (was depending on setup-build-engine/Docker) - Fix orchestratorBin to point to docker-compose (not nerdctl) when containerd engine is active - Skip Docker Desktop binary check for containerd in landonet hasRun
The old service was 'enabled' but didn't have finch-daemon or /run/lando/ paths. hasRun returned true, skipping the service update. Now also verifies both sockets exist at /run/lando/ before considering done.
When the service is already running with old config, 'systemctl start' is a no-op. Need 'restart' to pick up the new service file with finch-daemon and /run/lando/ socket paths.
finch-daemon adds unix:// internally. Passing unix:///run/lando/finch.sock causes it to try 'unix://unix:///run/lando/finch.sock'. Just pass the bare path.
finch-daemon/nerdctl needs /etc/cni/net.d/ for network lock files and /opt/cni/bin/ for CNI plugins. Create them before containerd starts.
Users shouldn't need manual commands. Create /etc/cni/net.d and /opt/cni/bin during the setup task with sudo, before restarting the service.
finch-daemon uses nerdctl internally which defaults to /run/containerd/ containerd.sock. Need to point it at our socket /run/lando/containerd.sock via CONTAINERD_ADDRESS env var.
finch-daemon internally uses nerdctl which looks for containerd at /run/containerd/containerd.sock. Symlink our socket there so finch-daemon finds it without custom config.
isUp() and _healthCheck() used nerdctl ps which fails with rootless error. Replace with Dockerode.ping() against finch-daemon socket — same pattern as all other container ops.
Remove the /run/containerd/containerd.sock symlink hack. Instead, write a nerdctl.toml config pointing to our socket and set NERDCTL_TOML env var when starting finch-daemon. No conflict with system containerd.
finch-daemon expects CNI plugins at /opt/cni/bin/ but Ubuntu installs them at /usr/lib/cni/. Symlink in ExecStartPre. Also add cni_path to nerdctl.toml config.
…pping docker-compose on WSL detects Docker Desktop and translates bind mount paths through /run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/. Set DOCKER_CONTEXT=default to prevent this when using containerd.
containerd needs runc in PATH. Our runc is at /usr/local/lib/lando/bin/. Add Environment=PATH with our bin dir to the systemd service.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Adds containerd + nerdctl + BuildKit as an alternative container engine backend. Lando can run independently of Docker with
engine: "containerd"in config.What
DaemonBackend,ContainerBackend,ComposeBackendinterfacesContainerdDaemon(containerd + buildkitd + finch-daemon lifecycle),ContainerdContainer(nerdctl ops),NerdctlCompose(compose adapter)lando setupprompts for engine selection, downloads binaries from GitHubConfig
Stats
72 files changed, ~10,000 lines added. 467 tests passing. No existing behavior changed — Docker path works exactly as before.
Not yet done
auto→ Docker)engine: containerdto exercise the backend end-to-endNote
High Risk
Introduces a new container engine backend (containerd/nerdctl) and changes engine bootstrap/setup paths, which can impact core lifecycle, orchestration, and proxy behavior across platforms. Risk is mitigated by preserving the existing Docker codepath but the surface area is large and includes new daemon/process management.
Overview
Adds an experimental
containerdengine option (and defaultengine: auto) that lets Lando run viacontainerd + buildkitd + nerdctlinstead of Docker, including engine selection duringlando setupand containerd-specific compatibility/health checks.Replaces
utils/setup-engine.jsbootstrapping with a newBackendManagerthat instantiates either the Docker-backed engine or a new containerd-backed engine; the containerd path manages its own daemons, usesnerdctl composefor orchestration, and introduces a Docker-API compatibility socket (finch-daemon) to keep the Traefik proxy working.Extends CI to run core Leia tests against both
dockerandcontainerd, addsenginedocumentation and developer benchmarking tooling (scripts/benchmark-engines.sh).Written by Cursor Bugbot for commit 9f53c2d. This will update automatically on new commits. Configure here.