# Docker Module - Development Hints ## New Features (2025-11-25 - v5.1.0) ### 1. Enhanced Network Creation with Full Configuration Support **Problem:** Users were unable to create non-overlay networks or customize network configuration. The `INetworkCreationDescriptor` interface only had a `Name` property, and `DockerNetwork._create()` hardcoded `Driver: 'overlay'`. **Solution:** Expanded the interface and implementation to support all Docker network configuration options: ```typescript // New interface properties: export interface INetworkCreationDescriptor { Name: string; Driver?: 'bridge' | 'overlay' | 'host' | 'none' | 'macvlan'; // NEW Attachable?: boolean; // NEW Labels?: Record; // NEW IPAM?: { // NEW - IP Address Management Driver?: string; Config?: Array<{ Subnet?: string; Gateway?: string; IPRange?: string; AuxiliaryAddresses?: Record; }>; }; Internal?: boolean; // NEW EnableIPv6?: boolean; // NEW } ``` **Usage Example:** ```typescript // Create bridge network with custom IPAM const network = await docker.createNetwork({ Name: 'custom-bridge', Driver: 'bridge', IPAM: { Config: [{ Subnet: '172.20.0.0/16', Gateway: '172.20.0.1', }] }, Labels: { environment: 'production' }, }); ``` **Files Modified:** - `ts/interfaces/network.ts` - Added all missing properties to interface - `ts/classes.network.ts` - Updated `_create()` to pass through descriptor properties instead of hardcoding ### 2. Docker Daemon Version Information **Added:** `dockerHost.getVersion()` method to retrieve Docker daemon version information. **Purpose:** Essential for API compatibility checking, debugging, and ensuring minimum Docker version requirements. **Returns:** ```typescript { Version: string; // e.g., "20.10.21" ApiVersion: string; // e.g., "1.41" MinAPIVersion?: string; // Minimum supported API version GitCommit: string; GoVersion: string; Os: string; // e.g., "linux" Arch: string; // e.g., "amd64" KernelVersion: string; BuildTime?: string; } ``` **Usage Example:** ```typescript const version = await docker.getVersion(); console.log(`Docker ${version.Version} (API ${version.ApiVersion})`); console.log(`Platform: ${version.Os}/${version.Arch}`); ``` **Files Modified:** - `ts/classes.host.ts` - Added `getVersion()` method after `ping()` ### 3. Image Pruning for Disk Space Management **Added:** `dockerHost.pruneImages(options?)` method to clean up unused images. **Purpose:** Automated disk space management, CI/CD cleanup, scheduled maintenance tasks. **Options:** ```typescript { dangling?: boolean; // Remove untagged images filters?: Record; // Custom filters (until, label, etc.) } ``` **Returns:** ```typescript { ImagesDeleted: Array<{ Untagged?: string; Deleted?: string }>; SpaceReclaimed: number; // Bytes freed } ``` **Usage Example:** ```typescript // Remove dangling images const result = await docker.pruneImages({ dangling: true }); console.log(`Reclaimed: ${(result.SpaceReclaimed / 1024 / 1024).toFixed(2)} MB`); // Remove old images (older than 7 days) await docker.pruneImages({ filters: { until: ['168h'] } }); ``` **Files Modified:** - `ts/classes.host.ts` - Added `pruneImages()` method with filter support ### 4. Exec Command Exit Codes and Inspection **Problem:** Users could not determine if exec commands succeeded or failed. The `container.exec()` method returned a stream but provided no way to access exit codes, which are essential for: - Health checks (e.g., `pg_isready` exit code) - Test automation (npm test success/failure) - Deployment validation (migration checks) - Container readiness probes **Solution:** Added `inspect()` method to `exec()` return value that provides comprehensive execution information. **New Return Type:** ```typescript { stream: Duplex; close: () => Promise; inspect: () => Promise; // NEW } ``` **IExecInspectInfo Interface:** ```typescript export interface IExecInspectInfo { ExitCode: number; // 0 = success, non-zero = failure Running: boolean; // Whether exec is still running Pid: number; // Process ID ContainerID: string; // Container where exec ran ID: string; // Exec instance ID OpenStderr: boolean; OpenStdin: boolean; OpenStdout: boolean; CanRemove: boolean; DetachKeys: string; ProcessConfig: { tty: boolean; entrypoint: string; arguments: string[]; privileged: boolean; }; } ``` **Usage Example:** ```typescript // Health check with exit code const { stream, close, inspect } = await container.exec('pg_isready -U postgres'); stream.on('end', async () => { const info = await inspect(); if (info.ExitCode === 0) { console.log('✅ Database is ready'); } else { console.log(`❌ Database check failed (exit code ${info.ExitCode})`); } await close(); }); ``` **Real-World Use Cases Enabled:** - Health checks: Verify service readiness with proper exit code handling - Test automation: Run tests in container and determine pass/fail - Deployment validation: Execute migration checks and verify success - CI/CD pipelines: Run build/test commands and get accurate results **Files Modified:** - `ts/interfaces/container.ts` - Added `IExecInspectInfo` interface - `ts/classes.container.ts` - Updated `exec()` return type and added `inspect()` implementation ### Implementation Notes All changes are non-breaking additions that enhance existing functionality: - Network creation: New optional properties with sensible defaults - getVersion(): New method, no changes to existing APIs - pruneImages(): New method, no changes to existing APIs - exec() inspect(): Added to return value, existing stream/close properties unchanged ## getContainerById() Bug Fix (2025-11-24 - v5.0.1) ### Problem The `getContainerById()` method had a critical bug where it would create a DockerContainer object from Docker API error responses when a container didn't exist. **Symptoms:** - Calling `docker.getContainerById('invalid-id')` returned a DockerContainer object with `{ message: "No such container: invalid-id" }` - Calling `.logs()` on this invalid container returned "[object Object]" instead of logs or throwing an error - No way to detect the error state without checking for a `.message` property **Root Cause:** The `DockerContainer._fromId()` method made a direct API call to `/containers/{id}/json` and blindly passed `response.body` to the constructor, even when the API returned a 404 error response. ### Solution Changed `DockerContainer._fromId()` to use the **list+filter pattern**, matching the behavior of all other resource getter methods (DockerImage, DockerNetwork, DockerService, DockerSecret): ```typescript // Before (buggy): public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise { const response = await dockerHostArg.request('GET', `/containers/${containerId}/json`); return new DockerContainer(dockerHostArg, response.body); // Creates invalid object from error! } // After (fixed): public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise { const containers = await this._list(dockerHostArg); return containers.find((container) => container.Id === containerId); // Returns undefined if not found } ``` **Benefits:** - 100% consistent with all other resource classes - Type-safe return signature: `Promise` - Cannot create invalid objects - `.find()` naturally returns undefined - Users can now properly check for non-existent containers **Usage:** ```typescript const container = await docker.getContainerById('abc123'); if (container) { const logs = await container.logs(); console.log(logs); } else { console.log('Container not found'); } ``` ## OOP Refactoring - Clean Architecture (2025-11-24) ### Architecture Changes The module has been restructured to follow a clean OOP Facade pattern: - **DockerHost** is now the single entry point for all Docker operations - All resource classes extend abstract `DockerResource` base class - Static methods are prefixed with `_` to indicate internal use - Public API is exclusively through DockerHost methods ### Key Changes **1. Factory Pattern** - All resource creation/retrieval goes through DockerHost: ```typescript // Old (deprecated): const container = await DockerContainer.getContainers(dockerHost); const network = await DockerNetwork.createNetwork(dockerHost, descriptor); // New (clean API): const containers = await dockerHost.listContainers(); const network = await dockerHost.createNetwork(descriptor); ``` **2. Container Management Methods Added** The DockerContainer class now has full CRUD and streaming operations: **Lifecycle:** - `container.start()` - Start container - `container.stop(options?)` - Stop container - `container.remove(options?)` - Remove container - `container.refresh()` - Reload state **Information:** - `container.inspect()` - Get detailed info - `container.logs(options)` - Get logs as string (one-shot) - `container.stats(options)` - Get stats **Streaming & Interactive:** - `container.streamLogs(options)` - Stream logs continuously (follow mode) - `container.attach(options)` - Attach to main process (PID 1) with bidirectional stream - `container.exec(command, options)` - Execute commands in container interactively **Example - Stream Logs:** ```typescript const container = await dockerHost.getContainerById('abc123'); const logStream = await container.streamLogs({ timestamps: true }); logStream.on('data', (chunk) => { console.log(chunk.toString()); }); ``` **Example - Attach to Container:** ```typescript const { stream, close } = await container.attach({ stdin: true, stdout: true, stderr: true }); // Pipe to/from process process.stdin.pipe(stream); stream.pipe(process.stdout); // Later: detach await close(); ``` **Example - Execute Command:** ```typescript const { stream, close } = await container.exec('ls -la /app', { tty: true }); stream.on('data', (chunk) => { console.log(chunk.toString()); }); stream.on('end', async () => { await close(); }); ``` **3. DockerResource Base Class** All resource classes now extend `DockerResource`: - Consistent `dockerHost` property (not `dockerHostRef`) - Required `refresh()` method - Standardized constructor pattern **4. ImageStore Encapsulation** - `dockerHost.imageStore` is now private - Use `dockerHost.storeImage(name, stream)` instead - Use `dockerHost.retrieveImage(name)` instead **5. Creation Descriptors Support Both Primitives and Instances** Interfaces now accept both strings and class instances: ```typescript // Both work: await dockerHost.createService({ image: 'nginx:latest', // String networks: ['my-network'], // String array secrets: ['my-secret'] // String array }); await dockerHost.createService({ image: imageInstance, // DockerImage instance networks: [networkInstance], // DockerNetwork array secrets: [secretInstance] // DockerSecret array }); ``` ### Migration Guide Replace all static method calls with dockerHost methods: - `DockerContainer.getContainers(host)` → `dockerHost.listContainers()` - `DockerImage.createFromRegistry(host, opts)` → `dockerHost.createImageFromRegistry(opts)` - `DockerService.createService(host, desc)` → `dockerHost.createService(desc)` - `dockerHost.imageStore.storeImage(...)` → `dockerHost.storeImage(...)` ## smartrequest v5+ Migration (2025-11-17) ### Breaking Change smartrequest v5.0.0+ returns web `ReadableStream` objects (Web Streams API) instead of Node.js streams. ### Solution Implemented All streaming methods now convert web ReadableStreams to Node.js streams using: ```typescript plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream) ``` ### Files Modified - `ts/classes.host.ts`: - `requestStreaming()` - Converts web stream to Node.js stream before returning - `getEventObservable()` - Works with converted Node.js stream - `ts/classes.image.ts`: - `createFromTarStream()` - Uses converted Node.js stream for event handling - `exportToTarStream()` - Uses converted Node.js stream for backpressure management ### Testing - Build:  All 11 type errors resolved - Tests:  Node.js tests pass (DockerHost, DockerContainer, DockerImage, DockerImageStore) ### Notes - The conversion maintains backward compatibility with existing code expecting Node.js stream methods (`.on()`, `.emit()`, `.pause()`, `.resume()`) - smartstream's `nodewebhelpers` module provides bidirectional conversion utilities between web and Node.js streams