Skip to main content

Multi-Environment Execution

Xec provides a unified interface for executing commands across multiple environments, from local shells to remote Kubernetes clusters. This section covers how to configure and use each environment type effectively.

Supported Environments​

Local Environment​

Execute commands directly on your local machine using native shell integration. The local adapter supports both Node.js and Bun runtimes with automatic detection.

Key Features:

  • Native shell execution (bash, sh, zsh, cmd.exe)
  • Bun runtime optimization when available
  • Process lifecycle management
  • Signal handling and timeout support
  • Stream piping and buffering

Basic Usage:

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

// Simple local execution
const result = await $`ls -la`;
console.log(result.stdout);

// With custom shell
const output = await $.with({ shell: '/bin/zsh' })`echo $ZSH_VERSION`;

SSH Environment​

Execute commands on remote servers via SSH with advanced connection management.

Key Features:

  • Connection pooling with automatic reuse
  • SSH tunneling and port forwarding
  • Batch operations across multiple hosts
  • Secure password handling
  • SFTP file transfers
  • Keep-alive and auto-reconnect

Basic Usage:

// Single host execution
const result = await $({
ssh: {
host: 'server.example.com',
username: 'user',
privateKey: '/path/to/key'
}
})`uname -a`;

// Connection pooling (enabled by default)
const ssh = {
host: 'server.example.com',
username: 'user',
connectionPool: {
enabled: true,
maxConnections: 10,
idleTimeout: 300000
}
};

await $.with({ ssh })`command1`;
await $.with({ ssh })`command2`; // Reuses connection

Docker Environment​

Execute commands inside Docker containers with comprehensive lifecycle management.

Key Features:

  • Container lifecycle management
  • Docker Compose integration
  • Volume and network management
  • Image building and management
  • Real-time log streaming
  • Health check support

Basic Usage:

// Execute in existing container
const result = await $({
docker: {
container: 'my-app'
}
})`npm test`;

// Run ephemeral container
const output = await $({
docker: {
image: 'node:18',
runMode: 'run',
rm: true
}
})`node --version`;

// With volume mounts
await $({
docker: {
image: 'alpine',
runMode: 'run',
volumes: ['/local/path:/container/path']
}
})`ls /container/path`;

Kubernetes Environment​

Execute commands in Kubernetes pods with cluster-aware features.

Key Features:

  • Pod and container selection
  • Multi-container pod support
  • Port forwarding to services
  • Real-time log streaming
  • Namespace management
  • Context switching

Basic Usage:

// Execute in pod
const result = await $({
kubernetes: {
pod: 'app-pod-xyz',
namespace: 'production'
}
})`df -h`;

// Specific container in multi-container pod
await $({
kubernetes: {
pod: 'app-pod-xyz',
container: 'web',
namespace: 'production'
}
})`nginx -t`;

// With context
await $({
kubernetes: {
pod: 'debug-pod',
context: 'staging-cluster'
}
})`env`;

Environment Detection and Auto-Selection​

Xec can automatically detect the appropriate environment based on the command configuration:

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

// Auto-detect based on options
const result = await $(auto({
// Will use SSH if host is provided
host: process.env.REMOTE_HOST,
// Falls back to local if not
fallback: 'local'
}))`echo "Hello from ${await $`hostname`}"`;

Adapter Configuration​

Global Configuration​

Set default configurations that apply to all executions:

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

configure({
// Default timeout for all commands
defaultTimeout: 30000,

// Default encoding
encoding: 'utf8',

// Maximum output buffer size
maxBuffer: 10 * 1024 * 1024,

// Throw on non-zero exit codes
throwOnNonZeroExit: true,

// Default environment variables
defaultEnv: {
NODE_ENV: 'production'
}
});

Per-Execution Configuration​

Override settings for specific executions:

const result = await $({
timeout: 60000,
throwOnNonZeroExit: false,
env: {
DEBUG: 'true'
},
cwd: '/app'
})`npm run build`;

Environment Chaining​

Execute commands that span multiple environments:

// Copy file from remote to local via Docker
const remotePath = '/remote/data.tar.gz';
const containerPath = '/tmp/data.tar.gz';
const localPath = './data.tar.gz';

// Download from remote server
await $.with({ ssh: sshConfig })`cat ${remotePath}`
.pipe($.with({ docker: { container: 'processor' } })`cat > ${containerPath}`);

// Process in Docker
await $.with({ docker: { container: 'processor' } })`
cd /tmp &&
tar -xzf data.tar.gz &&
./process.sh
`;

// Copy result to local
const processed = await $.with({ docker: { container: 'processor' } })`cat /tmp/result.json`;
await $`echo '${processed}' > ${localPath}`;

Parallel Execution Across Environments​

Execute commands simultaneously across multiple environments:

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

const results = await parallel([
$`local-command`,
$.with({ ssh: server1 })`remote-command-1`,
$.with({ ssh: server2 })`remote-command-2`,
$.with({ docker: { container: 'app' } })`container-command`,
$.with({ kubernetes: { pod: 'pod-1' } })`pod-command`
]);

// Results array maintains order
const [local, remote1, remote2, docker, k8s] = results;

Error Handling​

Each environment provides specific error information:

try {
await $.with({ ssh: sshConfig })`false`;
} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.error('SSH connection refused');
} else if (error.exitCode === 1) {
console.error('Command failed with exit code 1');
}

// Access environment-specific details
console.error('Host:', error.details?.host);
console.error('Command:', error.command);
}

Stream Processing​

All environments support unified stream processing:

// Stream from SSH to Docker
await $.with({ ssh: remoteConfig })`tail -f /var/log/app.log`
.pipe($.with({ docker: { container: 'logger' } })`tee /logs/remote.log`);

// Real-time processing
const proc = $.with({ kubernetes: { pod: 'streamer' } })`watch -n 1 date`;
proc.stdout.on('data', (chunk) => {
console.log('K8s output:', chunk.toString());
});

// Graceful shutdown
setTimeout(() => proc.kill(), 10000);

Performance Optimization​

Connection Reuse​

SSH and Kubernetes adapters automatically reuse connections:

// SSH connection pooling
const sshPool = {
host: 'server.com',
connectionPool: {
maxConnections: 5,
idleTimeout: 600000, // 10 minutes
keepAlive: true
}
};

// Execute 100 commands using only 5 connections
await Promise.all(
Array.from({ length: 100 }, (_, i) =>
$.with({ ssh: sshPool })`echo "Task ${i}"`
)
);

Batch Operations​

Execute commands efficiently across multiple targets:

const hosts = ['server1.com', 'server2.com', 'server3.com'];

// Parallel execution with connection pooling
const results = await Promise.all(
hosts.map(host =>
$.with({
ssh: {
host,
username: 'deploy',
connectionPool: { enabled: true }
}
})`systemctl restart app.service`
)
);

Best Practices​

1. Use Connection Pooling​

Always enable connection pooling for SSH when executing multiple commands:

// Good - reuses connection
const ssh = { host: 'server', connectionPool: { enabled: true } };
await $.with({ ssh })`command1`;
await $.with({ ssh })`command2`;

// Bad - creates new connection each time
await $.with({ ssh: { host: 'server' } })`command1`;
await $.with({ ssh: { host: 'server' } })`command2`;

2. Handle Environment-Specific Errors​

Each environment can produce unique errors:

try {
await $.with({ docker: { container: 'app' } })`test -f /app/config.json`;
} catch (error) {
if (error.code === 'CONTAINER_NOT_FOUND') {
// Container doesn't exist
await $.with({ docker: { image: 'app:latest', runMode: 'run' } })`setup.sh`;
} else if (error.exitCode === 1) {
// File doesn't exist
await $.with({ docker: { container: 'app' } })`cp /defaults/config.json /app/`;
}
}

3. Use Appropriate Timeouts​

Set timeouts based on environment latency:

// Local - short timeout
await $.with({ timeout: 5000 })`quick-local-command`;

// SSH - medium timeout
await $.with({ ssh: config, timeout: 30000 })`remote-command`;

// K8s - longer timeout for cluster operations
await $.with({ kubernetes: config, timeout: 60000 })`kubectl apply -f manifest.yaml`;

4. Clean Up Resources​

Always dispose of adapters when done:

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

const engine = createExecutionEngine();
try {
await engine.execute({ ssh: config }, 'command');
} finally {
await engine.dispose(); // Closes all connections
}

Environment-Specific Features​

Local: Shell Detection​

The local adapter automatically detects and uses the appropriate shell:

// Auto-detects bash, zsh, sh, or cmd.exe
await $`echo $SHELL`;

// Force specific shell
await $.with({ shell: '/bin/bash' })`echo $BASH_VERSION`;

SSH: Tunneling​

Create SSH tunnels for secure access:

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

const tunnel = await createSSHTunnel({
ssh: sshConfig,
localPort: 3306,
remotePort: 3306,
remoteHost: 'database.internal'
});

// Use tunnel
const mysql = new MySQL({ host: 'localhost', port: 3306 });

// Clean up
await tunnel.close();

Docker: Compose Integration​

Work with Docker Compose projects:

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

// Start services
await compose.up({
file: 'docker-compose.yml',
detach: true
});

// Execute in service
await $.with({
docker: {
container: 'myapp_web_1'
}
})`npm run migrate`;

// Stop services
await compose.down();

Kubernetes: Port Forwarding​

Forward ports from Kubernetes services:

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

const forward = await portForward({
service: 'web-service',
namespace: 'default',
localPort: 8080,
remotePort: 80
});

// Access service locally
const response = await fetch('http://localhost:8080');

// Clean up
await forward.close();

Next Steps​