Skip to main content

Targets Overview

A target is where a command runs. Xec ships four: the local machine, SSH hosts, Docker containers and Kubernetes pods. All four sit behind the same template-literal API, so the code that runs a command does not change when the place it runs does.

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

await $`uptime`; // local machine
await $.ssh('deploy@web-1')`uptime`; // SSH host
await $.docker('my-app')`uptime`; // Docker container
await $.k8s('prod/api-7d9f')`uptime`; // Kubernetes pod

Each target is implemented as an adapter in @xec-sh/core. Adapters are loaded lazily — a script that never touches Kubernetes never pays for the Kubernetes adapter.

The four targets​

Local​

The default. Commands run on the machine executing the script through Node's child_process (or Bun.spawn under Bun) — no configuration required. The section covers shell selection and troubleshooting of local execution.

SSH​

Commands run on remote hosts over pooled ssh2 connections. The section covers connection configuration, authentication, sudo and security, tunneling and batch operations across many hosts.

Docker​

Commands run inside containers by shelling out to your local docker CLI — either exec into a running container or an ephemeral run from an image. The section covers the container lifecycle API, Compose integration, volumes and networking.

Kubernetes​

Commands run inside pods through kubectl exec, with namespace, container and kubeconfig-context targeting. The section covers pod execution, multi-container pods, port forwarding, log streaming and file operations.

What stays the same across targets​

Every target returns the same ExecutionResult — stdout, stderr, exitCode and ok — and throws the same way on failure unless you opt out:

const result = await $.ssh('deploy@web-1')`systemctl is-active nginx`.nothrow();
if (!result.ok) {
console.error(`nginx is not active: ${result.stderr.trim()}`);
}

Every target context accepts the same chainable configuration before the command runs:

const app = $.docker('my-app')
.cd('/app')
.env({ NODE_ENV: 'production' })
.timeout(30000);

await app`npm run build`;

Values interpolated into the template are escaped for the target's shell on every adapter. $.transfer.copy moves files between local, SSH and Docker targets (Kubernetes has its own file operations built on kubectl cp). Because the surface is uniform, a function that takes an engine runs unchanged against any of the four — Working across environments builds on exactly that to cover multi-host rollouts, retries and cross-target pipelines.

Targets in the CLI​

Scripts pick a target in code, as above. The CLI picks one from .xec/config.yaml, where named targets carry the connection details:

# .xec/config.yaml
targets:
hosts:
production:
host: prod.example.com
user: deploy
privateKey: ~/.ssh/id_ed25519
containers:
app:
container: my-app
xec on hosts.production "systemctl status nginx" # SSH host
xec in containers.app "npm test" # Docker container

Targets Configuration documents the full YAML schema for every target type.