Node.js native bindings for LXC (Linux Containers) — a complete, production-ready wrapper around
liblxcbuilt with N-API.
- Full lifecycle control — create, start, stop, reboot, shutdown, destroy
- Async-first — all blocking operations return
Promises backed by a libuv thread pool - Interactive console — non-blocking console sessions with
EventEmitterinterface - Rich configuration API — get, set, clear, and bulk-read container configuration
- Snapshot support — create, list, restore, and destroy snapshots
- CRIU integration — checkpoint, restore, and live-migrate containers
- Mount injection — bind-mount paths into running containers without restart
- Resource metrics — async cgroup stat reads (memory, CPU, block I/O)
- Network management — attach/detach interfaces, query IPs and interfaces
- Device hotplug — add/remove device nodes into running containers
- Seccomp support — retrieve seccomp notification file descriptors
- Memory-safe — no leaks; all heap buffers freed, all async captures by value
- TypeScript — full type declarations included
- Node.js ≥ 12.17
- LXC ≥ 4.0 (runtime library only)
Install LXC on Debian/Ubuntu:
sudo apt install lxcVerify your kernel supports LXC features:
lxc-checkconfignpm install node-lxcPre-built binaries are included for the following platforms — no compilation step required:
| Architecture | GNU triplet |
|---|---|
| x86-64 | x86_64-linux-gnu |
| ARM64 | aarch64-linux-gnu |
Other Linux architectures must be built from source.
import { Container, GetVersion } from "node-lxc";
console.log("LXC", GetVersion()); // e.g. "5.0.2"
// Create and start a container
const c = new Container("my-container");
await c.create({
template: "download",
argv: ["--dist", "ubuntu", "--release", "jammy", "--arch", "amd64"],
});
c.setConfigItem("lxc.net.0.type", "veth");
c.setConfigItem("lxc.net.0.link", "lxcbr0");
c.setConfigItem("lxc.net.0.flags", "up");
await c.start();
console.log(c.state); // "RUNNING"
console.log(c.initPID); // e.g. 12345
// Run a command and get the exit code
const code = await c.exec({ argv: ["/bin/sh", "-c", "echo hello"] });
console.log(code); // 0
await c.shutdown(30);
await c.destroy();import {
GetVersion, // → string
GetGlobalConfigItem, // (key: string) → string
ListAllContainers, // (lxcpath?: string) → string[]
ListAllDefinedContainers, // (lxcpath?: string) → string[]
ListAllActiveContainers, // (lxcpath?: string) → string[]
ConfigItemIsSupported, // (key: string) → boolean
HasApiExtension, // (extension: string) → boolean
GetWaitStates, // () → string[]
} from "node-lxc";const c = new Container(name: string, configPath?: string, alt_file?: string);| Parameter | Description |
|---|---|
name |
Container name |
configPath |
LXC container directory (defaults to lxc.lxcpath) |
alt_file |
Alternate configuration file path |
await c.create({ template?, argv?, bdevtype?, bdev_specs?, flags? });
await c.start(useinit?, argv?);
await c.stop();
await c.reboot(timeout?); // → Promise<boolean>
await c.shutdown(timeout?); // → Promise<boolean>
await c.freeze();
await c.unfreeze();
await c.destroy({ include_snapshots?, force? });
await c.clone(options); // → Promise<Container>c.name // string
c.defined // boolean
c.running // boolean
c.state // ContainerState ("RUNNING" | "STOPPED" | ...)
c.initPID // number (-1 if not running)
c.error // { num: number, string: string | null }
c.daemonize // boolean (get/set)
c.configPath // string (get/set)
c.configFileName // string
c.mayControl() // → boolean
await c.initPIDFd() // → number (pidfd)
await c.devptsFd() // → numberc.getConfigItem(key) // → string | null
c.setConfigItem(key, value)
c.clearConfigItem(key)
c.clearConfig()
c.getRunningConfigItem(key) // → string | null (live, running container)
c.getKeys(prefix?) // → string[]
c.getConfigItems(prefix?) // → Record<string, string | null>
await c.loadConfig(alt_file)
await c.save(alt_file)// Run a command; returns its exit code
await c.exec({ argv: string[], ...lxc_attach_options }) // → number
// Run a command and capture all output
const { exitCode, stdout, stderr } = await c.execOutput({ argv: ["/bin/hostname"] });
// Run a command and stream output as events
const session = await c.execAsync({ argv: ["/usr/bin/top", "-b", "-n1"] });
session
.on("stdout", (chunk: Buffer) => process.stdout.write(chunk))
.on("stderr", (chunk: Buffer) => process.stderr.write(chunk))
.on("exit", (code: number) => console.log("exit:", code));
session.kill(); // send SIGTERM
session.kill(9); // send SIGKILL
// Open a shell; returns the shell's exit code
await c.attach(options?) // → number
// Non-blocking async console session (EventEmitter)
const session = await c.consoleAsync(ttynum?) // → ConsoleSession
session.on("data", (chunk: Buffer) => { ... });
session.on("close", () => { ... });
session.write(data);
session.resize(cols, rows);
session.close();
// Blocking console (connects current stdio)
await c.console(ttynum, [stdinfd, stdoutfd, stderrfd], escape);
// Raw TTY allocation
const [ttyfd, ptxfd] = await c.consoleGetFds(ttynum?);const stats: ContainerStats = await c.stats();
// {
// "memory.usage_in_bytes": string | null,
// "memory.limit_in_bytes": string | null,
// "memory.memsw.usage_in_bytes": string | null,
// "cpuacct.usage": string | null,
// "cpu.stat": string | null,
// "blkio.throttle.io_service_bytes": string | null,
// }await c.getInterfaces() // → string[]
await c.getIPs(iface, "inet") // → string[] (IPv4)
await c.getIPs(iface, "inet6", scope) // → string[] (IPv6)
await c.attachInterface(dev, dst_dev?)
await c.detachInterface(dev, dst_dev?)await c.addDeviceNode(src_path, dest_path?)
await c.removeDeviceNode(src_path, dest_path?)
c.getCGroupItem(subsys) // → string | undefined
c.setCGroupItem(subsys, value)await c.snapshot(commentfile) // → number (snap index)
await c.snapshotList() // → lxc_snapshot[]
await c.snapshotRestore(snapname, newname?)
await c.snapshotDestroy(snapname)
await c.snapshotDestroy(true) // destroy allawait c.mount(source, target, filesystemtype, mountflags, mnt)
await c.umount(source, mountflags, mnt)await c.checkpoint(directory, stop?, verbose?)
await c.restore(directory, verbose?)
await c.migrate(cmd: LXC_MIGRATE, options?)await c.seccompNotifyFd() // → number
await c.seccompNotifyFdActive() // → numberawait c.consoleLog({ clear?, read?, read_max? }) // → stringThe examples/ directory contains runnable scripts for common use cases:
| Script | Description |
|---|---|
examples/create/ |
Create a container from a template |
examples/start/ |
Start a container |
examples/execute/ |
Run a command in a container |
examples/attach/ |
Open a shell in a container |
examples/console_async/ |
Non-blocking console session |
examples/clone/ |
Clone a container |
examples/checkpoint/ |
CRIU checkpoint/restore |
examples/concurrent_create/ |
Concurrent container creation |
examples/stats/ |
Read cgroup resource metrics |
examples/images/ |
Browse available LXC images |
Run any example with ts-node:
npm run example:create
npm run example:execute
npm run example:console_asyncRequired additional dependencies:
| Tool | Version |
|---|---|
| liblxc-dev | same as LXC |
| g++ | ≥ 7 |
| cmake | any recent |
sudo apt install lxc lxc-dev g++ cmakeClone and build:
git clone https://github.com/SourceRegistry/node-lxc.git
cd node-lxc
npm install
npx node-gyp configure && npx node-gyp build
npx tsc --buildnpm testThe test suite uses Node.js's built-in node:test runner. Tests that require root access (container create/start/stop/destroy) are automatically skipped unless the process is running as root.
To run the full integration test suite:
sudo npm testBug reports and pull requests are welcome on GitHub.
Please ensure:
- New C++ code uses
std::unique_ptr<char[]>for heap buffers - Async lambda captures use by-value (
[this, var]not[this, &var]) - All public API changes include TypeScript type updates and JSDoc
Apache 2.0 © 2026 ProjectSource V.O.F.