12 KiB
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:
// New interface properties:
export interface INetworkCreationDescriptor {
Name: string;
Driver?: 'bridge' | 'overlay' | 'host' | 'none' | 'macvlan'; // NEW
Attachable?: boolean; // NEW
Labels?: Record<string, string>; // NEW
IPAM?: { // NEW - IP Address Management
Driver?: string;
Config?: Array<{
Subnet?: string;
Gateway?: string;
IPRange?: string;
AuxiliaryAddresses?: Record<string, string>;
}>;
};
Internal?: boolean; // NEW
EnableIPv6?: boolean; // NEW
}
Usage Example:
// 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 interfacets/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:
{
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:
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- AddedgetVersion()method afterping()
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:
{
dangling?: boolean; // Remove untagged images
filters?: Record<string, string[]>; // Custom filters (until, label, etc.)
}
Returns:
{
ImagesDeleted: Array<{ Untagged?: string; Deleted?: string }>;
SpaceReclaimed: number; // Bytes freed
}
Usage Example:
// 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- AddedpruneImages()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_isreadyexit 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:
{
stream: Duplex;
close: () => Promise<void>;
inspect: () => Promise<IExecInspectInfo>; // NEW
}
IExecInspectInfo Interface:
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:
// 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- AddedIExecInspectInfointerfacets/classes.container.ts- Updatedexec()return type and addedinspect()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
.messageproperty
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):
// Before (buggy):
public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise<DockerContainer> {
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<DockerContainer | undefined> {
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<DockerContainer | undefined> - Cannot create invalid objects -
.find()naturally returns undefined - Users can now properly check for non-existent containers
Usage:
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
DockerResourcebase 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:
// 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 containercontainer.stop(options?)- Stop containercontainer.remove(options?)- Remove containercontainer.refresh()- Reload state
Information:
container.inspect()- Get detailed infocontainer.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 streamcontainer.exec(command, options)- Execute commands in container interactively
Example - Stream Logs:
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:
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:
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
dockerHostproperty (notdockerHostRef) - Required
refresh()method - Standardized constructor pattern
4. ImageStore Encapsulation
dockerHost.imageStoreis 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:
// 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:
plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream)
Files Modified
-
ts/classes.host.ts:requestStreaming()- Converts web stream to Node.js stream before returninggetEventObservable()- Works with converted Node.js stream
-
ts/classes.image.ts:createFromTarStream()- Uses converted Node.js stream for event handlingexportToTarStream()- 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
nodewebhelpersmodule provides bidirectional conversion utilities between web and Node.js streams