Skip to main content

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`;