How to Copy Data to and from Docker Containers: Docker CP Command and File Transfer Guide

How to Copy Data to and from Docker Containers

How to Copy Data to and from Docker Containers

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

Troubleshooting Common Issues

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

Exit mobile version