@dockstat/docker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # @dockstat/docker
2
+
3
+ > A Bun-native, type-safe wrapper for the Docker API
4
+
5
+ **@dockstat/docker** provides a modern, fully-typed interface to Docker's remote API, built specifically for [Bun](https://bun.sh). It offers a modular architecture, comprehensive type safety, and support for both Unix sockets and TCP connections with TLS.
6
+
7
+ ## Features
8
+
9
+ - **Bun-Native**: Optimized for Bun's performance and APIs
10
+ - **Type-Safe**: Full TypeScript coverage with generated types from Docker's OpenAPI spec
11
+ - **Modular Architecture**: Separate modules for containers, images, networks, volumes, and more
12
+ - **Flexible Connection**: Support for Unix sockets, TCP, and TLS-secured connections
13
+ - **Environment-Based Config**: Automatically reads Docker environment variables (like the Docker CLI)
14
+ - **Zero Dependencies**: Minimal footprint, leveraging Bun's built-in capabilities
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun add @dockstat/docker
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Using Environment Variables
25
+
26
+ The easiest way to get started is by letting the package auto-configure from your environment:
27
+
28
+ ```ts
29
+ import { createDockerFromEnv } from "@dockstat/docker"
30
+
31
+ const docker = createDockerFromEnv()
32
+ ```
33
+
34
+ This reads from standard Docker environment variables:
35
+ - `DOCKER_SOCKET` - Path to Unix socket or TCP URL (default: `/var/run/docker.sock`)
36
+ - `CERT_FILE`, `KEY_FILE`, `CA_FILE` - Paths to TLS certificates
37
+
38
+ ### Manual Configuration
39
+
40
+ For full control over the connection:
41
+
42
+ ```ts
43
+ import { Docker } from "@dockstat/docker"
44
+
45
+ // Unix socket connection
46
+ const docker = new Docker({
47
+ mode: "unix",
48
+ socketPath: "/var/run/docker.sock"
49
+ })
50
+
51
+ // TCP connection
52
+ const docker = new Docker({
53
+ mode: "tcp",
54
+ baseUrl: "http://localhost:2375"
55
+ })
56
+
57
+ // TCP with TLS
58
+ const docker = new Docker({
59
+ mode: "tcp",
60
+ baseUrl: "https://remote-docker:2376",
61
+ tls: {
62
+ cert: Bun.file("/path/to/cert.pem"),
63
+ key: Bun.file("/path/to/key.pem"),
64
+ ca: Bun.file("/path/to/ca.pem")
65
+ }
66
+ })
67
+ ```
68
+
69
+ ### Verify Connection
70
+
71
+ ```ts
72
+ const isConnected = await docker.ping()
73
+ console.log("Docker daemon reachable:", isConnected)
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ ### Containers
79
+
80
+ List all containers:
81
+
82
+ ```ts
83
+ const containers = await docker.containers.list()
84
+ console.log(containers)
85
+ ```
86
+
87
+ List running containers only:
88
+
89
+ ```ts
90
+ const runningContainers = await docker.containers.list({
91
+ all: false
92
+ })
93
+ ```
94
+
95
+ Create and start a container:
96
+
97
+ ```ts
98
+ const result = await docker.containers.create({
99
+ Image: "nginx:latest",
100
+ Env: ["NGINX_PORT=80"]
101
+ })
102
+ await docker.containers.start(result.Id)
103
+ ```
104
+
105
+ Inspect a container:
106
+
107
+ ```ts
108
+ const details = await docker.containers.inspect("my-container")
109
+ console.log(details.State)
110
+ ```
111
+
112
+ Get container logs:
113
+
114
+ ```ts
115
+ const logs = await docker.containers.logs("my-container", {
116
+ stdout: true,
117
+ stderr: true
118
+ })
119
+ ```
120
+
121
+ Stop and remove a container:
122
+
123
+ ```ts
124
+ await docker.containers.stop("my-container")
125
+ await docker.containers.remove("my-container", false, true)
126
+ ```
127
+
128
+ ### Images
129
+
130
+ List images:
131
+
132
+ ```ts
133
+ const images = await docker.images.list()
134
+ ```
135
+
136
+ Pull an image:
137
+
138
+ ```ts
139
+ await docker.images.create("nginx:latest", {})
140
+ ```
141
+
142
+ ### Networks
143
+
144
+ List networks:
145
+
146
+ ```ts
147
+ const networks = await docker.networks.list()
148
+ ```
149
+
150
+ Create a network:
151
+
152
+ ```ts
153
+ await docker.networks.create({
154
+ Name: "my-network",
155
+ Driver: "bridge"
156
+ })
157
+ ```
158
+
159
+ ### Volumes
160
+
161
+ List volumes:
162
+
163
+ ```ts
164
+ const volumes = await docker.volumes.list()
165
+ ```
166
+
167
+ Create a volume:
168
+
169
+ ```ts
170
+ await docker.volumes.create({
171
+ Name: "my-volume",
172
+ Driver: "local"
173
+ })
174
+ ```
175
+
176
+ ### Executing Commands
177
+
178
+ Run a command in a container:
179
+
180
+ ```ts
181
+ // Create exec instance
182
+ const exec = await docker.containers.execCreate("my-container", {
183
+ AttachStdout: true,
184
+ AttachStderr: true,
185
+ Cmd: ["ls", "-la", "/app"]
186
+ })
187
+
188
+ // Start exec and get output
189
+ const response = await docker.containers.execStart(exec.Id)
190
+ ```
191
+
192
+ ### Statistics
193
+
194
+ Get container resource usage:
195
+
196
+ ```ts
197
+ // Get single stats snapshot
198
+ const stats = await docker.containers.stats("my-container")
199
+ console.log(stats.cpu_stats, stats.memory_stats)
200
+
201
+ // Stream stats
202
+ const stream = await docker.containers.stats("my-container", { stream: true })
203
+ // stream is a Response object with streaming body
204
+ ```
205
+
206
+ ## API Modules
207
+
208
+ The `Docker` class provides access to the following modules:
209
+
210
+ - **`containers`** - Container lifecycle, logs, stats, exec, filesystem operations
211
+ - **`images`** - Image management, building, pulling
212
+ - **`networks`** - Network creation, inspection, connection management
213
+ - **`volumes`** - Volume lifecycle and management
214
+ - **`exec`** - Execute commands in running containers
215
+ - **`distribution`** - Registry and distribution operations
216
+ - **`nodes`** - Swarm node management
217
+
218
+ All modules are fully typed with TypeScript types generated from Docker's OpenAPI specification.
219
+
220
+ ## Configuration
221
+
222
+ ### Connection Modes
223
+
224
+ **Unix Socket** (default):
225
+
226
+ ```ts
227
+ {
228
+ mode: "unix",
229
+ socketPath: "/var/run/docker.sock",
230
+ tls?: TLSOptions
231
+ }
232
+ ```
233
+
234
+ **TCP**:
235
+
236
+ ```ts
237
+ {
238
+ mode: "tcp",
239
+ baseUrl: "http://localhost:2375",
240
+ tls?: TLSOptions
241
+ }
242
+ ```
243
+
244
+ ### Environment Variables
245
+
246
+ When using `createDockerFromEnv()`, the following environment variables are respected:
247
+
248
+ | Variable | Description | Default |
249
+ |----------|-------------|---------|
250
+ | `DOCKER_SOCKET` | Unix socket path or TCP URL | `/var/run/docker.sock` |
251
+ | `CERT_FILE` | Path to client certificate | - |
252
+ | `KEY_FILE` | Path to client key | - |
253
+ | `CA_FILE` | Path to CA certificate | - |
254
+
255
+ ## TypeScript Support
256
+
257
+ This package includes comprehensive TypeScript types for all Docker API operations.
258
+
259
+ All methods return properly typed responses:
260
+
261
+ ```ts
262
+ import type { ContainerInspectResponse } from "@dockstat/docker"
263
+
264
+ const container: ContainerInspectResponse = await docker.containers.inspect("my-id")
265
+ // Full type safety and autocomplete
266
+ ```
267
+
268
+ ## Part of the DockStat Ecosystem
269
+
270
+ `@dockstat/docker` is a core package in the [DockStat](https://github.com/its4nik/dockstat) project, an extensible container administration and monitoring platform. It powers Docker interactions throughout the DockStat ecosystem, providing a unified, type-safe interface for container management.
271
+
272
+ ## Contributing
273
+
274
+ Contributions are welcome! Please follow these guidelines:
275
+
276
+ 1. Follow the existing code style and structure
277
+ 2. Ensure TypeScript types are properly defined
278
+ 3. Add JSDoc comments for public APIs
279
+ 4. Test your changes thoroughly
280
+
281
+ For detailed development guidelines, see the [main DockStat README](https://github.com/its4nik/dockstat).
282
+
283
+ ## License
284
+
285
+ [ Mozilla Public License Version 2.0 ](https://www.mozilla.org/en-US/MPL/2.0/)
286
+
287
+ ---
288
+
289
+ **Made with ❤️ for the DockStat project**
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@dockstat/docker",
3
+ "description": "A bun native, typesafe wrapper for the docker API",
4
+ "author": {
5
+ "email": "info@itsnik.de",
6
+ "name": "ItsNik",
7
+ "url": "https://itsnik.de"
8
+ },
9
+ "keywords": [
10
+ "docker",
11
+ "bun"
12
+ ],
13
+ "version": "0.1.0",
14
+ "license": "MPL-2.0",
15
+ "repository": {
16
+ "directory": "packages/bun-docker",
17
+ "type": "git",
18
+ "url": "https://github.com/its4nik/dockstat"
19
+ },
20
+ "module": "src/index.ts",
21
+ "type": "module",
22
+ "devDependencies": {
23
+ "@types/bun": "latest",
24
+ "@types/node": "^25.5.0"
25
+ },
26
+ "peerDependencies": {
27
+ "typescript": "^5"
28
+ },
29
+ "files": [
30
+ "src",
31
+ "README.md",
32
+ "tsconfig.json"
33
+ ],
34
+ "scripts": {
35
+ "build": "bun build --target=bun --production --sourcemap=external --outdir=dist src/index.ts",
36
+ "typegen": "bunx tsc --declaration --pretty --outFile dist/types.d.ts --emitDeclarationOnly src/index.ts",
37
+ "publish": "bun run build && bun run typegen && bun pm pack --destination ./dist && npm publish --access public ./dist/*.tgz ; rm -r ./dist"
38
+ }
39
+ }
package/src/docker.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { BaseModule } from "./modules/base"
2
+ import type { ConnectionConfig } from "./modules/base/types"
3
+ import { ContainerModule } from "./modules/container"
4
+ import { DistributionModule } from "./modules/distribution"
5
+ import { ExecModule } from "./modules/exec"
6
+ import { ImagesModule } from "./modules/images"
7
+ import { NetworksModule } from "./modules/networks"
8
+ import { NodesModule } from "./modules/nodes"
9
+ import { VolumeModule } from "./modules/volumes"
10
+
11
+ export class Docker {
12
+ public readonly containers: ContainerModule
13
+ public readonly images: ImagesModule
14
+ public readonly networks: NetworksModule
15
+ public readonly volumes: VolumeModule
16
+ public readonly exec: ExecModule
17
+ public readonly distribution: DistributionModule
18
+ public readonly nodes: NodesModule
19
+
20
+ constructor(private config: ConnectionConfig) {
21
+ this.containers = new ContainerModule(config)
22
+ this.images = new ImagesModule(config)
23
+ this.networks = new NetworksModule(config)
24
+ this.volumes = new VolumeModule(config)
25
+ this.exec = new ExecModule(config)
26
+ this.distribution = new DistributionModule(config)
27
+ this.nodes = new NodesModule(config)
28
+ }
29
+
30
+ async ping(): Promise<boolean> {
31
+ const requester = new BaseModule(this.config)
32
+ const res = await requester.request("/_ping", "GET")
33
+ return res.ok
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ declare global {
2
+ namespace NodeJS {
3
+ interface ProcessEnv {
4
+ DOCKER_SOCKET: "/var/run/docker.sock" | string
5
+ ENABLE_API?: string
6
+ API_KEYS?: string
7
+ API_PORT?: string
8
+ KEY_FILE?: "./key.pem"
9
+ CERT_FILE?: "./cert.pem"
10
+ CA_FILE?: "./ca.pem"
11
+ }
12
+ }
13
+ }
14
+
15
+ export {}
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { Docker } from "./docker"
2
+ import { getConnectionConfig } from "./utils/env"
3
+
4
+ export const createDockerFromEnv = () => {
5
+ const config = getConnectionConfig()
6
+ return new Docker(config)
7
+ }
8
+
9
+ export { Docker }
@@ -0,0 +1,106 @@
1
+ import type { ReadableStreamDefaultReader } from "node:stream/web"
2
+
3
+ /**
4
+ * WebSocket-like interface for Docker container attach
5
+ * Uses the attach endpoint with stream wrapping for compatibility with Unix sockets
6
+ *
7
+ * @note Currently supports read-only operations (receiving stdout/stderr). Full WebSocket
8
+ * protocol support with bidirectional communication is planned for future versions.
9
+ */
10
+ export class DockerWebSocket {
11
+ private static readonly CONNECTING = 0
12
+ private static readonly OPEN = 1
13
+ private static readonly CLOSING = 2
14
+ private static readonly CLOSED = 3
15
+
16
+ private readyStateValue: number = DockerWebSocket.CONNECTING
17
+ private listeners: Map<string, Array<(event: unknown) => void>> = new Map()
18
+ private reader: ReadableStreamDefaultReader | null = null
19
+
20
+ addEventListener(type: string, listener: (event: unknown) => void) {
21
+ if (!this.listeners.has(type)) {
22
+ this.listeners.set(type, [])
23
+ }
24
+ this.listeners.get(type)?.push(listener)
25
+ }
26
+
27
+ removeEventListener(type: string, listener: (event: unknown) => void) {
28
+ const typeListeners = this.listeners.get(type)
29
+ if (typeListeners) {
30
+ const index = typeListeners.indexOf(listener)
31
+ if (index > -1) {
32
+ typeListeners.splice(index, 1)
33
+ }
34
+ }
35
+ }
36
+
37
+ async attach(response: Response) {
38
+ if (!response.body) {
39
+ this.emit("error", new Error("Response has no body"))
40
+ this.readyStateValue = DockerWebSocket.CLOSED
41
+ this.emit("close", {})
42
+ return
43
+ }
44
+
45
+ this.reader = response.body.getReader()
46
+ this.readyStateValue = DockerWebSocket.OPEN
47
+ this.emit("open", {})
48
+
49
+ try {
50
+ while (this.readyStateValue === DockerWebSocket.OPEN) {
51
+ const { done, value } = await this.reader.read()
52
+ if (done) break
53
+
54
+ // Decode the chunk to string
55
+ const text = new TextDecoder().decode(value)
56
+ this.emit("message", { data: text })
57
+ }
58
+ } catch (error) {
59
+ if (this.readyStateValue !== DockerWebSocket.CLOSING) {
60
+ this.emit("error", error)
61
+ }
62
+ } finally {
63
+ this.readyStateValue = DockerWebSocket.CLOSED
64
+ this.emit("close", {})
65
+ }
66
+ }
67
+
68
+ close() {
69
+ if (this.readyStateValue === DockerWebSocket.CLOSED) return
70
+
71
+ this.readyStateValue = DockerWebSocket.CLOSING
72
+
73
+ if (this.reader) {
74
+ this.reader.cancel().catch(() => {})
75
+ }
76
+
77
+ this.readyStateValue = DockerWebSocket.CLOSED
78
+ this.emit("close", {})
79
+ }
80
+
81
+ get CONNECTING() {
82
+ return DockerWebSocket.CONNECTING
83
+ }
84
+ get OPEN() {
85
+ return DockerWebSocket.OPEN
86
+ }
87
+ get CLOSING() {
88
+ return DockerWebSocket.CLOSING
89
+ }
90
+ get CLOSED() {
91
+ return DockerWebSocket.CLOSED
92
+ }
93
+
94
+ get readyState() {
95
+ return this.readyStateValue
96
+ }
97
+
98
+ private emit(type: string, event: unknown) {
99
+ const listeners = this.listeners.get(type)
100
+ if (listeners) {
101
+ for (const listener of listeners) {
102
+ listener(event)
103
+ }
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,30 @@
1
+ import type { BodyInit, HeadersInit } from "bun"
2
+ import { prepareRequestOptions } from "../../utils/request-options"
3
+ import { handleDockerResponse } from "../../utils/response"
4
+ import { buildDockerUrl, buildQueryString } from "../../utils/url"
5
+ import { DockerWebSocket } from "../_socket"
6
+ import type { ConnectionConfig, HttpMethod } from "./types"
7
+
8
+ export class BaseModule {
9
+ constructor(private config: ConnectionConfig) {}
10
+
11
+ protected ws = new DockerWebSocket()
12
+
13
+ async request(
14
+ path: string,
15
+ method: HttpMethod = "GET",
16
+ body?: BodyInit | object,
17
+ headers?: HeadersInit,
18
+ params?: object
19
+ ) {
20
+ const dockerApiVersion = this.config.dockerAPIVersion || "1.54"
21
+ const query = buildQueryString(params)
22
+ const url = buildDockerUrl(this.config, path, query, dockerApiVersion)
23
+
24
+ const options = prepareRequestOptions(this.config, method, body, headers, url)
25
+
26
+ const response = await fetch(url, options)
27
+
28
+ return handleDockerResponse(response, path, dockerApiVersion, params)
29
+ }
30
+ }
@@ -0,0 +1,17 @@
1
+ import type { TLSOptions } from "bun"
2
+
3
+ export type ConnectionMode = "unix" | "tcp"
4
+
5
+ export interface ConnectionConfig {
6
+ mode: ConnectionMode
7
+ // For Unix: the file path
8
+ socketPath?: string
9
+ // For TCP: the full base URL (e.g., http://192.168.1.50:2375)
10
+ baseUrl?: string
11
+ // TLS options (used for TCP or TLS-secured Unix sockets)
12
+ tls?: TLSOptions
13
+ // The docker API version
14
+ dockerAPIVersion?: string
15
+ }
16
+
17
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD"