Перейти к основному содержимому

Docker Container Lifecycle

Implementation Reference

Source Files:

  • packages/core/src/adapters/docker-adapter.ts - Docker adapter implementation
  • packages/core/src/docker/docker-client.ts - Docker API client
  • packages/core/src/docker/container.ts - Container management
  • packages/core/src/docker/types.ts - Docker type definitions
  • apps/xec/src/commands/in.ts - Container execution command

Key Functions:

  • DockerAdapter.execute() - Main execution entry point
  • DockerClient.exec() - Execute commands in containers
  • DockerClient.run() - Run new containers
  • DockerClient.start() - Start stopped containers
  • DockerClient.stop() - Stop running containers
  • DockerClient.inspect() - Get container details
  • ContainerManager.waitForHealthy() - Wait for container readiness

Overview

Xec provides comprehensive Docker container lifecycle management through the @xec-sh/core execution engine. This enables seamless command execution in containers, container management, and integration with Docker Compose.

Container States

State Transitions

Docker containers managed by Xec follow standard Docker state transitions:

Created → Running → Paused → Stopped → Removed
↓ ↑
Restarting

State Detection (via DockerClient.inspect()):

  • running - Container is actively executing
  • paused - Container execution is paused
  • restarting - Container is restarting
  • exited - Container has stopped
  • dead - Container is dead (unrecoverable)
  • created - Container created but not started

Container Execution

Direct Execution

Execute commands in existing containers using the Docker adapter:

import { $ } from '@xec-sh/core';

// Execute in running container
const result = await $.docker('my-container')`ls -la /app`;
console.log(result.stdout);

// Execute with working directory
const result = await $.docker('my-container', {
cwd: '/app'
})`npm test`;

// Execute as specific user
const result = await $.docker('my-container', {
user: 'node'
})`whoami`;

CLI Execution

Use the in command for container execution:

# Execute command in container
xec in my-container ls -la

# Execute in specific container of a pod
xec in my-container:app "npm start"

# Interactive shell
xec in my-container /bin/bash

Container Management

Starting Containers

Start stopped containers or create new ones:

// Start existing container
await $.docker.start('my-container');

// Run new container from image
const container = await $.docker.run('node:18', {
name: 'my-app',
detach: true,
ports: ['3000:3000'],
volumes: ['./app:/app'],
env: {
NODE_ENV: 'production'
}
});

// Run with command
await $.docker.run('alpine', {
command: ['echo', 'Hello World'],
rm: true // Remove after exit
});

Stopping Containers

Stop running containers gracefully:

// Stop with default timeout (10s)
await $.docker.stop('my-container');

// Stop with custom timeout
await $.docker.stop('my-container', {
timeout: 30 // 30 seconds
});

// Force stop (SIGKILL)
await $.docker.kill('my-container');

Restarting Containers

Restart containers with optional timeout:

// Restart container
await $.docker.restart('my-container');

// Restart with timeout
await $.docker.restart('my-container', {
timeout: 5 // Wait 5s before killing
});

Removing Containers

Remove stopped containers:

// Remove stopped container
await $.docker.remove('my-container');

// Force remove running container
await $.docker.remove('my-container', {
force: true
});

// Remove with volumes
await $.docker.remove('my-container', {
volumes: true
});

Container Creation

Configuration Options

Create containers with detailed configuration:

const container = await $.docker.create('nginx:latest', {
name: 'web-server',
hostname: 'web',
domainname: 'example.com',

// Port mapping
ports: [
'80:80',
'443:443',
'127.0.0.1:8080:8080'
],

// Volume mounts
volumes: [
'./html:/usr/share/nginx/html:ro',
'nginx-cache:/var/cache/nginx',
'/etc/ssl/certs:/etc/ssl/certs:ro'
],

// Environment variables
env: {
NGINX_HOST: 'example.com',
NGINX_PORT: '80'
},

// Resource limits
memory: '512m',
cpus: '0.5',

// Networking
network: 'bridge',
networkAlias: ['web', 'nginx'],

// Health check
healthcheck: {
test: ['CMD', 'curl', '-f', 'http://localhost/'],
interval: '30s',
timeout: '3s',
retries: 3
},

// Restart policy
restart: 'unless-stopped',

// Labels
labels: {
'com.example.app': 'web',
'com.example.version': '1.0'
}
});

// Start the created container
await $.docker.start(container.id);

Container Inspection

Getting Container Information

Inspect container details and state:

// Get container details
const info = await $.docker.inspect('my-container');

console.log({
id: info.Id,
name: info.Name,
state: info.State.Status,
running: info.State.Running,
exitCode: info.State.ExitCode,
startedAt: info.State.StartedAt,
image: info.Config.Image,
ports: info.NetworkSettings.Ports,
volumes: info.Mounts,
env: info.Config.Env
});

// Check if container exists
const exists = await $.docker.exists('my-container');

// Get container logs
const logs = await $.docker.logs('my-container', {
follow: false,
tail: 100,
timestamps: true
});

Health Checks

Container Health Monitoring

Monitor and wait for container health:

// Wait for container to be healthy
await $.docker.waitHealthy('my-container', {
timeout: 60000, // 60 seconds
interval: 1000 // Check every second
});

// Check health status
const health = await $.docker.health('my-container');
if (health.Status === 'healthy') {
console.log('Container is healthy');
}

// Custom health check
const isHealthy = await $.docker.exec('my-container')`curl -f http://localhost/health`
.then(() => true)
.catch(() => false);

Container Events

Monitoring Container Events

Listen to container lifecycle events:

// Monitor container events
const events = $.docker.events({
filters: {
container: ['my-container'],
event: ['start', 'stop', 'die', 'restart']
}
});

events.on('data', (event) => {
console.log(`Container ${event.Actor.ID}: ${event.Action}`);
});

// Stop monitoring
events.stop();

Auto-Cleanup

Temporary Containers

Create containers that clean up automatically:

// Run with auto-remove
await $.docker.run('alpine', {
rm: true,
command: ['echo', 'Temporary execution']
});

// Using try/finally for cleanup
const container = await $.docker.create('node:18', {
name: `temp-${Date.now()}`
});

try {
await $.docker.start(container.id);
await $.docker.exec(container.id)`npm test`;
} finally {
await $.docker.stop(container.id);
await $.docker.remove(container.id, { force: true });
}

Container Groups

Managing Multiple Containers

Work with groups of related containers:

// Start multiple containers
const containers = ['web', 'api', 'db'];
await Promise.all(
containers.map(name => $.docker.start(name))
);

// Stop all with prefix
const allContainers = await $.docker.list({
filters: { name: ['^myapp-'] }
});

for (const container of allContainers) {
await $.docker.stop(container.Names[0]);
}

// Restart all running containers
const running = await $.docker.list({
filters: { status: ['running'] }
});

await Promise.all(
running.map(c => $.docker.restart(c.Names[0]))
);

Configuration in Xec

Target Configuration

Define Docker targets in .xec/config.yaml:

targets:
containers:
web:
type: docker
container: web-server

app:
type: docker
container: app-server
user: node
workdir: /app

db:
type: docker
container: postgres
env:
PGUSER: postgres

# Auto-start container if not running
worker:
type: docker
container: worker
autoStart: true
image: myapp:worker # Image to use if container doesn't exist

# Compose service reference
api:
type: docker
compose:
file: docker-compose.yml
service: api

Lifecycle Hooks

Configure lifecycle hooks:

targets:
containers:
app:
type: docker
container: my-app
hooks:
beforeStart: |
echo "Starting container..."
docker network create app-net 2>/dev/null || true
afterStart: |
echo "Waiting for app to be ready..."
sleep 5
beforeStop: |
echo "Gracefully shutting down..."
docker exec my-app npm run shutdown
afterStop: |
echo "Container stopped"

Performance Characteristics

Based on Implementation Analysis:

Operation Timings

  • Container Start: 100-500ms (image cached)
  • Container Stop: 100ms-10s (depends on grace period)
  • Container Exec: 50-100ms overhead
  • Container Create: 200ms-2s (depends on image)
  • Container Remove: 50-200ms
  • Health Check: 100ms per check

Resource Usage

  • Memory per Exec: ~1MB
  • Connection Overhead: Minimal (Unix socket)
  • Event Stream: ~100KB/hour

Error Handling

Common Errors and Solutions

ErrorExit CodeSolution
Container not found3Verify container name/ID
Container not running5Start container first
Permission denied11Check Docker permissions
Image not found8Pull image first
Port already in use8Use different port mapping
Volume mount failed8Check path permissions

Error Recovery

// Automatic retry on failure
async function executeWithRetry(container: string, command: string) {
for (let i = 0; i < 3; i++) {
try {
return await $.docker(container)`${command}`;
} catch (error) {
if (error.code === 'CONTAINER_NOT_RUNNING') {
await $.docker.start(container);
continue;
}
throw error;
}
}
}

// Health-based execution
async function executeWhenHealthy(container: string, command: string) {
await $.docker.waitHealthy(container);
return await $.docker(container)`${command}`;
}

Best Practices

Container Management

  1. Always use names - Use meaningful container names instead of IDs
  2. Set resource limits - Prevent containers from consuming all resources
  3. Use health checks - Define health checks for reliable container state
  4. Clean up - Remove stopped containers and unused images regularly
  5. Use restart policies - Configure appropriate restart behavior

Execution Patterns

// Good: Named container with cleanup
const containerName = `test-${Date.now()}`;
try {
await $.docker.run('node:18', {
name: containerName,
rm: false // Don't auto-remove for debugging
});
await $.docker(containerName)`npm test`;
} finally {
await $.docker.remove(containerName, { force: true });
}

// Good: Wait for readiness
await $.docker.start('database');
await $.docker.waitHealthy('database');
await $.docker('app')`npm run migrate`;