docker-engine

Interact with the Docker Engine API via dockerode in Node.js/TypeScript -- listing containers, streaming logs, collecting stats, and running Compose operations as subprocesses.

7 stars

Best use case

docker-engine is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Interact with the Docker Engine API via dockerode in Node.js/TypeScript -- listing containers, streaming logs, collecting stats, and running Compose operations as subprocesses.

Teams using docker-engine should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/docker-engine/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/devops-infrastructure/docker-compose-ui/skills/docker-engine/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/docker-engine/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How docker-engine Compares

Feature / Agentdocker-engineStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Interact with the Docker Engine API via dockerode in Node.js/TypeScript -- listing containers, streaming logs, collecting stats, and running Compose operations as subprocesses.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

## 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

```bash
pnpm add dockerode
pnpm add -D @types/dockerode
```

## Connect to Docker

```typescript
import Dockerode from 'dockerode';

// Unix socket (default)
const docker = new Dockerode({
  socketPath: process.env.DOCKER_SOCKET ?? '/var/run/docker.sock',
});

// TCP host (remote Docker or Docker-in-Docker)
const docker = new Dockerode({
  host: 'remote-host',
  port: 2376,
  protocol: 'https',
});

// From environment (supports DOCKER_HOST)
const docker = new Dockerode();
```

## List Containers

```typescript
// All containers (including stopped)
const all = await docker.listContainers({ all: true });

// Only running
const running = await docker.listContainers({ all: false });

// Filter by Compose project label
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

```typescript
const container = docker.getContainer(containerId);

await container.start();
await container.stop();
await container.restart();
await container.remove({ force: true });

// Inspect full container details
const info = await container.inspect();
// info.State.Status, info.Config.Image, info.HostConfig.Binds, etc.
```

## Stream Logs via SSE

```typescript
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,
    // stdout handler
    {
      write(chunk: Buffer) {
        const line = chunk.toString('utf8').replace(/\r?\n$/, '');
        const [ts, ...rest] = line.split(' ');
        const data = JSON.stringify({ timestamp: ts, stream: 'stdout', line: rest.join(' ') });
        res.write(`event: log\ndata: ${data}\n\n`);
      },
    },
    // stderr handler
    {
      write(chunk: Buffer) {
        const line = chunk.toString('utf8').replace(/\r?\n$/, '');
        const [ts, ...rest] = line.split(' ');
        const data = JSON.stringify({ timestamp: ts, stream: 'stderr', line: rest.join(' ') });
        res.write(`event: log\ndata: ${data}\n\n`);
      },
    },
  );

  // Clean up on client disconnect
  req.on('close', () => {
    (stream as NodeJS.ReadableStream).destroy();
  });
}
```

## One-Shot Stats (CPU and Memory)

```typescript
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

```typescript
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).forEach(onLine);
    });

    proc.on('exit', (code) => {
      if (code === 0) resolve();
      else reject(new Error(`docker compose exited with code ${code}`));
    });
  });
}

// Usage
await runCompose('/projects/myapp', ['up', '-d'], console.log);
await runCompose('/projects/myapp', ['down'], console.log);
await runCompose('/projects/myapp', ['pull'], console.log);
```

## Project Discovery Pattern

```typescript
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;

    for (const candidate of candidates) {
      const fp = path.join(projectDir, candidate);
      try {
        await fs.access(fp);
        composeFile = fp;
        break;
      } catch {
        // not found
      }
    }

    if (!composeFile) continue;

    const raw = await fs.readFile(composeFile, 'utf8');
    const doc = yaml.load(raw) as { services?: Record<string, unknown> };
    const serviceNames = Object.keys(doc?.services ?? {});

    projects.push({
      name: entry.name,
      directory: projectDir,
      composeFile,
      serviceNames,
    });
  }

  return projects;
}
```

## Match Containers to Projects

```typescript
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 { name: svcName, status: 'not_created', containerName: '', containerId: '', image: '', ports: [] };
      }

      return {
        name: svcName,
        containerName: c.Names[0]?.replace(/^\//, '') ?? '',
        containerId: c.Id,
        status: mapState(c.State),
        image: c.Image,
        ports: c.Ports.map((p) => ({ hostPort: p.PublicPort, containerPort: p.PrivatePort, protocol: p.Type })),
      };
    });

    const runningCount = services.filter((s) => s.status === 'running').length;
    const stoppedCount = services.filter((s) => s.status !== 'running').length;

    return { name: p.name, directory: p.directory, composeFile: p.composeFile, services, runningCount, stoppedCount };
  });
}

function mapState(state: string): ServiceSummary['status'] {
  switch (state) {
    case 'running': return 'running';
    case 'paused': return 'paused';
    case 'exited': return 'exited';
    case 'created': return 'stopped';
    default: return 'stopped';
  }
}
```

## 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 |

Related Skills

Skill: queue-engine

7
from heldernoid/agentic-build-templates

## When to use this skill

reminder-engine

7
from heldernoid/agentic-build-templates

Server-side cron scheduler that polls a reminders table and delivers appointment reminders via WebSocket to the browser, which then shows Web Notifications API alerts. Use when building or debugging reminder delivery in appointment or scheduling applications.

sql-tutorial-engine

7
from heldernoid/agentic-build-templates

No description provided.

flashcard-engine

7
from heldernoid/agentic-build-templates

Spaced repetition flashcard system with SM-2 scheduling, Markdown support, and Anki import. Use when you need to study or manage flashcard decks, grade cards, check review schedules, or import/export card data. Triggers include "flashcards", "spaced repetition", "study cards", "Anki import", "SM-2", "review schedule".

docker-compose-ui

7
from heldernoid/agentic-build-templates

Operate and troubleshoot the docker-compose-ui dashboard -- project discovery, container actions, log streaming, audit log, and deployment.

prompt-engineering

7
from heldernoid/agentic-build-templates

No description provided.

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.