@otterdeploy/docker
v0.3.0
Published
Modern TypeScript Docker Engine SDK with Result-based error handling
Maintainers
Readme
@otterdeploy/docker
Modern TypeScript Docker Engine SDK with Result-based error handling. A complete, type-safe alternative to dockerode and docker-modem.
Install
bun add @otterdeploy/dockerQuick Start
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
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)
const docker = new Docker({
transport: { type: "unix", socketPath: "/var/run/docker.sock" },
apiVersion: "v1.47",
});TCP / TLS
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
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
execorattachoperations (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
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. Use isOk() / isErr() for type-safe error handling, or .unwrap() to get the value directly (throws on error).
Containers
// 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
// 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
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
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
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)
// 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 patternPlugins
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:
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):
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:
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:
const info = (await container.inspect()).unwrap(); // throws DockerError on failureError types: DockerBadRequestError, DockerUnauthorizedError, DockerForbiddenError, DockerNotFoundError, DockerConflictError, DockerServerError, DockerServiceUnavailableError, DockerNetworkError, DockerTimeoutError, DockerAbortError.
BuildKit Sessions
For BuildKit v2 builds with registry authentication:
import { withSession } from "@otterdeploy/docker";
const session = await withSession(transport, "v1.47", {
username: "user",
password: "pass",
});License
MIT
