Moving Files Out of Docker Containers and Kubernetes Pods
By SRIFT maintainers · Published
Reviewed by SRIFT maintainers · last reviewed against the SRIFT 4.3.2 source · third-party facts checked 8 October 2026.
Short answer
For Docker, docker cp copies files out of a running or stopped container through the Docker daemon, so the image needs nothing installed. For Kubernetes, kubectl cp works by running tar inside the container, so it fails on distroless and other minimal images; the fallbacks are kubectl exec with cat, an ephemeral debug container, or a volume another pod can read.
When you have no Docker socket or Kubernetes API access at all (a CI-created pod, a customer's cluster), the container can push the file out itself through an outbound link that someone downloads in a browser.
- docker cp needs in the image
- Nothing; works on stopped containers
- kubectl cp needs in the image
- A tar binary
- Distroless pod
- Ephemeral debug container or a shared volume
- No cluster access
- Outbound link from inside the container
Docker: docker cp and its limits
docker cp asks the Docker daemon to read the container's filesystem directly, so it does not depend on a shell or any tool inside the image. The Docker documentation states that the container can be running or stopped, which makes it the standard way to recover files from a container that crashed on start-up.
# file or directory to the current directory
docker cp api:/var/log/app/error.log .
docker cp api:/srv/uploads ./uploads
# stream a directory as a tar archive to stdout
docker cp api:/srv/uploads - | gzip > uploads.tar.gz
# Compose services
docker compose cp api:/var/log/app/error.log .A few behaviours trip people up. Files copied to the host are owned by the user who ran the command, and -a keeps the original UID and GID. Symbolic links in the source are copied as links unless you pass -L. Resources under /proc, /sys and /dev, tmpfs mounts and mounts created inside the container cannot be copied; Docker suggests running tar through docker exec for those.
The real constraint is access: docker cp needs the Docker daemon, which is root-equivalent on the host. If the container runs on a remote host you can reach over SSH, the Docker CLI can talk to that daemon directly (DOCKER_HOST=ssh://user@host). If you only have a shell inside the container, a redirect works for single files, as long as you do not allocate a TTY:
docker exec api cat /data/report.pdf > report.pdf # no -t: a TTY can mangle binary outputKubernetes: kubectl cp and kubectl exec
kubectl cp looks like docker cp but works differently. The API server cannot read a container's filesystem, so kubectl runs tar in the container through an exec session and unpacks the stream locally. The kubectl reference is explicit: it requires the tar binary in the image, and if tar is missing, kubectl cp fails.
kubectl cp prod/api-7d9f8:/var/log/app/error.log ./error.log -c api --retries 5
# what it does under the hood, useful for symlinks or modes
kubectl exec -n prod api-7d9f8 -c api -- tar cf - -C /var/log app | tar xf - -C ./logsChoose the container with -c in multi-container pods; otherwise kubectl uses the default-container annotation or the first container. --retries (default 0) re-attempts an interrupted copy, which matters for large files over a flaky API-server connection.
kubectl exec with cat
Images without tar sometimes still have cat. Redirecting its output gives you the file, with caveats: never pass -t (a TTY rewrites line endings and corrupts binary data), there is no resume if the stream drops, and everything flows through the API server and kubelet, so very large files are slow. Compare checksums on both sides when the image has sha256sum.
kubectl exec -n prod api-7d9f8 -c api -- cat /data/dump.sql.gz > dump.sql.gz
kubectl exec -n prod api-7d9f8 -c api -- sha256sum /data/dump.sql.gz
sha256sum dump.sql.gzDistroless images: ephemeral debug containers
Distroless images ship no shell, no tar and no cat, so neither approach above works. Kubernetes' answer is the ephemeral container (stable since v1.25): kubectl debug adds a temporary container with your choice of image to a running pod. With --target, it joins the process namespace of the named container.
kubectl debug -it -n prod api-7d9f8 --image=busybox:1.36 --target=api -- sh
# inside the debug container: find the app's PID, then read through /proc
ps
ls /proc/1/root/data/The Kubernetes documentation notes that container filesystems are visible to other containers sharing the process namespace through /proc/$pid/root, protected only by filesystem permissions. Two practical limits apply. Access through /proc is permission-checked, so a debug container running as a different user than the application may be refused; kubectl debug --profile and your cluster's admission policies decide what the debug container may do. And an ephemeral container cannot be removed once added: it stays in the pod spec until the pod is replaced. The container runtime must also support --target.
From the debug shell you still need a way to get the bytes to your machine. If the debug image has tar, a kubectl exec pipe into the debug container works; another option is to use a debug image that can send the file out itself, shown below.
Volumes, bind mounts and sidecars
If you control how the workload is deployed, the cleanest fix is to write files somewhere outside the container's own filesystem. With Docker, a bind mount puts them straight on the host, and a named volume can be archived by a throwaway container:
docker run --rm --volumes-from db -v "$PWD":/backup alpine \
tar czf /backup/dbdata.tar.gz /var/lib/postgresql/dataIn Kubernetes, a PersistentVolumeClaim can be mounted by a short-lived helper pod that has the tools you need. Remember that a ReadWriteOnce volume can be mounted only by pods on one node at a time, so the helper must be scheduled alongside the existing pod or the workload must be scaled down first. For logs or dumps the application produces routinely, a sidecar that shares an emptyDir with the app and ships files somewhere is the durable solution, but changing a pod spec restarts the pod, so it is a deployment change, not an incident-time fix.
When you cannot kubectl into the cluster at all
Two situations break every method above. A CI-created pod (a Kubernetes executor or ephemeral runner) disappears when the job ends and you have no credentials for its namespace. A customer's cluster belongs to someone else: you can ask them to run a command, but you will never get a kubeconfig. In both cases, the process that can see the file has to push it out.
An outbound streamed link fits well because the container needs only outbound HTTPS. With SRIFT, the command prints a link and then serves the file from inside the container through a connection to srift.app on port 443; nothing is stored on the server, and the recipient downloads in a browser or with srift get.
npx -y srift-transfer quick-share /data/dump.sql.gz --encrypt --waitcurl -fsSL https://srift.app/install.sh | sh
~/.srift/bin/srift quick-share /data/dump.sql.gz --encrypt --wait --wait-timeout 1hFor a customer, a one-off Job that mounts the volume is easier to hand over than a sequence of commands. The link appears in the Job's log, which the customer reads and forwards to you:
apiVersion: batch/v1
kind: Job
metadata:
name: srift-export
spec:
backoffLimit: 0
activeDeadlineSeconds: 7200
template:
spec:
restartPolicy: Never
containers:
- name: export
image: node:22-alpine
command: ["npx", "-y", "srift-transfer", "quick-share", "/data/dump.sql.gz",
"--encrypt", "--once", "--wait", "--wait-timeout", "90m"]
volumeMounts:
- name: data
mountPath: /data
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: app-dataPoints that matter in containers:
- Embedded mode needs no local port. Where a background daemon cannot start, the command serves the link from its own process using only outbound HTTPS and WSS on port 443.
- Keep the process alive. A container stops when its main process exits, taking any daemon with it.
--waitblocks until the file has been downloaded; with--wait-timeout, the command exits with code 3 if nobody downloaded in time, and the timeout never interrupts a download in progress. - npx needs the npm registry. In an egress-restricted namespace, either allow it or use the standalone binary (a musl build exists for Alpine; the installer verifies its SHA-256 checksum).
- Logs are not private. An
--encryptlink carries its key after#k=, so anyone who can read the pod or Job log can download the file. Use--onceso the link dies after the first download.
NetworkPolicy, egress proxies and diagnosis
Any push-out method depends on egress. In clusters with default-deny NetworkPolicies, the pod needs an egress rule for port 443 to the destination and for DNS (port 53 to the cluster DNS service), or name resolution fails before anything connects. Standard NetworkPolicy selects destinations by pod, namespace or IP block, not by hostname, so allowing a SaaS endpoint usually means going through an egress proxy or using a network plugin with FQDN-based policies.
The SRIFT CLI honours HTTPS_PROXY, HTTP_PROXY, ALL_PROXY and NO_PROXY, including socks5:// and socks5h:// proxies. Behind a TLS-inspecting proxy, set NODE_EXTRA_CA_CERTS to the proxy's CA bundle. Running npx -y srift-transfer doctor in the pod checks DNS, HTTPS, WebSocket, proxy reachability, loopback binding and whether a background daemon can start, then prints which mode will work and how to fix what is blocked.
If the cluster forbids egress to anything outside an allow-list, the honest answer is that a push-out link needs an exception, and an in-cluster object store or a kubectl cp through an approved path may be the better route. The firewall and proxy guide explains the common blocking patterns.
Choosing a method
| Method | Needs in the image | Needs your access to | Distroless | Best for |
|---|---|---|---|---|
| docker cp | Nothing | Docker daemon on the host | Yes | Any local or SSH-reachable Docker host, including stopped containers |
| kubectl cp | tar | Kubernetes API (exec permission) | No | Everyday copies from pods you can exec into |
| kubectl exec … cat > | cat | Kubernetes API (exec permission) | No | Single files when tar is missing |
| kubectl debug (ephemeral) | Nothing (tools come from the debug image) | Kubernetes API (ephemeral containers) | Yes | Distroless pods during an incident |
| Volume / helper pod / sidecar | Nothing in the app image | Deployment config or PVC | Yes | Recurring exports and planned changes |
| Outbound link (srift quick-share) | Node 20+ or the standalone binary (or a debug image with them) | Nothing: someone runs one command | Via a helper or debug image | CI pods and clusters you cannot access |
For an AI coding agent working inside a container, the same outbound link is how it can hand you a file; see how AI coding agents deliver files. The CLI tools comparison covers general-purpose alternatives.
Frequently asked questions
Why does kubectl cp fail with an error about tar?
kubectl cp runs tar inside the container to package the files. If the image has no tar binary, as with distroless or scratch-based images, the copy fails. Use kubectl exec with cat if cat exists, or an ephemeral debug container that brings its own tools.
Can I copy files from a stopped Docker container?
Yes. docker cp works on running and stopped containers because the Docker daemon reads the container filesystem directly. It does not work once the container has been removed, and it cannot copy from tmpfs mounts or from /proc, /sys or /dev.
References
- docker container cp (Docker)
- kubectl cp (Kubernetes)
- Ephemeral Containers (Kubernetes)
- Debug Running Pods (Kubernetes)
- Share Process Namespace between Containers in a Pod (Kubernetes)
- Network Policies (Kubernetes)
Use SRIFT from a terminal
One command prints a download link that streams from your machine. Needs only outbound HTTPS; --encrypt keeps the key out of the relay.
npx -y srift-transfer quick-share ./build.tar.gz --encryptRelated resources
- Get Files Off a Remote Server Without Opening Inbound PortsPull logs, dumps and builds from Linux servers, cloud VMs and headless boxes when SSH is not an option: outbound-only methods and their trade-offs.How-to
- Sharing CI/CD Build Artifacts SecurelyHow to hand a build artifact from a CI job to a person: built-in artifact stores, object storage links and short-lived streamed links compared.How-to
- Command-Line File Transfer Tools Comparedscp, sftp, rsync, Magic Wormhole, croc and SRIFT compared on network requirements, encryption, authentication and the jobs each one fits.Comparison
