Virtual Machine Drivers
| ⚡ Requirement | Lima >= 2.0 |
|---|
Lima supports two types of drivers: internal and external. This architecture allows for extensibility and platform-specific implementations. Drivers are unware whether they are internal or external.
💡 See also: VM Types for user configuration of different virtualization backends.
Internal vs External Drivers
Internal Drivers are compiled directly into the limactl binary and are registered automatically at startup by passing the driver object into registry.Register() function and importing the package in the main limactl code using Go’s blank import _. For example:
- qemu: registration file & import file
Build tags control which drivers are compiled as internal vs external (e.g., external_qemu, external_vz, external_wsl2).
External Drivers are separate executables that communicate with Lima via gRPC. They are discovered at runtime from configured directories.
⚠️ Note: External drivers are experimental and the API may change in future releases.
Building Drivers as External
You can build existing internal drivers as external drivers using the ADDITIONAL_DRIVERS Makefile variable:
# Build QEMU as external driver
make ADDITIONAL_DRIVERS=qemu limactl additional-drivers
# Build multiple drivers as external
make ADDITIONAL_DRIVERS="qemu vz wsl2" limactl additional-drivers
This creates external driver binaries in _output/libexec/lima/ with the naming pattern lima-driver-<name> (or lima-driver-<name>.exe on Windows).
Driver Discovery
Lima discovers external drivers from these locations:
- Custom directories: Set path to the external driver’s directory via
LIMA_DRIVERS_PATHenvironment variable - Standard directory:
<LIMA-PREFIX>/libexec/lima/, where<LIMA_PREFIX>is the location path where the Lima binary is present
The discovery process is handled by pkg/registry/registry.go.
Creating Custom External Drivers
To create a new external driver:
- Implement the interface: Your driver must implement the
driver.Driverinterface:
type Driver interface {
Lifecycle
GUI
SnapshotManager
GuestAgent
Info(ctx context.Context) Info
Configure(ctx context.Context, inst *limatype.Instance) (*ConfiguredDriver, error)
SSHAddress(ctx context.Context) (string, error)
AdditionalSetupForSSH(ctx context.Context) error
}
The gRPC transport that carries these methods to an external driver is defined
in driver.proto,
whose per-RPC doc comments are the authoritative contract (call ordering,
idempotency and per-RPC semantics) for driver authors. The Go side is
documented as godoc on the driver.Driver
interface.
- Create main.go: Use
server.Serve()to expose your driver:
package main
import (
"context"
"github.com/lima-vm/lima/v2/pkg/driver/external/server"
)
func main() {
driver := &MyDriver{}
server.Serve(context.Background(), driver)
}
Build and deploy:
- Build your driver:
go build -o lima-driver-mydriver main.go - Place the binary in a directory accessible via
LIMA_DRIVERS_PATH - Ensure the binary is executable
- Build your driver:
Use the driver: Explicitly specify the driver when creating instances:
limactl create myinstance --vm-type=mydriver template:default
Driver lifecycle & call ordering
Drivers are unaware whether they are internal or external, but external driver
authors need to know when each method is invoked. The sequence below is the
instance start path: instance.Prepare (in pkg/instance) runs steps 1-4,
then the hostagent (pkg/hostagent) boots the VM and runs steps 5-7:
Configure- sets the instance configuration on the driver. It must run before any method that depends on the config.Validate- rejects a config the driver cannot support. Returning an error aborts the start.Create- first-time provisioning (e.g. writing a driver identifier file). It must be a no-op (succeed) when the instance already exists, and it must not create the disks.CreateDisk- creates the instance disk(s).Start- begins booting the VM. It is streaming and returns as soon as boot has been initiated (the initial “started” message), not once the guest has finished booting; it then streams any runtime errors, then a final “done” (see theStartResponsemessage indriver.proto).AdditionalSetupForSSH- runs right afterStartreturns (boot initiated, the guest is not necessarily up yet), before the first SSH connection.SSHAddress- re-queried afterStartonly whenInforeportsFeatures.dynamicSSHAddress(e.g. WSL2, whose address is not known until the VM is up).
The guest agent is reached in one of two ways, chosen by what
ForwardGuestAgent returns. When it returns true, the host agent forwards
the guest-agent socket over SSH and GuestAgentConn returns nil. When it
returns false, the driver provides a direct guest-agent connection (e.g.
vsock/virtio) through GuestAgentConn.
These start-path methods are invoked sequentially in the order above. Creating an
instance without starting it (limactl create) only runs Configure and
Create. Once the VM is running, the host agent polls read-only methods (notably
Info and ForwardGuestAgent) from background goroutines, so a driver’s
gRPC server must be safe to handle concurrent calls.
For the full per-method contract, see the doc comments in
driver.proto
and the driver.Driver godoc.
Examples
See existing external driver implementations: