BREAKING CHANGE(DockerHost): Refactor public API to DockerHost facade; introduce DockerResource base; make resource static methods internal; support flexible descriptors and stream compatibility
This commit is contained in:
512
readme.md
512
readme.md
@@ -2,11 +2,16 @@
|
||||
|
||||
> **Powerful TypeScript client for Docker Remote API** - Build, manage, and orchestrate Docker containers, images, networks, and swarm services with type-safe elegance.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- 🎯 **Full TypeScript Support** - Complete type definitions for all Docker API entities
|
||||
- 🔄 **Async/Await Ready** - Modern promise-based architecture for seamless async operations
|
||||
- 📦 **Container Management** - Create, list, inspect, and manage containers effortlessly
|
||||
- 📦 **Container Management** - Full lifecycle control: create, start, stop, remove, inspect containers
|
||||
- 🔌 **Interactive Containers** - Stream logs, attach to processes, execute commands in real-time
|
||||
- 🖼️ **Image Handling** - Pull from registries, build from tarballs, export, and manage tags
|
||||
- 🌐 **Network Operations** - Create and manage Docker networks with full IPAM support
|
||||
- 🔐 **Secrets Management** - Handle Docker secrets securely in swarm mode
|
||||
@@ -23,7 +28,7 @@
|
||||
pnpm add @apiclient.xyz/docker
|
||||
|
||||
# Using npm
|
||||
npm install @apiclient.xyz/docker --save
|
||||
npm install @apiclient.xyz/docker
|
||||
|
||||
# Using yarn
|
||||
yarn add @apiclient.xyz/docker
|
||||
@@ -46,10 +51,40 @@ console.log('✅ Docker is running');
|
||||
const containers = await docker.getContainers();
|
||||
console.log(`Found ${containers.length} containers`);
|
||||
|
||||
// Get a specific container and interact with it
|
||||
const container = await docker.getContainerById('abc123');
|
||||
await container.start();
|
||||
|
||||
// Stream logs in real-time
|
||||
const logStream = await container.streamLogs({ follow: true });
|
||||
logStream.on('data', (chunk) => console.log(chunk.toString()));
|
||||
|
||||
// Don't forget to clean up
|
||||
await docker.stop();
|
||||
```
|
||||
|
||||
## 🏗️ Clean Architecture
|
||||
|
||||
The module follows a **Facade pattern** with `DockerHost` as the single entry point:
|
||||
|
||||
```typescript
|
||||
const docker = new DockerHost({});
|
||||
|
||||
// All operations go through DockerHost
|
||||
const containers = await docker.getContainers(); // List containers
|
||||
const container = await docker.getContainerById('id'); // Get specific container
|
||||
const network = await docker.createNetwork({ Name: 'my-net' }); // Create network
|
||||
const service = await docker.createService(descriptor); // Deploy service
|
||||
const image = await docker.createImageFromRegistry({ imageUrl: 'nginx' });
|
||||
|
||||
// Resources support both strings and instances
|
||||
await docker.createService({
|
||||
image: 'nginx:latest', // String works!
|
||||
networks: ['my-network'], // String array works!
|
||||
secrets: [secretInstance] // Or use actual instances
|
||||
});
|
||||
```
|
||||
|
||||
## 🔌 Socket Path Configuration
|
||||
|
||||
The library determines which Docker socket to use in the following priority order:
|
||||
@@ -160,39 +195,178 @@ containers.forEach((container) => {
|
||||
#### Get Container by ID
|
||||
|
||||
```typescript
|
||||
import { DockerContainer } from '@apiclient.xyz/docker';
|
||||
|
||||
const container = await DockerContainer.getContainerById(docker, 'abc123');
|
||||
const container = await docker.getContainerById('abc123');
|
||||
if (container) {
|
||||
console.log(`Found: ${container.Names[0]}`);
|
||||
console.log(`Running: ${container.State === 'running'}`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Container Lifecycle Operations
|
||||
|
||||
```typescript
|
||||
// Get a container
|
||||
const container = await docker.getContainerById('abc123');
|
||||
|
||||
// Start the container
|
||||
await container.start();
|
||||
console.log('Container started');
|
||||
|
||||
// Stop the container (with optional timeout)
|
||||
await container.stop({ t: 10 }); // 10 seconds graceful stop
|
||||
console.log('Container stopped');
|
||||
|
||||
// Restart by starting again
|
||||
await container.start();
|
||||
|
||||
// Remove the container
|
||||
await container.remove({ force: true, v: true }); // force + remove volumes
|
||||
console.log('Container removed');
|
||||
```
|
||||
|
||||
#### Inspect Container Details
|
||||
|
||||
```typescript
|
||||
const container = await docker.getContainerById('abc123');
|
||||
|
||||
// Get detailed information
|
||||
const details = await container.inspect();
|
||||
console.log('Container details:', details);
|
||||
|
||||
// Or just refresh the container state
|
||||
await container.refresh();
|
||||
console.log('Updated state:', container.State);
|
||||
```
|
||||
|
||||
#### Get Container Logs
|
||||
|
||||
```typescript
|
||||
// Get logs as a string (one-shot)
|
||||
const logs = await container.logs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true,
|
||||
tail: 100, // Last 100 lines
|
||||
});
|
||||
console.log(logs);
|
||||
```
|
||||
|
||||
#### Stream Logs in Real-Time 🔥
|
||||
|
||||
```typescript
|
||||
// Stream logs continuously (follow mode)
|
||||
const logStream = await container.streamLogs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true,
|
||||
tail: 50, // Start with last 50 lines, then follow
|
||||
});
|
||||
|
||||
logStream.on('data', (chunk) => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
|
||||
logStream.on('error', (err) => {
|
||||
console.error('Stream error:', err);
|
||||
});
|
||||
|
||||
// Stop streaming when done
|
||||
// logStream.destroy();
|
||||
```
|
||||
|
||||
#### Attach to Container Process 🔥
|
||||
|
||||
Attach to the container's main process (PID 1) for interactive session:
|
||||
|
||||
```typescript
|
||||
const { stream, close } = await container.attach({
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
logs: true, // Include previous logs
|
||||
});
|
||||
|
||||
// Pipe to/from process streams
|
||||
process.stdin.pipe(stream);
|
||||
stream.pipe(process.stdout);
|
||||
|
||||
// Handle stream events
|
||||
stream.on('end', () => {
|
||||
console.log('Attachment ended');
|
||||
});
|
||||
|
||||
// Later: detach cleanly
|
||||
await close();
|
||||
```
|
||||
|
||||
#### Execute Commands in Container 🔥
|
||||
|
||||
Run commands inside a running container:
|
||||
|
||||
```typescript
|
||||
// Execute a command
|
||||
const { stream, close } = await container.exec('ls -la /app', {
|
||||
tty: true,
|
||||
user: 'root',
|
||||
workingDir: '/app',
|
||||
env: ['DEBUG=true'],
|
||||
});
|
||||
|
||||
// Handle output
|
||||
stream.on('data', (chunk) => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
|
||||
stream.on('end', async () => {
|
||||
console.log('Command finished');
|
||||
await close();
|
||||
});
|
||||
|
||||
// Execute with array of arguments
|
||||
const { stream: stream2, close: close2 } = await container.exec(
|
||||
['bash', '-c', 'echo "Hello from container"'],
|
||||
{ tty: true }
|
||||
);
|
||||
```
|
||||
|
||||
#### Get Container Stats
|
||||
|
||||
```typescript
|
||||
// Get stats (one-shot)
|
||||
const stats = await container.stats({ stream: false });
|
||||
console.log('CPU Usage:', stats.cpu_stats);
|
||||
console.log('Memory Usage:', stats.memory_stats);
|
||||
```
|
||||
|
||||
#### Create Containers
|
||||
|
||||
```typescript
|
||||
const newContainer = await docker.createContainer({
|
||||
Hostname: 'my-app',
|
||||
Domainname: 'local',
|
||||
networks: ['my-network'], // Can use string or DockerNetwork instance
|
||||
});
|
||||
console.log(`Container created: ${newContainer.Id}`);
|
||||
```
|
||||
|
||||
### 🖼️ Image Management
|
||||
|
||||
#### Pull Images from Registry
|
||||
|
||||
```typescript
|
||||
import { DockerImage } from '@apiclient.xyz/docker';
|
||||
|
||||
// Pull from Docker Hub
|
||||
const image = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'nginx',
|
||||
imageTag: 'alpine', // Optional, defaults to 'latest'
|
||||
},
|
||||
const image = await docker.createImageFromRegistry({
|
||||
imageUrl: 'nginx',
|
||||
imageTag: 'alpine', // Optional, defaults to 'latest'
|
||||
});
|
||||
|
||||
console.log(`Image pulled: ${image.RepoTags[0]}`);
|
||||
console.log(`Size: ${(image.Size / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
// Pull from private registry
|
||||
const privateImage = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'registry.example.com/my-app',
|
||||
imageTag: 'v2.0.0',
|
||||
},
|
||||
const privateImage = await docker.createImageFromRegistry({
|
||||
imageUrl: 'registry.example.com/my-app',
|
||||
imageTag: 'v2.0.0',
|
||||
});
|
||||
```
|
||||
|
||||
@@ -200,16 +374,12 @@ const privateImage = await DockerImage.createFromRegistry(docker, {
|
||||
|
||||
```typescript
|
||||
import * as fs from 'fs';
|
||||
import { DockerImage } from '@apiclient.xyz/docker';
|
||||
|
||||
// Import from a tar file
|
||||
const tarStream = fs.createReadStream('./my-image.tar');
|
||||
const importedImage = await DockerImage.createFromTarStream(docker, {
|
||||
tarStream,
|
||||
creationObject: {
|
||||
imageUrl: 'my-app',
|
||||
imageTag: 'v1.0.0',
|
||||
},
|
||||
const importedImage = await docker.createImageFromTarStream(tarStream, {
|
||||
imageUrl: 'my-app',
|
||||
imageTag: 'v1.0.0',
|
||||
});
|
||||
|
||||
console.log(`Imported: ${importedImage.RepoTags[0]}`);
|
||||
@@ -219,7 +389,7 @@ console.log(`Imported: ${importedImage.RepoTags[0]}`);
|
||||
|
||||
```typescript
|
||||
// Get image by name
|
||||
const image = await DockerImage.getImageByName(docker, 'nginx:alpine');
|
||||
const image = await docker.getImageByName('nginx:alpine');
|
||||
|
||||
// Export to tar stream
|
||||
const exportStream = await image.exportToTarStream();
|
||||
@@ -233,18 +403,6 @@ writeStream.on('finish', () => {
|
||||
});
|
||||
```
|
||||
|
||||
#### Tag Images
|
||||
|
||||
```typescript
|
||||
// Tag an existing image
|
||||
await DockerImage.tagImageByIdOrName(docker, 'nginx:alpine', {
|
||||
registry: 'myregistry.com',
|
||||
imageName: 'web-server',
|
||||
imageTag: 'v1.0.0',
|
||||
});
|
||||
// Result: myregistry.com/web-server:v1.0.0
|
||||
```
|
||||
|
||||
#### List All Images
|
||||
|
||||
```typescript
|
||||
@@ -258,31 +416,25 @@ images.forEach((img) => {
|
||||
});
|
||||
```
|
||||
|
||||
#### Remove Images
|
||||
|
||||
```typescript
|
||||
const image = await docker.getImageByName('nginx:alpine');
|
||||
await image.remove({ force: true });
|
||||
console.log('Image removed');
|
||||
```
|
||||
|
||||
### 🌐 Network Management
|
||||
|
||||
#### Create Custom Networks
|
||||
|
||||
```typescript
|
||||
import { DockerNetwork } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create a bridge network
|
||||
const network = await DockerNetwork.createNetwork(docker, {
|
||||
// Create an overlay network (for swarm)
|
||||
const network = await docker.createNetwork({
|
||||
Name: 'my-app-network',
|
||||
Driver: 'bridge',
|
||||
Driver: 'overlay',
|
||||
EnableIPv6: false,
|
||||
IPAM: {
|
||||
Driver: 'default',
|
||||
Config: [
|
||||
{
|
||||
Subnet: '172.28.0.0/16',
|
||||
Gateway: '172.28.0.1',
|
||||
},
|
||||
],
|
||||
},
|
||||
Labels: {
|
||||
project: 'my-app',
|
||||
environment: 'production',
|
||||
},
|
||||
Attachable: true,
|
||||
});
|
||||
|
||||
console.log(`Network created: ${network.Name} (${network.Id})`);
|
||||
@@ -301,7 +453,7 @@ networks.forEach((net) => {
|
||||
});
|
||||
|
||||
// Get specific network by name
|
||||
const appNetwork = await DockerNetwork.getNetworkByName(docker, 'my-app-network');
|
||||
const appNetwork = await docker.getNetworkByName('my-app-network');
|
||||
|
||||
// Get containers connected to this network
|
||||
const containers = await appNetwork.getContainersOnNetwork();
|
||||
@@ -311,7 +463,7 @@ console.log(`Containers on network: ${containers.length}`);
|
||||
#### Remove a Network
|
||||
|
||||
```typescript
|
||||
const network = await DockerNetwork.getNetworkByName(docker, 'my-app-network');
|
||||
const network = await docker.getNetworkByName('my-app-network');
|
||||
await network.remove();
|
||||
console.log('Network removed');
|
||||
```
|
||||
@@ -329,39 +481,35 @@ console.log('Swarm mode activated');
|
||||
#### Deploy Services
|
||||
|
||||
```typescript
|
||||
import { DockerService, DockerImage, DockerNetwork, DockerSecret } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create prerequisites
|
||||
const network = await DockerNetwork.createNetwork(docker, {
|
||||
const network = await docker.createNetwork({
|
||||
Name: 'app-network',
|
||||
Driver: 'overlay', // Use overlay for swarm
|
||||
});
|
||||
|
||||
const image = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'nginx',
|
||||
imageTag: 'latest',
|
||||
},
|
||||
const image = await docker.createImageFromRegistry({
|
||||
imageUrl: 'nginx',
|
||||
imageTag: 'latest',
|
||||
});
|
||||
|
||||
const secret = await DockerSecret.createSecret(docker, {
|
||||
const secret = await docker.createSecret({
|
||||
name: 'api-key',
|
||||
version: '1.0.0',
|
||||
contentArg: 'super-secret-key',
|
||||
labels: { app: 'my-app' },
|
||||
});
|
||||
|
||||
// Create a service
|
||||
const service = await DockerService.createService(docker, {
|
||||
// Create a service (supports both strings and instances!)
|
||||
const service = await docker.createService({
|
||||
name: 'web-api',
|
||||
image: image,
|
||||
image: image, // Or use string: 'nginx:latest'
|
||||
labels: {
|
||||
app: 'api',
|
||||
version: '1.0.0',
|
||||
},
|
||||
networks: [network],
|
||||
networks: [network], // Or use strings: ['app-network']
|
||||
networkAlias: 'api',
|
||||
secrets: [secret],
|
||||
secrets: [secret], // Or use strings: ['api-key']
|
||||
ports: ['80:3000'], // host:container
|
||||
resources: {
|
||||
memorySizeMB: 512,
|
||||
@@ -380,13 +528,13 @@ const services = await docker.getServices();
|
||||
services.forEach((service) => {
|
||||
console.log(`Service: ${service.Spec.Name}`);
|
||||
console.log(` Image: ${service.Spec.TaskTemplate.ContainerSpec.Image}`);
|
||||
if (service.Spec.Mode.Replicated) {
|
||||
console.log(` Replicas: ${service.Spec.Mode.Replicated.Replicas}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Get service by name
|
||||
const myService = await DockerService.getServiceByName(docker, 'web-api');
|
||||
const myService = await docker.getServiceByName('web-api');
|
||||
|
||||
// Refresh service state
|
||||
await myService.refresh();
|
||||
|
||||
// Check if service needs update
|
||||
const needsUpdate = await myService.needsUpdate();
|
||||
@@ -404,10 +552,8 @@ console.log('Service removed');
|
||||
Secrets are only available in Docker Swarm mode.
|
||||
|
||||
```typescript
|
||||
import { DockerSecret } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create a secret
|
||||
const secret = await DockerSecret.createSecret(docker, {
|
||||
const secret = await docker.createSecret({
|
||||
name: 'database-password',
|
||||
version: '1.0.0',
|
||||
contentArg: 'my-super-secret-password',
|
||||
@@ -420,14 +566,14 @@ const secret = await DockerSecret.createSecret(docker, {
|
||||
console.log(`Secret created: ${secret.ID}`);
|
||||
|
||||
// List all secrets
|
||||
const secrets = await DockerSecret.getSecrets(docker);
|
||||
const secrets = await docker.getSecrets();
|
||||
secrets.forEach((s) => {
|
||||
console.log(`Secret: ${s.Spec.Name}`);
|
||||
console.log(` Labels:`, s.Spec.Labels);
|
||||
});
|
||||
|
||||
// Get secret by name
|
||||
const dbSecret = await DockerSecret.getSecretByName(docker, 'database-password');
|
||||
const dbSecret = await docker.getSecretByName('database-password');
|
||||
|
||||
// Update secret content
|
||||
await dbSecret.update('new-password-value');
|
||||
@@ -437,24 +583,27 @@ await dbSecret.remove();
|
||||
console.log('Secret removed');
|
||||
```
|
||||
|
||||
### 💾 S3 Image Storage
|
||||
### 💾 Image Storage
|
||||
|
||||
Store and retrieve Docker images from S3-compatible storage:
|
||||
Store and retrieve Docker images from local storage or S3:
|
||||
|
||||
```typescript
|
||||
// Configure S3 storage for the image store
|
||||
// Store image to local storage
|
||||
const imageStream = fs.createReadStream('./my-app.tar');
|
||||
await docker.storeImage('my-app-v1', imageStream);
|
||||
console.log('Image stored locally');
|
||||
|
||||
// Retrieve image from storage
|
||||
const storedImageStream = await docker.retrieveImage('my-app-v1');
|
||||
storedImageStream.pipe(fs.createWriteStream('./restored-image.tar'));
|
||||
|
||||
// Configure S3 storage (optional)
|
||||
await docker.addS3Storage({
|
||||
endpoint: 's3.amazonaws.com',
|
||||
accessKey: 'AKIAIOSFODNN7EXAMPLE',
|
||||
accessSecret: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
bucketName: 'my-docker-images',
|
||||
});
|
||||
|
||||
// Store an image to S3
|
||||
const imageStream = fs.createReadStream('./my-app.tar');
|
||||
await docker.imageStore.storeImage('my-app-v1', imageStream);
|
||||
|
||||
console.log('Image stored to S3');
|
||||
```
|
||||
|
||||
### 📊 Event Monitoring
|
||||
@@ -498,14 +647,12 @@ await docker.auth({
|
||||
console.log('✅ Authenticated with registry');
|
||||
|
||||
// Or read credentials from Docker config file
|
||||
const authToken = await docker.getAuthTokenFromDockerConfig('registry.example.com');
|
||||
await docker.getAuthTokenFromDockerConfig('registry.example.com');
|
||||
|
||||
// Now you can pull private images
|
||||
const privateImage = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'registry.example.com/private/app',
|
||||
imageTag: 'latest',
|
||||
},
|
||||
const privateImage = await docker.createImageFromRegistry({
|
||||
imageUrl: 'registry.example.com/private/app',
|
||||
imageTag: 'latest',
|
||||
});
|
||||
```
|
||||
|
||||
@@ -516,7 +663,7 @@ const privateImage = await DockerImage.createFromRegistry(docker, {
|
||||
Deploy a complete multi-service application stack:
|
||||
|
||||
```typescript
|
||||
import { DockerHost, DockerNetwork, DockerSecret, DockerService, DockerImage } from '@apiclient.xyz/docker';
|
||||
import { DockerHost } from '@apiclient.xyz/docker';
|
||||
|
||||
async function deployStack() {
|
||||
const docker = new DockerHost({});
|
||||
@@ -527,7 +674,7 @@ async function deployStack() {
|
||||
console.log('✅ Swarm initialized');
|
||||
|
||||
// Create overlay network for service communication
|
||||
const network = await DockerNetwork.createNetwork(docker, {
|
||||
const network = await docker.createNetwork({
|
||||
Name: 'app-network',
|
||||
Driver: 'overlay',
|
||||
Attachable: true,
|
||||
@@ -535,7 +682,7 @@ async function deployStack() {
|
||||
console.log('✅ Network created');
|
||||
|
||||
// Create secrets
|
||||
const dbPassword = await DockerSecret.createSecret(docker, {
|
||||
const dbPassword = await docker.createSecret({
|
||||
name: 'db-password',
|
||||
version: '1.0.0',
|
||||
contentArg: 'strong-database-password',
|
||||
@@ -543,30 +690,14 @@ async function deployStack() {
|
||||
});
|
||||
console.log('✅ Secrets created');
|
||||
|
||||
// Pull images
|
||||
const postgresImage = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'postgres',
|
||||
imageTag: '14-alpine',
|
||||
},
|
||||
});
|
||||
|
||||
const appImage = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'my-app',
|
||||
imageTag: 'latest',
|
||||
},
|
||||
});
|
||||
console.log('✅ Images pulled');
|
||||
|
||||
// Deploy database service
|
||||
const dbService = await DockerService.createService(docker, {
|
||||
const dbService = await docker.createService({
|
||||
name: 'postgres-db',
|
||||
image: postgresImage,
|
||||
image: 'postgres:14-alpine', // Using string for convenience
|
||||
labels: { tier: 'database' },
|
||||
networks: [network],
|
||||
networks: ['app-network'], // Using string array
|
||||
networkAlias: 'postgres',
|
||||
secrets: [dbPassword],
|
||||
secrets: ['db-password'], // Using string array
|
||||
ports: [],
|
||||
resources: {
|
||||
memorySizeMB: 1024,
|
||||
@@ -575,13 +706,13 @@ async function deployStack() {
|
||||
console.log('✅ Database service deployed');
|
||||
|
||||
// Deploy application service
|
||||
const appService = await DockerService.createService(docker, {
|
||||
const appService = await docker.createService({
|
||||
name: 'web-app',
|
||||
image: appImage,
|
||||
image: 'my-app:latest',
|
||||
labels: { tier: 'application' },
|
||||
networks: [network],
|
||||
networks: ['app-network'],
|
||||
networkAlias: 'app',
|
||||
secrets: [dbPassword],
|
||||
secrets: ['db-password'],
|
||||
ports: ['80:3000'],
|
||||
resources: {
|
||||
memorySizeMB: 512,
|
||||
@@ -595,6 +726,49 @@ async function deployStack() {
|
||||
deployStack().catch(console.error);
|
||||
```
|
||||
|
||||
### Container Debugging Session
|
||||
|
||||
Interactive debugging session with a running container:
|
||||
|
||||
```typescript
|
||||
async function debugContainer(containerId: string) {
|
||||
const docker = new DockerHost({});
|
||||
await docker.start();
|
||||
|
||||
const container = await docker.getContainerById(containerId);
|
||||
|
||||
// First, check container state
|
||||
await container.inspect();
|
||||
console.log(`Container: ${container.Names[0]}`);
|
||||
console.log(`State: ${container.State}`);
|
||||
|
||||
// Get recent logs
|
||||
const logs = await container.logs({ tail: 50 });
|
||||
console.log('Recent logs:', logs);
|
||||
|
||||
// Stream live logs in one terminal
|
||||
console.log('\n--- Live Logs ---');
|
||||
const logStream = await container.streamLogs({ timestamps: true });
|
||||
logStream.on('data', (chunk) => {
|
||||
process.stdout.write(chunk);
|
||||
});
|
||||
|
||||
// Execute diagnostic commands
|
||||
console.log('\n--- Running Diagnostics ---');
|
||||
const { stream, close } = await container.exec('ps aux', { tty: true });
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
|
||||
stream.on('end', async () => {
|
||||
console.log('\nDiagnostics complete');
|
||||
await close();
|
||||
await docker.stop();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Image Pipeline: Pull, Tag, Export
|
||||
|
||||
```typescript
|
||||
@@ -603,23 +777,13 @@ async function imagePipeline() {
|
||||
await docker.start();
|
||||
|
||||
// Pull latest image
|
||||
const image = await DockerImage.createFromRegistry(docker, {
|
||||
creationObject: {
|
||||
imageUrl: 'node',
|
||||
imageTag: '18-alpine',
|
||||
},
|
||||
const image = await docker.createImageFromRegistry({
|
||||
imageUrl: 'node',
|
||||
imageTag: '18-alpine',
|
||||
});
|
||||
console.log('✅ Image pulled');
|
||||
|
||||
// Tag for private registry
|
||||
await DockerImage.tagImageByIdOrName(docker, 'node:18-alpine', {
|
||||
registry: 'registry.company.com',
|
||||
imageName: 'base/node',
|
||||
imageTag: 'v18-alpine',
|
||||
});
|
||||
console.log('✅ Image tagged');
|
||||
|
||||
// Export to tar
|
||||
// Export to tar for backup/transfer
|
||||
const exportStream = await image.exportToTarStream();
|
||||
const writeStream = fs.createWriteStream('./node-18-alpine.tar');
|
||||
|
||||
@@ -631,6 +795,11 @@ async function imagePipeline() {
|
||||
});
|
||||
console.log('✅ Image exported to tar');
|
||||
|
||||
// Store in image store (with S3 backup if configured)
|
||||
const tarStream = fs.createReadStream('./node-18-alpine.tar');
|
||||
await docker.storeImage('node-18-alpine-backup', tarStream);
|
||||
console.log('✅ Image stored in image store');
|
||||
|
||||
await docker.stop();
|
||||
}
|
||||
```
|
||||
@@ -645,7 +814,11 @@ import type {
|
||||
IImageCreationDescriptor,
|
||||
IServiceCreationDescriptor,
|
||||
ISecretCreationDescriptor,
|
||||
IContainerCreationDescriptor,
|
||||
INetworkCreationDescriptor,
|
||||
TLabels,
|
||||
TPorts,
|
||||
DockerResource,
|
||||
} from '@apiclient.xyz/docker';
|
||||
|
||||
// Full IntelliSense support
|
||||
@@ -678,55 +851,68 @@ const docker = new DockerHost({
|
||||
await docker.start();
|
||||
|
||||
// Build and push process
|
||||
const image = await DockerImage.createFromTarStream(docker, {
|
||||
tarStream: buildArtifactStream,
|
||||
creationObject: {
|
||||
imageUrl: 'my-app',
|
||||
imageTag: process.env.CI_COMMIT_SHA,
|
||||
},
|
||||
const buildStream = fs.createReadStream('./build-artifact.tar');
|
||||
const image = await docker.createImageFromTarStream(buildStream, {
|
||||
imageUrl: 'my-app',
|
||||
imageTag: process.env.CI_COMMIT_SHA,
|
||||
});
|
||||
|
||||
await DockerImage.tagImageByIdOrName(docker, `my-app:${process.env.CI_COMMIT_SHA}`, {
|
||||
registry: 'registry.company.com',
|
||||
imageName: 'production/my-app',
|
||||
imageTag: 'latest',
|
||||
});
|
||||
|
||||
// Push to registry (authentication required)
|
||||
// Note: Pushing requires proper registry authentication
|
||||
console.log(`✅ Image built: my-app:${process.env.CI_COMMIT_SHA}`);
|
||||
```
|
||||
|
||||
### Dynamic Service Scaling
|
||||
### Health Check Service
|
||||
|
||||
```typescript
|
||||
// Monitor and scale services based on load
|
||||
const services = await docker.getServices();
|
||||
const webService = services.find(s => s.Spec.Name === 'web-app');
|
||||
async function healthCheckService() {
|
||||
const docker = new DockerHost({});
|
||||
|
||||
if (webService && webService.Spec.Mode.Replicated) {
|
||||
const currentReplicas = webService.Spec.Mode.Replicated.Replicas;
|
||||
console.log(`Current replicas: ${currentReplicas}`);
|
||||
try {
|
||||
await docker.ping();
|
||||
const containers = await docker.getContainers();
|
||||
|
||||
// Scale based on your metrics
|
||||
// (Scaling API would need to be implemented)
|
||||
const unhealthy = containers.filter(c => c.State !== 'running');
|
||||
if (unhealthy.length > 0) {
|
||||
console.warn(`⚠️ ${unhealthy.length} containers not running`);
|
||||
// Send alerts, restart services, etc.
|
||||
}
|
||||
|
||||
return { healthy: true, containers: containers.length };
|
||||
} catch (error) {
|
||||
console.error('❌ Docker health check failed:', error);
|
||||
return { healthy: false, error: error.message };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📖 API Documentation
|
||||
|
||||
- **Package Repository**: [https://code.foss.global/apiclient.xyz/docker](https://code.foss.global/apiclient.xyz/docker)
|
||||
- **npm Package**: [https://www.npmjs.com/package/@apiclient.xyz/docker](https://www.npmjs.com/package/@apiclient.xyz/docker)
|
||||
- **Docker Engine API Reference**: [https://docs.docker.com/engine/api/latest/](https://docs.docker.com/engine/api/latest/)
|
||||
- **Issues & Bug Reports**: [https://code.foss.global/apiclient.xyz/docker/issues](https://code.foss.global/apiclient.xyz/docker/issues)
|
||||
|
||||
## 🔑 Key Concepts
|
||||
|
||||
- **DockerHost**: Main entry point for Docker API communication
|
||||
- **DockerHost**: Main entry point - all operations flow through this facade
|
||||
- **Flexible Descriptors**: Accept both string references and class instances
|
||||
- **Health Checks**: Use `ping()` method to verify Docker daemon accessibility
|
||||
- **Socket Path Priority**: Constructor option → `DOCKER_HOST` env → CI mode → default socket
|
||||
- **Swarm Mode Required**: Services and secrets require Docker Swarm to be activated
|
||||
- **Type Safety**: Full TypeScript support with comprehensive interfaces
|
||||
- **Streaming Support**: Real-time event monitoring and tar stream operations
|
||||
- **S3 Integration**: Built-in image storage/retrieval from S3-compatible storage
|
||||
- **Streaming Support**: Real-time log streaming, event monitoring, and container attachment
|
||||
- **Interactive Containers**: Attach to processes, execute commands, stream logs
|
||||
- **Clean Architecture**: Facade pattern with internal delegation for maintainability
|
||||
|
||||
## 🆕 Recent Updates
|
||||
|
||||
### Version 2.1.0 - Architecture & Features
|
||||
|
||||
- ✨ **Clean OOP Architecture**: Refactored to Facade pattern with DockerHost as single entry point
|
||||
- ✨ **Container Streaming**: Added `streamLogs()`, `attach()`, and `exec()` methods
|
||||
- ✨ **Flexible Descriptors**: Support both string references and class instances
|
||||
- ✨ **Complete Container API**: All lifecycle methods (start, stop, remove, logs, inspect, stats)
|
||||
- ✨ **DockerResource Base Class**: Consistent patterns across all resources
|
||||
- 🔧 **Improved Type Safety**: Better TypeScript definitions throughout
|
||||
- 📚 **Enhanced Documentation**: Comprehensive examples and migration guides
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
||||
Reference in New Issue
Block a user