Files
docker/readme.hints.md

391 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<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:**
```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<string, string[]>; // 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<void>;
inspect: () => Promise<IExecInspectInfo>; // 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<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:**
```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