npm package: @otterdeploy/docker
Description: Modern TypeScript Docker Engine SDK with Result-based error handling
Latest version: 0.3.0

# @otterdeploy/docker

Modern TypeScript Docker Engine SDK with Result-based error handling. A complete, type-safe alternative to `dockerode` and `docker-modem`.

## Install

```bash
bun add @otterdeploy/docker
```

## Quick Start

```ts
import { Docker } from "@otterdeploy/docker";

// Auto-detect from DOCKER_HOST, DOCKER_TLS_VERIFY, etc.
const docker = Docker.fromEnv();

// List containers (check result)
const result = await docker.containers.list({ all: true });
if (result.isOk()) {
  console.log(result.value);
}

// Or unwrap directly (throws on error)
const containers = (await docker.containers.list({ all: true })).unwrap();

// Pull an image
const stream = await docker.pull("nginx:latest");

// Run a container
const run = await docker.run("alpine", ["echo", "hello"], {
  autoRemove: true,
});
```

## Transport Configuration

Transports connect to the Docker daemon over different protocols.

### Auto-detect from environment

```ts
const docker = Docker.fromEnv();
```

Reads `DOCKER_HOST`, `DOCKER_TLS_VERIFY`, `DOCKER_CERT_PATH`, and `DOCKER_CLIENT_TIMEOUT`. Defaults to API version `v1.47`.

### Unix Socket (default)

```ts
const docker = new Docker({
  transport: { type: "unix", socketPath: "/var/run/docker.sock" },
  apiVersion: "v1.47",
});
```

### TCP / TLS

```ts
const docker = new Docker({
  transport: {
    type: "tcp",
    host: "localhost",
    port: 2376,
    tls: {
      ca: fs.readFileSync("/certs/ca.pem"),
      cert: fs.readFileSync("/certs/cert.pem"),
      key: fs.readFileSync("/certs/key.pem"),
    },
  },
  apiVersion: "v1.47",
});
```

### SSH

```ts
const docker = new Docker({
  transport: {
    type: "ssh",
    host: "myhost",
    port: 22,
    username: "deploy",
    privateKey: fs.readFileSync("~/.ssh/id_rsa", "utf8"),
  },
  apiVersion: "v1.47",
});
```

> **Note:** SSH transport does not support interactive `exec` or `attach` operations (i.e., `dialHijack`). These require a direct connection upgrade that is not possible over SSH tunnels. Use Unix socket or TCP transport for interactive container sessions.

### Windows Named Pipes

```ts
const docker = new Docker({
  transport: { type: "npipe", path: "//./pipe/docker_engine" },
  apiVersion: "v1.47",
});
```

## API Reference

All methods return `Result<T, DockerError>` from [better-result](https://github.com/niconiahi/better-result). Use `isOk()` / `isErr()` for type-safe error handling, or `.unwrap()` to get the value directly (throws on error).

### Containers

```ts
// List, create, inspect
const containers = await docker.containers.list({ all: true });
const container = await docker.containers.create({ Image: "nginx", name: "web" });
const info = await container.inspect();

// Lifecycle
await container.start();
await container.stop({ t: 10 });
await container.restart();
await container.kill({ signal: "SIGTERM" });
await container.pause();
await container.unpause();
await container.remove({ force: true });

// Logs & stats
const logs = await container.logs({ stdout: true, stderr: true, follow: true });
const stats = await container.stats({ stream: false });

// Exec
const exec = await container.exec({ Cmd: ["ls", "-la"], AttachStdout: true });
const output = await exec.start();

// Interactive attach (stdin support via hijack)
const duplex = await container.attach({ stream: true, stdin: true, stdout: true });

// Filesystem
await container.putArchive(tarStream, { path: "/app" });
const archive = await container.getArchive({ path: "/app" });
const archiveInfo = await container.infoArchive({ path: "/app" });

// Checkpoints
await container.listCheckpoints();
await container.createCheckpoint({ CheckpointID: "cp1", Exit: true });
await container.deleteCheckpoint("cp1");

// Other
await container.wait();
await container.commit({ repo: "myimage", tag: "v1" });
await container.top();
await container.changes();
await container.resize({ h: 40, w: 120 });
await container.update({ Memory: 512 * 1024 * 1024 });
await container.rename("new-name");
```

### Images

```ts
// List, inspect, search
const images = await docker.images.list();
const image = await docker.images.get("nginx:latest");
const info = await image.inspect();
const results = await docker.images.search({ term: "nginx" });

// Pull (convenience)
const stream = await docker.pull("nginx:1.25");

// Pull with auth
const stream = await docker.pull("private/image:v1", {
  authconfig: { username: "user", password: "pass" },
});

// Build from tar context
const buildStream = await docker.images.build(tarStream, {
  t: "myapp:latest",
  dockerfile: "Dockerfile",
});

// Load / Import
const loadStream = await docker.images.load(tarStream);
const importStream = await docker.images.import(tarStream, { repo: "imported", tag: "v1" });

// Tag, push, remove
await image.tag({ repo: "myrepo/myimage", tag: "v2" });
await image.push({ authconfig: { username: "user", password: "pass" } });
await image.remove({ force: true });
await image.history();
```

### Volumes

```ts
const volumes = await docker.volumes.list();
const volume = await docker.volumes.create({ Name: "mydata", Driver: "local" });
const info = await volume.inspect();
await volume.remove();
await docker.volumes.prune();
```

### Networks

```ts
const networks = await docker.networks.list();
const network = await docker.networks.create({ Name: "mynet", Driver: "bridge" });
await network.connect({ Container: "abc123" });
await network.disconnect({ Container: "abc123" });
await network.remove();
await docker.networks.prune();
```

### System

```ts
const info = await docker.system.info();
const version = await docker.system.version();
const ping = await docker.system.ping();
const events = await docker.system.events({ since: "1h" });
const df = await docker.system.df();
const auth = await docker.system.auth({ username: "user", password: "pass" });

// Prune
await docker.system.pruneContainers();
await docker.system.pruneImages();
await docker.system.pruneVolumes();
await docker.system.pruneNetworks();
await docker.system.pruneBuilder({ all: true });
```

### Swarm (Services, Tasks, Nodes, Secrets, Configs)

```ts
// Services
const services = await docker.services.list();
const service = await docker.services.create({
  Name: "web",
  TaskTemplate: {
    /* ... */
  },
});
await service.update({
  version: 1,
  spec: {
    /* ... */
  },
});
await service.logs({ stdout: true });
await service.remove();

// Tasks, Nodes, Secrets, Configs follow the same pattern
```

### Plugins

```ts
const plugins = await docker.plugins.list();
await docker.plugins.install({ remote: "vieux/sshfs", name: "sshfs" });
const plugin = await docker.plugins.get("sshfs");
await plugin.enable();
await plugin.disable();
await plugin.remove();
```

## Stream Utilities

### demuxStream

Demultiplex Docker's multiplexed stdout/stderr stream:

```ts
import { demuxStream } from "@otterdeploy/docker";

const logs = await container.logs({ stdout: true, stderr: true, follow: true });
if (logs.isOk()) {
  demuxStream(logs.value, process.stdout, process.stderr);
}
```

### followProgress

Parse newline-delimited JSON progress streams (from pull, push, build):

```ts
import { followProgress } from "@otterdeploy/docker";

const stream = await docker.pull("nginx:latest");
if (stream.isOk()) {
  followProgress(
    stream.value,
    (err, output) => {
      if (err) console.error(err);
      else console.log("Done!", output.length, "events");
    },
    (event) => console.log(event),
  );
}
```

## Error Handling

All API methods return `Result<T, DockerError>`. Errors are tagged by type:

```ts
import { DockerNotFoundError } from "@otterdeploy/docker";

const result = await container.inspect();
if (result.isErr()) {
  const error = result.error;
  if (error instanceof DockerNotFoundError) {
    console.log("Container not found");
  }
}
```

If you prefer exceptions, use `.unwrap()` to get the value directly or throw on error:

```ts
const info = (await container.inspect()).unwrap(); // throws DockerError on failure
```

Error types: `DockerBadRequestError`, `DockerUnauthorizedError`, `DockerForbiddenError`, `DockerNotFoundError`, `DockerConflictError`, `DockerServerError`, `DockerServiceUnavailableError`, `DockerNetworkError`, `DockerTimeoutError`, `DockerAbortError`.

## BuildKit Sessions

For BuildKit v2 builds with registry authentication:

```ts
import { withSession } from "@otterdeploy/docker";

const session = await withSession(transport, "v1.47", {
  username: "user",
  password: "pass",
});
```

## License

MIT