| name | docker-engine |
| description | Interact with the Docker Engine API via dockerode in Node.js/TypeScript -- listing containers, streaming logs, collecting stats, and running Compose operations as subprocesses. |
| triggers | ["dockerode","docker engine api","docker socket","docker node"] |
When to use this skill
Use this skill when implementing Docker Engine API access in Node.js: listing containers, inspecting container state, streaming log output via SSE, collecting one-shot CPU and memory stats, or spawning docker compose subprocesses.
Install
pnpm add dockerode
pnpm add -D @types/dockerode
Connect to Docker
import Dockerode from 'dockerode';
const docker = new Dockerode({
socketPath: process.env.DOCKER_SOCKET ?? '/var/run/docker.sock',
});
const docker = new Dockerode({
host: 'remote-host',
port: 2376,
protocol: 'https',
});
const docker = new Dockerode();
List Containers
const all = await docker.listContainers({ all: true });
const running = await docker.listContainers({ all: false });
const compose = await docker.listContainers({
all: true,
filters: JSON.stringify({ label: ['com.docker.compose.project'] }),
});
ContainerInfo includes: Id, Names, Image, State, Status, Labels, Ports, Created.
Standard Compose Labels
| Label | Example Value | Purpose |
|---|
com.docker.compose.project | myapp | Project name |
com.docker.compose.service | web | Service name |
com.docker.compose.version | 2.24.0 | Compose CLI version |
com.docker.compose.project.working_dir | /projects/myapp | Project directory |
Container Actions
const container = docker.getContainer(containerId);
await container.start();
await container.stop();
await container.restart();
await container.remove({ force: true });
const info = await container.inspect();
Stream Logs via SSE
import type { Request, Response } from 'express';
export async function streamLogs(req: Request, res: Response) {
const container = docker.getContainer(containerId);
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
res.flushHeaders();
const stream = await container.logs({
follow: true,
stdout: true,
stderr: true,
tail: 200,
timestamps: true,
});
docker.modem.demuxStream(
stream,
{
write(chunk: Buffer) {
const line = chunk.toString().(, );
[ts, ...rest] = line.();
data = .({ : ts, : , : rest.() });
res.();
},
},
{
() {
line = chunk.().(, );
[ts, ...rest] = line.();
data = .({ : ts, : , : rest.() });
res.();
},
},
);
req.(, {
(stream .).();
});
}
One-Shot Stats (CPU and Memory)
const container = docker.getContainer(containerId);
const stats = await container.stats({ stream: false });
function calculateCpuPercent(stats: Dockerode.ContainerStats): number {
const cpuDelta =
stats.cpu_stats.cpu_usage.total_usage -
stats.precpu_stats.cpu_usage.total_usage;
const systemDelta =
stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
const numCpus =
stats.cpu_stats.online_cpus ??
stats.cpu_stats.cpu_usage.percpu_usage?.length ??
1;
if (systemDelta <= 0 || cpuDelta <= 0) return 0;
return (cpuDelta / systemDelta) * numCpus * 100;
}
const cpuPercent = calculateCpuPercent(stats);
const memoryMb = stats.memory_stats.usage / 1_048_576;
Note: stream: false returns a single stats snapshot. The CPU calculation compares the current snapshot against precpu_stats. If both cpu_stats and precpu_stats have total_usage = 0 the container is likely not running; return 0.
Spawn Compose Subprocesses
import { spawn } from 'node:child_process';
function runCompose(
projectDir: string,
args: string[],
onLine: (line: string) => void,
): Promise<void> {
return new Promise((resolve, reject) => {
const proc = spawn('docker', ['compose', ...args], {
cwd: projectDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
proc.stdout.on('data', (buf: Buffer) => {
buf.toString().split('\n').filter(Boolean).forEach(onLine);
});
proc.stderr.on('data', (buf: Buffer) => {
buf.toString().split('\n').filter(Boolean).(onLine);
});
proc.(, {
(code === ) ();
( ());
});
});
}
(, [, ], .);
(, [], .);
(, [], .);
Project Discovery Pattern
import fs from 'node:fs/promises';
import path from 'node:path';
import yaml from 'js-yaml';
interface ParsedProject {
name: string;
directory: string;
composeFile: string;
serviceNames: string[];
}
async function discoverProjects(dir: string): Promise<ParsedProject[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
const projects: ParsedProject[] = [];
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const projectDir = path.join(dir, entry.name);
const candidates = ['docker-compose.yml', 'docker-compose.yaml'];
let composeFile: string | null = null;
( candidate candidates) {
fp = path.(projectDir, candidate);
{
fs.(fp);
composeFile = fp;
;
} {
}
}
(!composeFile) ;
raw = fs.(composeFile, );
doc = yaml.(raw) { ?: <, > };
serviceNames = .(doc?. ?? {});
projects.({
: entry.,
: projectDir,
composeFile,
serviceNames,
});
}
projects;
}
Match Containers to Projects
function matchContainersToProjects(
projects: ParsedProject[],
containers: Dockerode.ContainerInfo[],
): Project[] {
const byProject = new Map<string, Dockerode.ContainerInfo[]>();
for (const c of containers) {
const projectLabel = c.Labels['com.docker.compose.project'];
if (!projectLabel) continue;
const list = byProject.get(projectLabel) ?? [];
list.push(c);
byProject.set(projectLabel, list);
}
return projects.map((p) => {
const projectContainers = byProject.get(p.name) ?? [];
const services: ServiceSummary[] = p.serviceNames.map((svcName) => {
const c = projectContainers.find(
(c) => c.Labels['com.docker.compose.service'] === svcName,
);
if (!c) {
return { : svcName, : , : , : , : , : [] };
}
{
: svcName,
: c.[]?.(, ) ?? ,
: c.,
: (c.),
: c.,
: c..( ({ : p., : p., : p. })),
};
});
runningCount = services.( s. === ).;
stoppedCount = services.( s. !== ).;
{ : p., : p., : p., services, runningCount, stoppedCount };
});
}
(): [] {
(state) {
: ;
: ;
: ;
: ;
: ;
}
}
Error Handling
| Error | Cause | Fix |
|---|
ENOENT /var/run/docker.sock | Docker not running or socket path wrong | Start Docker; set DOCKER_SOCKET |
EACCES /var/run/docker.sock | Process lacks permission | Add user to docker group; or run as root |
404 No such container | Container removed between list and action | Re-fetch project before acting |
409 Conflict | Container already in target state | Treat as success or surface to user |
Compose exited with code 1 | Compose file error or image pull failure | Show stderr to user; log to audit |