There’s a specific moment I hit constantly when debugging containerized apps: I need to pull a log file, a core dump, or a generated report out of a running container without rebuilding the image or setting up a volume I didn’t originally plan for. docker cp is the tool for exactly that job, and despite being one of the simplest commands in the Docker CLI, it has enough edge cases around trailing slashes, symlinks, and permissions that it’s worth covering properly. In this guide I’ll go through docker cp in depth, plus the alternative approaches (volumes, bind mounts, multi-stage builds) you should reach for instead when cp isn’t the right tool.
What docker cp Actually Does
docker cp copies files or directories between a container’s filesystem and the local filesystem of the Docker host — it does not go through the container’s network stack, and it doesn’t require the container to even be running for copies out of it (a stopped container’s filesystem is still accessible, since the container’s writable layer persists on disk until the container is removed).
Basic syntax:
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
Copying a File Out of a Container
docker run -d --name myapp myapp:1.0.0
docker cp myapp:/app/logs/error.log ./error.log
Expected output:
Successfully copied 2.56kB to /Users/me/project/error.log
Copying an entire directory out:
docker cp myapp:/app/logs ./logs-backup
Successfully copied 15.36kB to /Users/me/project/logs-backup
Copying a File Into a Container
docker cp ./config.prod.yaml myapp:/app/config.yaml
Successfully copied 1.02kB to myapp:/app/config.yaml
Copying a whole directory in:
docker cp ./static-assets myapp:/app/static
The Trailing Slash Gotcha
This is the single most common source of confusion with docker cp, and it works exactly like cp -r/rsync semantics: whether the source path ends in /. (or the destination behavior) determines whether Docker copies the directory itself or just its contents.
# Copies the "logs" directory itself into dest, resulting in dest/logs/*
docker cp myapp:/app/logs ./dest
# Copies the CONTENTS of "logs" directly into dest, resulting in dest/*
docker cp myapp:/app/logs/. ./dest
I’ve broken deploy scripts more than once by missing this distinction, so when scripting docker cp, I always test both directions manually first with ls on the result before trusting it in automation.
Copying Between Two Containers
docker cp doesn’t support container-to-container copies directly — you have to stage through the host:
docker cp source-container:/app/data.db /tmp/data.db
docker cp /tmp/data.db target-container:/app/data.db
rm /tmp/data.db
Working with Stopped Containers
Because docker cp reads/writes the container’s filesystem layer directly rather than executing anything inside it, it works on stopped containers too — useful for pulling forensic data out of a crashed container before removing it:
docker ps -a --filter "name=myapp" --format "table {{.Names}}\t{{.Status}}"
NAMES STATUS
myapp Exited (1) 3 minutes ago
docker cp myapp:/app/crash.log ./crash.log
Successfully copied 890B to /Users/me/project/crash.log
This is a genuinely useful debugging technique — you can extract diagnostic data from a crashed container before running docker rm on it, without needing to restart anything.
Preserving File Ownership and Permissions
By default, docker cp preserves the UID/GID numbers from the source, which can look strange on the host if those UIDs don’t correspond to real users there:
docker cp myapp:/app/data.db ./data.db
ls -l data.db
-rw-r--r-- 1 1000 1000 20480 Jul 29 10:22 data.db
That 1000 1000 reflects the container’s appuser UID/GID, not necessarily a matching user on your host. If you need it owned by your own user for further processing, chown it afterward:
sudo chown "$(id -u):$(id -g)" data.db
Combining docker cp with docker exec for Verification
I usually pair a copy with a quick exec-based check to confirm the source actually contains what I expect before I copy it out, especially in scripted pipelines:
docker exec myapp ls -la /app/logs
docker cp myapp:/app/logs/access.log ./access.log
sha256sum access.log
docker exec myapp sha256sum /app/logs/access.log
Comparing the two checksums confirms the copy transferred byte-for-byte correctly.
When NOT to Use docker cp
docker cp is a manual, one-off operation — it is not a substitute for a proper data-persistence or file-sharing strategy. A few situations where you should reach for something else instead:
Use a Bind Mount for Live Development
If you’re actively editing source files and want changes reflected inside the container immediately, don’t docker cp on every save — mount the directory instead:
docker run -d --name myapp -v "$(pwd)/src:/app/src" myapp:1.0.0
or in Compose:
services:
web:
build: .
volumes:
- ./src:/app/src
Use a Named Volume for Persistent Data
For data that needs to survive container recreation (databases, uploaded files), use a named volume rather than manually copying data in and out around every redeploy:
docker volume create pgdata
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16-alpine
Use Multi-Stage Builds for Build Artifacts
If you find yourself building in one container and repeatedly docker cp-ing the output into another, that’s almost always better expressed as a multi-stage Dockerfile so the artifact transfer happens automatically and reproducibly on every build:
FROM golang:1.23 AS builder
WORKDIR /src
COPY . .
RUN go build -o /out/app .
FROM gcr.io/distroless/static-debian12
COPY --from=builder /out/app /app
ENTRYPOINT ["/app"]
This achieves the same result as manually building in one container and docker cp-ing the binary out, but it’s reproducible, version-controlled, and doesn’t depend on a human remembering to run the copy step.
Real-World Workflow: Extracting a Database Dump for Backup
docker exec db pg_dump -U appuser appdb > /tmp/appdb-dump.sql
Note this actually uses exec with shell redirection on the host rather than cp, which is the more idiomatic way to get command output off a container. docker cp is for filesystem paths that already exist as files; for generating a file from a command and immediately capturing it, docker exec ... > local-file is simpler and avoids an extra step:
docker exec db pg_dump -U appuser -d appdb -f /tmp/dump.sql
docker cp db:/tmp/dump.sql ./appdb-backup-$(date +%Y%m%d).sql
docker exec db rm /tmp/dump.sql
Real-World Workflow: Injecting a TLS Certificate at Runtime
docker cp ./certs/server.crt myapp:/etc/ssl/certs/server.crt
docker cp ./certs/server.key myapp:/etc/ssl/private/server.key
docker exec myapp nginx -s reload
Useful for a quick manual rotation, though for anything recurring you’d want this driven by a mounted secret volume or an orchestrator-managed secret instead (see the security note below).
Internal Working: How docker cp Moves Data
Under the hood, docker cp doesn’t use the container’s network namespace or any application-level protocol — it operates through the Docker daemon’s API, which reads the container’s merged filesystem view (the overlay of all its image layers plus its writable layer) directly from the storage driver on the host, streams the requested path as a tar stream over the Docker API socket, and writes it out to the destination. This is why it works even on a stopped container: the storage driver still has that container’s layers on disk regardless of whether a process is currently running inside its namespace.
Security Considerations
docker cprequires the same access level as any other Docker CLI command — anyone with access to the Docker socket can copy arbitrary files in or out of any container, including reading secrets that might be sitting in environment-derived config files. Treat Docker socket access as root-equivalent access.- Avoid using
docker cpas a substitute for a real secrets-management workflow (Docker secrets, Kubernetes Secrets, Vault). A file copied in viacpsits in the container’s writable layer, unencrypted, and if the container is later committed as an image, that secret gets baked permanently into a layer. - When copying data out for debugging, be mindful that logs and dumps can contain sensitive information (tokens, PII); handle the extracted files with the same care you’d apply to the container’s normal access controls.
Troubleshooting Common Issues
- “Error: No such container:path” — double-check the path exists inside the container by running
docker exec CONTAINER ls -la /path/to/checkfirst; a typo in the path is the most common cause. - Copied directory ends up nested one level deeper than expected — this is almost always the trailing-slash behavior described above; add or remove the trailing
/.on the source path to get the semantics you want. - Permission denied writing into the container — the destination path inside the container may be owned by a different user than the one the container process runs as, or the container’s filesystem may be read-only (common with
--read-onlycontainers); check withdocker inspectforReadonlyRootfs. - Copy from a stopped container fails with a “container not found” style error — confirm the container hasn’t been removed (
docker rm) yet;docker cpworks on stopped containers, but not on ones that no longer exist. Check withdocker ps -a.
Summary
docker cp is the right tool for a specific, narrow job: moving individual files or directories between the host and a container’s filesystem, on demand, including against stopped containers. It’s invaluable for debugging, ad hoc backups, and quick manual fixes, but it’s a manual operation, not a persistence strategy — for anything recurring or structural, bind mounts, named volumes, and multi-stage Dockerfile builds are the correct long-term tools. Knowing which of these four tools fits a given situation is what separates a quick one-off fix from a maintainable, reproducible container workflow.
References
- Docker CLI Reference — docker cp: https://docs.docker.com/reference/cli/docker/container/cp/
- Docker Documentation — Manage Data in Containers (Volumes and Bind Mounts): https://docs.docker.com/storage/
- Docker Documentation — Multi-Stage Builds: https://docs.docker.com/build/building/multi-stage/
- Docker Documentation — Docker Socket and Security: https://docs.docker.com/engine/security/
