@procedures/docker 0.0.0-stage → 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.
Files changed (85) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +752 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/index.d.ts +35 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +35 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/module.d.ts +16 -0
  10. package/dist/module.d.ts.map +1 -0
  11. package/dist/module.js +183 -0
  12. package/dist/module.js.map +1 -0
  13. package/dist/procedures/docker/build.d.ts +12 -0
  14. package/dist/procedures/docker/build.d.ts.map +1 -0
  15. package/dist/procedures/docker/build.js +34 -0
  16. package/dist/procedures/docker/build.js.map +1 -0
  17. package/dist/procedures/docker/down.d.ts +12 -0
  18. package/dist/procedures/docker/down.d.ts.map +1 -0
  19. package/dist/procedures/docker/down.js +25 -0
  20. package/dist/procedures/docker/down.js.map +1 -0
  21. package/dist/procedures/docker/exec.d.ts +12 -0
  22. package/dist/procedures/docker/exec.d.ts.map +1 -0
  23. package/dist/procedures/docker/exec.js +30 -0
  24. package/dist/procedures/docker/exec.js.map +1 -0
  25. package/dist/procedures/docker/index.d.ts +11 -0
  26. package/dist/procedures/docker/index.d.ts.map +1 -0
  27. package/dist/procedures/docker/index.js +11 -0
  28. package/dist/procedures/docker/index.js.map +1 -0
  29. package/dist/procedures/docker/logs.d.ts +16 -0
  30. package/dist/procedures/docker/logs.d.ts.map +1 -0
  31. package/dist/procedures/docker/logs.js +40 -0
  32. package/dist/procedures/docker/logs.js.map +1 -0
  33. package/dist/procedures/docker/ps.d.ts +12 -0
  34. package/dist/procedures/docker/ps.d.ts.map +1 -0
  35. package/dist/procedures/docker/ps.js +22 -0
  36. package/dist/procedures/docker/ps.js.map +1 -0
  37. package/dist/procedures/docker/pull.d.ts +12 -0
  38. package/dist/procedures/docker/pull.d.ts.map +1 -0
  39. package/dist/procedures/docker/pull.js +17 -0
  40. package/dist/procedures/docker/pull.js.map +1 -0
  41. package/dist/procedures/docker/rm.d.ts +12 -0
  42. package/dist/procedures/docker/rm.d.ts.map +1 -0
  43. package/dist/procedures/docker/rm.js +21 -0
  44. package/dist/procedures/docker/rm.js.map +1 -0
  45. package/dist/procedures/docker/run.d.ts +21 -0
  46. package/dist/procedures/docker/run.d.ts.map +1 -0
  47. package/dist/procedures/docker/run.js +56 -0
  48. package/dist/procedures/docker/run.js.map +1 -0
  49. package/dist/procedures/docker/shared.d.ts +23 -0
  50. package/dist/procedures/docker/shared.d.ts.map +1 -0
  51. package/dist/procedures/docker/shared.js +51 -0
  52. package/dist/procedures/docker/shared.js.map +1 -0
  53. package/dist/procedures/docker/stop.d.ts +12 -0
  54. package/dist/procedures/docker/stop.d.ts.map +1 -0
  55. package/dist/procedures/docker/stop.js +20 -0
  56. package/dist/procedures/docker/stop.js.map +1 -0
  57. package/dist/procedures/docker/up.d.ts +12 -0
  58. package/dist/procedures/docker/up.d.ts.map +1 -0
  59. package/dist/procedures/docker/up.js +28 -0
  60. package/dist/procedures/docker/up.js.map +1 -0
  61. package/dist/register.d.ts +9 -0
  62. package/dist/register.d.ts.map +1 -0
  63. package/dist/register.js +16 -0
  64. package/dist/register.js.map +1 -0
  65. package/dist/types.d.ts +130 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +207 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +81 -3
  70. package/src/index.ts +75 -0
  71. package/src/module.ts +237 -0
  72. package/src/procedures/docker/build.ts +46 -0
  73. package/src/procedures/docker/down.ts +31 -0
  74. package/src/procedures/docker/exec.ts +37 -0
  75. package/src/procedures/docker/index.ts +10 -0
  76. package/src/procedures/docker/logs.ts +52 -0
  77. package/src/procedures/docker/ps.ts +27 -0
  78. package/src/procedures/docker/pull.ts +23 -0
  79. package/src/procedures/docker/rm.ts +28 -0
  80. package/src/procedures/docker/run.ts +65 -0
  81. package/src/procedures/docker/shared.ts +62 -0
  82. package/src/procedures/docker/stop.ts +29 -0
  83. package/src/procedures/docker/up.ts +35 -0
  84. package/src/register.ts +20 -0
  85. package/src/types.ts +351 -0
package/README.md CHANGED
@@ -1,3 +1,753 @@
1
- # Temporary Holding Version
1
+ # @procedures/docker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@procedures/docker.svg)](https://www.npmjs.com/package/@procedures/docker)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > Docker container management as procedures using client-shell
7
+
8
+ ## Overview
9
+
10
+ `client-docker` wraps Docker CLI commands as procedures using `client-shell`. It provides a comprehensive set of procedures for container management, image building, Docker Compose orchestration, and more - all accessible through the procedure system's RPC interface.
11
+
12
+ ### Key Features
13
+
14
+ - **Comprehensive Docker Coverage**: Run, build, pull, exec, stop, rm, ps, logs commands
15
+ - **Docker Compose Support**: Up and down operations for multi-container applications
16
+ - **Type-Safe Inputs**: Zod schemas for all inputs with full TypeScript support
17
+ - **Shell Integration**: Leverages `client-shell` for reliable CLI execution
18
+ - **Consistent Output**: Standardized output format across all procedures
19
+ - **CLI Wrapper Pattern**: One procedure per Docker command with full option support
20
+
21
+ ## Architecture
22
+
23
+ ```mermaid
24
+ graph TB
25
+ subgraph "client-docker"
26
+ REG[Registration Layer]
27
+ PROC[Docker Procedures]
28
+ TYPES[Type System]
29
+ end
30
+
31
+ subgraph "Procedures"
32
+ RUN[docker.run]
33
+ BUILD[docker.build]
34
+ PULL[docker.pull]
35
+ EXEC[docker.exec]
36
+ STOP[docker.stop]
37
+ RM[docker.rm]
38
+ PS[docker.ps]
39
+ LOGS[docker.logs]
40
+ UP[docker.compose.up]
41
+ DOWN[docker.compose.down]
42
+ end
43
+
44
+ subgraph "client-shell"
45
+ SHELL[shell.exec]
46
+ end
47
+
48
+ REG --> PROC
49
+ PROC --> RUN
50
+ PROC --> BUILD
51
+ PROC --> PULL
52
+ PROC --> EXEC
53
+ PROC --> STOP
54
+ PROC --> RM
55
+ PROC --> PS
56
+ PROC --> LOGS
57
+ PROC --> UP
58
+ PROC --> DOWN
59
+
60
+ RUN --> SHELL
61
+ BUILD --> SHELL
62
+ PULL --> SHELL
63
+ EXEC --> SHELL
64
+ STOP --> SHELL
65
+ RM --> SHELL
66
+ PS --> SHELL
67
+ LOGS --> SHELL
68
+ UP --> SHELL
69
+ DOWN --> SHELL
70
+
71
+ style REG fill:#4a90e2
72
+ style PROC fill:#7ed321
73
+ style SHELL fill:#f5a623
74
+ ```
75
+
76
+ ### Data Flow
77
+
78
+ ```mermaid
79
+ sequenceDiagram
80
+ participant Client
81
+ participant docker.run
82
+ participant shell.exec
83
+ participant Docker CLI
84
+
85
+ Client->>docker.run: { image, ports, env, ... }
86
+ docker.run->>docker.run: Build docker run command
87
+ docker.run->>shell.exec: { command: "docker run ..." }
88
+ shell.exec->>Docker CLI: Execute command
89
+ Docker CLI-->>shell.exec: stdout/stderr/exitCode
90
+ shell.exec-->>docker.run: ShellResult
91
+ docker.run->>docker.run: Format as DockerCommandOutput
92
+ docker.run-->>Client: { exitCode, stdout, stderr, success, duration }
93
+ ```
94
+
95
+ ## Installation
96
+
97
+ ```bash
98
+ npm install @procedures/docker
99
+ ```
100
+
101
+ Or with pnpm:
102
+
103
+ ```bash
104
+ pnpm add @procedures/docker
105
+ ```
106
+
107
+ ### Dependencies
108
+
109
+ This package depends on:
110
+ - `@procedures/client` - Core client/procedure system
111
+ - `@procedures/shell` - Shell execution for CLI commands
112
+ - `zod` - Schema validation and type inference
113
+
114
+ ### System Requirements
115
+
116
+ - Docker must be installed and available in PATH
117
+ - For Docker Compose procedures: Docker Compose must be installed
118
+
119
+ ## Usage
120
+
121
+ ### Basic Container Operations
122
+
123
+ ```typescript
124
+ import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
125
+ import "@procedures/docker"; // registers its procedures
126
+
127
+ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
128
+
129
+ // Run a container
130
+ const result = await client.exec(["docker", "run"], {
131
+ image: "nginx:latest",
132
+ name: "my-nginx",
133
+ ports: ["8080:80"],
134
+ detach: true,
135
+ });
136
+
137
+ console.log(result);
138
+ // {
139
+ // exitCode: 0,
140
+ // stdout: "container_id_here\n",
141
+ // stderr: "",
142
+ // success: true,
143
+ // duration: 1234
144
+ // }
145
+ ```
146
+
147
+ ### Building Images
148
+
149
+ ```typescript
150
+ // Build an image from a Dockerfile
151
+ await client.exec(["docker", "build"], {
152
+ context: ".",
153
+ tag: "my-app:latest",
154
+ dockerfile: "Dockerfile",
155
+ buildArgs: {
156
+ NODE_VERSION: "20",
157
+ APP_ENV: "production",
158
+ },
159
+ noCache: false,
160
+ });
161
+ ```
162
+
163
+ ### Executing Commands in Containers
164
+
165
+ ```typescript
166
+ // Execute a command in a running container
167
+ await client.exec(["docker", "exec"], {
168
+ container: "my-nginx",
169
+ command: "nginx -t",
170
+ interactive: false,
171
+ tty: false,
172
+ });
173
+
174
+ // Interactive shell session
175
+ await client.exec(["docker", "exec"], {
176
+ container: "my-app",
177
+ command: "/bin/bash",
178
+ interactive: true,
179
+ tty: true,
180
+ env: {
181
+ DEBUG: "true",
182
+ },
183
+ });
184
+ ```
185
+
186
+ ### Container Management
187
+
188
+ ```typescript
189
+ // List all containers
190
+ const containers = await client.exec(["docker", "ps"], {
191
+ all: true,
192
+ format: "json",
193
+ });
194
+
195
+ // Stop containers
196
+ await client.exec(["docker", "stop"], {
197
+ containers: ["nginx-1", "nginx-2"],
198
+ time: 10, // Grace period in seconds
199
+ });
200
+
201
+ // Remove containers
202
+ await client.exec(["docker", "rm"], {
203
+ containers: ["nginx-1"],
204
+ force: true,
205
+ volumes: true,
206
+ });
207
+
208
+ // Get container logs
209
+ const logs = await client.exec(["docker", "logs"], {
210
+ container: "my-app",
211
+ tail: 100,
212
+ timestamps: true,
213
+ follow: false,
214
+ });
215
+ ```
216
+
217
+ ### Docker Compose
218
+
219
+ ```typescript
220
+ // Start services
221
+ await client.exec(["docker", "compose", "up"], {
222
+ file: "docker-compose.yml",
223
+ detach: true,
224
+ build: true,
225
+ forceRecreate: false,
226
+ services: ["web", "db"], // Optional: specific services
227
+ });
228
+
229
+ // Stop and remove services
230
+ await client.exec(["docker", "compose", "down"], {
231
+ file: "docker-compose.yml",
232
+ volumes: true,
233
+ removeOrphans: true,
234
+ rmi: "local", // Remove images built locally
235
+ });
236
+ ```
237
+
238
+ ## API Reference
239
+
240
+ ### Common Output Type
241
+
242
+ All Docker procedures return a standardized output format:
243
+
244
+ ```typescript
245
+ interface DockerCommandOutput {
246
+ exitCode: number;
247
+ stdout: string;
248
+ stderr: string;
249
+ success: boolean; // true if exitCode === 0
250
+ duration: number; // milliseconds
251
+ }
252
+ ```
253
+
254
+ ### Procedures
255
+
256
+ #### `docker.run`
257
+
258
+ Run a Docker container.
259
+
260
+ **Input:**
261
+ ```typescript
262
+ {
263
+ image: string; // Required: Docker image
264
+ name?: string; // Container name
265
+ ports?: string[]; // Port mappings ["host:container", ...]
266
+ volumes?: string[]; // Volume mappings ["/host:/container", ...]
267
+ env?: Record<string, string>; // Environment variables
268
+ detach?: boolean; // Run in background (default: false)
269
+ rm?: boolean; // Auto-remove when stopped (default: false)
270
+ command?: string; // Command to run in container
271
+ cwd?: string; // Working directory for command
272
+ timeout?: number; // Command timeout in ms
273
+ }
274
+ ```
275
+
276
+ **Example:**
277
+ ```typescript
278
+ await client.exec(["docker", "run"], {
279
+ image: "postgres:15",
280
+ name: "my-db",
281
+ ports: ["5432:5432"],
282
+ env: {
283
+ POSTGRES_PASSWORD: "secret",
284
+ POSTGRES_DB: "myapp",
285
+ },
286
+ detach: true,
287
+ });
288
+ ```
289
+
290
+ #### `docker.build`
291
+
292
+ Build a Docker image from a Dockerfile.
293
+
294
+ **Input:**
295
+ ```typescript
296
+ {
297
+ context: string; // Required: Build context path
298
+ tag: string; // Required: Image tag
299
+ dockerfile?: string; // Dockerfile path (relative to context)
300
+ buildArgs?: Record<string, string>; // Build arguments
301
+ noCache?: boolean; // Don't use cache (default: false)
302
+ cwd?: string; // Working directory
303
+ timeout?: number; // Build timeout in ms
304
+ }
305
+ ```
306
+
307
+ **Example:**
308
+ ```typescript
309
+ await client.exec(["docker", "build"], {
310
+ context: ".",
311
+ tag: "my-app:v1.0.0",
312
+ buildArgs: {
313
+ NODE_VERSION: "20",
314
+ },
315
+ noCache: true,
316
+ });
317
+ ```
318
+
319
+ #### `docker.pull`
320
+
321
+ Pull a Docker image from a registry.
322
+
323
+ **Input:**
324
+ ```typescript
325
+ {
326
+ image: string; // Required: Image name
327
+ tag?: string; // Image tag (default: "latest")
328
+ cwd?: string; // Working directory
329
+ timeout?: number; // Pull timeout in ms
330
+ }
331
+ ```
332
+
333
+ **Example:**
334
+ ```typescript
335
+ await client.exec(["docker", "pull"], {
336
+ image: "nginx",
337
+ tag: "alpine",
338
+ });
339
+ ```
340
+
341
+ #### `docker.exec`
342
+
343
+ Execute a command in a running container.
344
+
345
+ **Input:**
346
+ ```typescript
347
+ {
348
+ container: string; // Required: Container name or ID
349
+ command: string; // Required: Command to execute
350
+ interactive?: boolean; // Allocate stdin (default: false)
351
+ tty?: boolean; // Allocate pseudo-TTY (default: false)
352
+ workdir?: string; // Working directory inside container
353
+ env?: Record<string, string>; // Environment variables
354
+ cwd?: string; // Working directory (host)
355
+ timeout?: number; // Command timeout in ms
356
+ }
357
+ ```
358
+
359
+ **Example:**
360
+ ```typescript
361
+ await client.exec(["docker", "exec"], {
362
+ container: "my-app",
363
+ command: "npm run migrate",
364
+ env: {
365
+ DATABASE_URL: "postgres://...",
366
+ },
367
+ });
368
+ ```
369
+
370
+ #### `docker.stop`
371
+
372
+ Stop running containers.
373
+
374
+ **Input:**
375
+ ```typescript
376
+ {
377
+ containers: string[]; // Required: Container names/IDs
378
+ time?: number; // Seconds to wait before killing (default: 10)
379
+ cwd?: string; // Working directory
380
+ timeout?: number; // Command timeout in ms
381
+ }
382
+ ```
383
+
384
+ **Example:**
385
+ ```typescript
386
+ await client.exec(["docker", "stop"], {
387
+ containers: ["web-1", "web-2", "worker-1"],
388
+ time: 30,
389
+ });
390
+ ```
391
+
392
+ #### `docker.rm`
393
+
394
+ Remove containers.
395
+
396
+ **Input:**
397
+ ```typescript
398
+ {
399
+ containers: string[]; // Required: Container names/IDs
400
+ force?: boolean; // Force removal (default: false)
401
+ volumes?: boolean; // Remove volumes (default: false)
402
+ cwd?: string; // Working directory
403
+ timeout?: number; // Command timeout in ms
404
+ }
405
+ ```
406
+
407
+ **Example:**
408
+ ```typescript
409
+ await client.exec(["docker", "rm"], {
410
+ containers: ["temp-container"],
411
+ force: true,
412
+ volumes: true,
413
+ });
414
+ ```
415
+
416
+ #### `docker.ps`
417
+
418
+ List containers.
419
+
420
+ **Input:**
421
+ ```typescript
422
+ {
423
+ all?: boolean; // Show all containers (default: false)
424
+ filter?: string; // Filter output (e.g., "status=running")
425
+ format?: string; // Format output (e.g., "json")
426
+ cwd?: string; // Working directory
427
+ timeout?: number; // Command timeout in ms
428
+ }
429
+ ```
430
+
431
+ **Example:**
432
+ ```typescript
433
+ const result = await client.exec(["docker", "ps"], {
434
+ all: true,
435
+ format: "json",
436
+ });
437
+
438
+ const containers = JSON.parse(result.stdout);
439
+ ```
440
+
441
+ #### `docker.logs`
442
+
443
+ Get container logs.
444
+
445
+ **Input:**
446
+ ```typescript
447
+ {
448
+ container: string; // Required: Container name or ID
449
+ follow?: boolean; // Follow log output (default: false)
450
+ tail?: number; // Number of lines from end
451
+ timestamps?: boolean; // Show timestamps (default: false)
452
+ cwd?: string; // Working directory
453
+ timeout?: number; // Command timeout in ms
454
+ }
455
+ ```
456
+
457
+ **Example:**
458
+ ```typescript
459
+ const logs = await client.exec(["docker", "logs"], {
460
+ container: "my-app",
461
+ tail: 50,
462
+ timestamps: true,
463
+ });
464
+
465
+ console.log(logs.stdout);
466
+ ```
467
+
468
+ #### `docker.compose.up`
469
+
470
+ Start services with Docker Compose.
471
+
472
+ **Input:**
473
+ ```typescript
474
+ {
475
+ file?: string; // Compose file path (default: "docker-compose.yml")
476
+ services?: string[]; // Services to start (default: all)
477
+ detach?: boolean; // Run in background (default: false)
478
+ build?: boolean; // Build images before starting (default: false)
479
+ forceRecreate?: boolean; // Force recreate containers (default: false)
480
+ cwd?: string; // Working directory
481
+ timeout?: number; // Command timeout in ms
482
+ }
483
+ ```
484
+
485
+ **Example:**
486
+ ```typescript
487
+ await client.exec(["docker", "compose", "up"], {
488
+ file: "docker-compose.prod.yml",
489
+ services: ["web", "db"],
490
+ detach: true,
491
+ build: true,
492
+ });
493
+ ```
494
+
495
+ #### `docker.compose.down`
496
+
497
+ Stop and remove services with Docker Compose.
498
+
499
+ **Input:**
500
+ ```typescript
501
+ {
502
+ file?: string; // Compose file path (default: "docker-compose.yml")
503
+ volumes?: boolean; // Remove volumes (default: false)
504
+ removeOrphans?: boolean; // Remove orphan containers (default: false)
505
+ rmi?: "all" | "local"; // Remove images (all or local only)
506
+ cwd?: string; // Working directory
507
+ timeout?: number; // Command timeout in ms
508
+ }
509
+ ```
510
+
511
+ **Example:**
512
+ ```typescript
513
+ await client.exec(["docker", "compose", "down"], {
514
+ volumes: true,
515
+ removeOrphans: true,
516
+ rmi: "local",
517
+ });
518
+ ```
519
+
520
+ ## Types
521
+
522
+ ### Input Types
523
+
524
+ All input types are exported with Zod schemas for validation:
525
+
526
+ ```typescript
527
+ import {
528
+ DockerRunInput,
529
+ DockerRunInputSchema,
530
+ DockerBuildInput,
531
+ DockerBuildInputSchema,
532
+ // ... etc
533
+ } from "@procedures/docker";
534
+
535
+ // Validate input
536
+ const parsed = DockerRunInputSchema.parse({
537
+ image: "nginx",
538
+ ports: ["80:80"],
539
+ });
540
+ ```
541
+
542
+ ### Output Type
543
+
544
+ ```typescript
545
+ export interface DockerCommandOutput {
546
+ /** Exit code of the command */
547
+ exitCode: number;
548
+ /** Standard output */
549
+ stdout: string;
550
+ /** Standard error */
551
+ stderr: string;
552
+ /** Whether command succeeded (exit code 0) */
553
+ success: boolean;
554
+ /** Duration in milliseconds */
555
+ duration: number;
556
+ }
557
+ ```
558
+
559
+ ## Integration with Ecosystem
560
+
561
+ ### With bundle-dev
562
+
563
+ The `bundle-dev` package includes `client-docker` for development workflows:
564
+
565
+ ```typescript
566
+ import "@procedures/bundle-dev"; // registers its procedures
567
+
568
+ // docker.* procedures are now available
569
+ await client.exec(["docker", "run"], { ... });
570
+ ```
571
+
572
+ ### With client-shell
573
+
574
+ `client-docker` uses `client-shell` under the hood:
575
+
576
+ ```typescript
577
+ // Internally, docker.run calls:
578
+ await ctx.client.call(["shell", "exec"], {
579
+ command: "docker run -d -p 8080:80 --name my-nginx nginx",
580
+ });
581
+ ```
582
+
583
+ ### With client-fs
584
+
585
+ Combine with `client-fs` for Dockerfile management:
586
+
587
+ ```typescript
588
+ // Write Dockerfile
589
+ await client.exec(["fs", "write"], {
590
+ path: "Dockerfile",
591
+ content: `
592
+ FROM node:20-alpine
593
+ WORKDIR /app
594
+ COPY . .
595
+ RUN npm ci --production
596
+ CMD ["node", "index.js"]
597
+ `.trim(),
598
+ });
599
+
600
+ // Build image
601
+ await client.exec(["docker", "build"], {
602
+ context: ".",
603
+ tag: "my-app:latest",
604
+ });
605
+ ```
606
+
607
+ ## Advanced Usage
608
+
609
+ ### Multi-Container Applications
610
+
611
+ ```typescript
612
+ // Start entire stack
613
+ await client.exec(["docker", "compose", "up"], {
614
+ detach: true,
615
+ build: true,
616
+ });
617
+
618
+ // Check service status
619
+ const containers = await client.exec(["docker", "ps"], {
620
+ all: false,
621
+ format: "json",
622
+ });
623
+
624
+ // View logs for specific service
625
+ const webLogs = await client.exec(["docker", "logs"], {
626
+ container: "myapp-web-1",
627
+ tail: 100,
628
+ });
629
+
630
+ // Clean up
631
+ await client.exec(["docker", "compose", "down"], {
632
+ volumes: true,
633
+ });
634
+ ```
635
+
636
+ ### Custom Build Process
637
+
638
+ ```typescript
639
+ // Multi-stage build with build args
640
+ await client.exec(["docker", "build"], {
641
+ context: ".",
642
+ tag: "my-app:production",
643
+ dockerfile: "Dockerfile.prod",
644
+ buildArgs: {
645
+ NODE_VERSION: "20",
646
+ NPM_TOKEN: process.env.NPM_TOKEN,
647
+ BUILD_ENV: "production",
648
+ },
649
+ noCache: true,
650
+ });
651
+
652
+ // Tag for registry
653
+ await client.exec(["shell", "exec"], {
654
+ command: "docker tag my-app:production registry.example.com/my-app:latest",
655
+ });
656
+
657
+ // Push to registry
658
+ await client.exec(["shell", "exec"], {
659
+ command: "docker push registry.example.com/my-app:latest",
660
+ });
661
+ ```
662
+
663
+ ### Container Health Checks
664
+
665
+ ```typescript
666
+ // Wait for container to be healthy
667
+ async function waitForHealthy(container: string, maxAttempts = 30) {
668
+ for (let i = 0; i < maxAttempts; i++) {
669
+ const result = await client.exec(["docker", "ps"], {
670
+ filter: `name=${container}`,
671
+ format: "json",
672
+ });
673
+
674
+ const containers = JSON.parse(result.stdout);
675
+ if (containers[0]?.Status?.includes("healthy")) {
676
+ return true;
677
+ }
678
+
679
+ await new Promise(resolve => setTimeout(resolve, 1000));
680
+ }
681
+
682
+ throw new Error(`Container ${container} did not become healthy`);
683
+ }
684
+ ```
685
+
686
+ ## Error Handling
687
+
688
+ ```typescript
689
+ try {
690
+ await client.exec(["docker", "run"], {
691
+ image: "nonexistent:image",
692
+ detach: true,
693
+ });
694
+ } catch (error) {
695
+ // Handle Docker errors
696
+ if (error.stderr?.includes("Unable to find image")) {
697
+ console.error("Image not found");
698
+ } else if (error.stderr?.includes("port is already allocated")) {
699
+ console.error("Port conflict");
700
+ }
701
+ }
702
+
703
+ // Check success field
704
+ const result = await client.exec(["docker", "build"], {
705
+ context: ".",
706
+ tag: "my-app",
707
+ });
708
+
709
+ if (!result.success) {
710
+ console.error("Build failed:", result.stderr);
711
+ throw new Error("Docker build failed");
712
+ }
713
+ ```
714
+
715
+ ## Performance Considerations
716
+
717
+ - **Detached Mode**: Use `detach: true` for long-running containers to avoid blocking
718
+ - **Build Cache**: Avoid `noCache: true` unless necessary to speed up builds
719
+ - **Timeouts**: Set appropriate timeouts for long-running operations (builds, pulls)
720
+ - **Cleanup**: Use `rm: true` for one-off containers to avoid accumulation
721
+ - **Docker Compose**: Prefer `docker.compose.up` for multi-container apps vs. multiple `docker.run` calls
722
+
723
+ ## Testing
724
+
725
+ The package includes both unit and integration tests:
726
+
727
+ ```bash
728
+ # Run all tests
729
+ pnpm test
730
+
731
+ # Unit tests only
732
+ pnpm test:unit
733
+
734
+ # Integration tests (requires Docker)
735
+ pnpm test:integration
736
+
737
+ # Watch mode
738
+ pnpm test:watch
739
+
740
+ # Coverage
741
+ pnpm test:coverage
742
+ ```
743
+
744
+ ## License
745
+
746
+ MIT
747
+
748
+ ## Related Packages
749
+
750
+ - `@procedures/client` - Core client/procedure system
751
+ - `@procedures/shell` - Shell command execution
752
+ - `@procedures/bundle-dev` - Development bundle
753
+ - `@procedures/fs` - Filesystem operations