Skip to main content

File Operations

File transfer runs through kubectl cp, in both directions, via a pod handle.

Copy To / From a Pod

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

const pod = $.k8s({ namespace: 'default' }).pod('web-server');

await pod.copyTo('./config.json', '/app/config.json');
await pod.copyFrom('/app/logs/application.log', './app-logs.log');

Both accept a directory path too — kubectl cp copies recursively, provided tar is available inside the target container (it copies by streaming a tar archive through kubectl exec):

await pod.copyTo('./assets/', '/app/assets/');
await pod.copyFrom('/app/generated-reports/', './reports/');

The uniform cross-target surface reaches the same kubectl cp without a pod handle: $.k8s(pod).transfer.upload(local, remote) and .download(remote, local) carry the target's namespace, container and cluster context — the same calls work against $.ssh(...) and $.docker(...) engines. See the K8s adapter page.

Copying to a Specific Container

copyTo/copyFrom take an optional third container argument, but it does not currently reach kubectl cp correctly — it gets folded into the path string instead of passed as its own flag, which produces a malformed destination. Until that's fixed, target a container explicitly through the adapter's lower-level copyFiles, which takes container as a real option:

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

const k8s = $.getAdapter('kubernetes') as KubernetesAdapter;

await k8s.copyFiles('./nginx.conf', 'multi-container-pod:/etc/nginx/nginx.conf', {
namespace: 'production',
container: 'nginx',
direction: 'to',
});

Configuration Deployment

A typical backup-then-deploy-then-validate sequence, using only copyTo/copyFrom/exec/nothrow:

async function deployConfig(podName: string, configDir: string) {
const pod = $.k8s({ namespace: 'production' }).pod(podName);
const backupDir = `./backup/${Date.now()}`;

await pod.copyFrom('/app/config/', `${backupDir}/`);
await pod.copyTo(`${configDir}/`, '/app/config/');

const validation = await pod.exec`/app/bin/validate-config`.nothrow();
if (!validation.ok) {
await pod.copyTo(`${backupDir}/`, '/app/config/'); // roll back
throw new Error(`Configuration validation failed: ${validation.stderr}`);
}
}

Log and Backup Collection

async function collectLogs(podName: string, remoteFiles: string[], localDir: string) {
const pod = $.k8s({ namespace: 'production' }).pod(podName);

for (const remote of remoteFiles) {
await pod.copyFrom(remote, `${localDir}/${remote.split('/').pop()}`);
}
}

async function backupDatabase(dbPod: string) {
const pod = $.k8s({ namespace: 'production' }).pod(dbPod);
const remoteFile = `/tmp/backup-${Date.now()}.sql`;

await pod.exec`pg_dump myapp > ${remoteFile}`;
await pod.copyFrom(remoteFile, `./backups/${remoteFile.split('/').pop()}`);
await pod.exec`rm ${remoteFile}`;
}

Error Handling and Retries

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

async function robustCopy(
pod: K8sPod,
localPath: string,
remotePath: string,
direction: 'to' | 'from',
retries = 3
) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
if (direction === 'to') await pod.copyTo(localPath, remotePath);
else await pod.copyFrom(remotePath, localPath);
return;
} catch (error) {
if (attempt === retries) throw error;
await new Promise((r) => setTimeout(r, 2000 * attempt));
}
}
}

Integrity Verification

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

async function verifiedCopyTo(pod: K8sPod, localPath: string, remotePath: string) {
const localHash = (await $`md5sum ${localPath}`.text()).split(' ')[0];

await pod.copyTo(localPath, remotePath);

const remoteHash = (await pod.exec`md5sum ${remotePath}`.text()).split(' ')[0];

if (localHash !== remoteHash) {
throw new Error(`Integrity check failed: ${localHash} != ${remoteHash}`);
}
}

Permissions

copyTo/copyFrom preserve no permission metadata beyond what kubectl cp itself does — set permissions explicitly afterward if they matter:

await pod.copyTo(configFile, '/tmp/new-config.json');
await pod.exec`chown app:app /tmp/new-config.json && chmod 600 /tmp/new-config.json`;
await pod.exec`mv /tmp/new-config.json /app/config/secure-config.json`;

Performance Notes

  • Many small files are slow over kubectl cp (one tar stream per call). Archive locally, copy the single archive, and extract inside the pod instead:

    await $`tar -czf /tmp/transfer.tar.gz -C ${localDir} .`;
    await pod.copyTo('/tmp/transfer.tar.gz', '/tmp/transfer.tar.gz');
    await pod.exec`mkdir -p ${remoteDir} && tar -xzf /tmp/transfer.tar.gz -C ${remoteDir}`;
  • Skip copies that aren't needed by comparing checksums first (as in Integrity Verification above) rather than unconditionally overwriting.

  • For very large files, prefer a volume mount or object storage over kubectl cp.