How to copy files from a Docker container to local machine

Copy a file from a container to the current folder:

docker cp app:/app/config.yml .

app is the container name or ID from docker ps. The first path is inside the container, the second one is on your machine. To rename the file, give the new name: ./config.backup.yml. An existing local file is overwritten without a prompt.

Tested with Docker 29.7 and Docker Compose 5.5 on Alpine 3.24 and nginx:alpine containers.

Find the container and the file path

docker ps -a
docker exec app ls -l /app

docker ps -a also lists stopped containers. Container paths always start at /: docker cp ignores the working directory. If the container's WORKDIR is /app, then app:config.yml means /config.yml. The full path of a file in WORKDIR: docker exec app readlink -f config.yml.

Copy a folder from a container

# ./logs doesn't exist: created, with the files inside
docker cp app:/app/logs ./logs
# ./backup exists: you get ./backup/logs
docker cp app:/app/logs ./backup
# contents only: ./backup/a.log, ./backup/b.log
docker cp app:/app/logs/. ./backup

If the destination doesn't exist, Docker creates it and puts the folder's contents there. If it exists, the folder lands inside it, unless the source ends with /.: then only the contents are copied. A trailing slash alone (logs/) changes nothing.

Copy files from local machine into a Docker container

Swap the arguments:

docker cp ./settings.yml app:/app/
# /srv/static
docker cp ./static app:/srv
# the contents of static in /var/www
docker cp ./static/. app:/var/www

The folder rules are the same. The copy lives only in this container: after docker rm or docker compose up --force-recreate it's gone. Files the app always needs belong in the image (COPY in the Dockerfile) or in a volume.

Copy a file inside a Docker container or between containers

To copy a file inside a container, run cp there:

docker exec app cp /app/config.yml /app/config.bak

To copy between two containers, pipe one docker cp into another:

docker cp app:/app/logs - | docker cp - other:/tmp

Stopped containers work too

docker cp copies from and into stopped containers. When a container crashes at start, pull out its logs, put back a fixed config and run docker start app. Files in volumes are copied too.

docker compose cp

In the folder with compose.yaml, use the service name instead of the container:

docker compose cp web:/app/config.yml .
docker compose cp ./override.yml web:/app/

If the service is scaled, copying in writes to every replica, while copying out picks one of them, not necessarily the first. Choose it with --index:

docker compose cp --index 2 web:/app/config.yml .

Stopped services work as well.

Permissions and file ownership

Mode bits survive both ways. The owner survives only on the way into the container, and only as numbers.

Container to host: files belong to whoever ran docker cp. So sudo docker cp on Linux leaves root-owned files. Take them back:

sudo chown -R "$(id -u):$(id -g)" ./logs

To drop sudo, add yourself to the docker group (sudo usermod -aG docker $USER) and log in again, as with any new group. This group gives root-level access to the machine.

Host to container: files keep the numeric UID and GID they had on the host, and sudo docker cp doesn't change them. That's 1000:1000 for a typical Linux user and 501:20 on a Mac. If the app in the container runs as another user, writing to them fails with Permission denied. With -a, Docker hands the files to the container's user, the one from --user or USER in the Dockerfile:

docker cp -a ./settings.yml app:/app/

If neither --user nor USER is set, -a changes nothing. It also does nothing when copying out. To fix an owner afterwards: docker exec -u 0 app chown 1000:1000 /app/settings.yml.

Copy a file from a Docker image

docker cp needs a container, but not a running one. docker create makes it from the image without starting anything:

docker create --name tmp nginx:alpine
docker cp tmp:/etc/nginx/nginx.conf .
docker rm tmp

An image without a default command (FROM scratch) fails with no command specified. Pass any command, nothing will run it:

docker create --name tmp my-image none

Stream a tar archive with -

With - instead of the local path, docker cp writes a tar archive to stdout or reads one from stdin:

docker cp app:/app/logs - > logs.tar
docker cp app:/app/logs - | gzip > logs.tar.gz
tar -c -C ./site . | docker cp - app:/var/www

When reading, the destination must be an existing folder; gzip is fine too. On a Mac, tar packs extended attributes, and Docker fails with xattr "com.apple.provenance": operation not supported. Pack without them:

COPYFILE_DISABLE=1 tar --no-xattrs -c -C ./site . | docker cp - app:/var/www

Plain docker cp ./site/. app:/var/www doesn't have this problem.

Copy files by mask or from tmpfs

docker cp doesn't expand globs: docker cp 'app:/app/logs/*.log' . fails. In tmpfs mounts it sees an empty folder, and it can't read /proc, /sys or /dev at all. Run tar inside the running container instead:

mkdir -p logs scratch
docker exec app sh -c 'cd /app/logs && tar -c *.log' | tar -x -C ./logs
docker exec app tar -c -C /scratch . | tar -x -C ./scratch

Run docker exec without -t: a TTY corrupts the archive.

Copy a file from a Docker container on Windows

Same command in PowerShell. A path with a drive letter counts as local:

docker cp app:/app/config.yml C:\Users\me\Desktop

Common errors

  • No such container: app: wrong name or ID, check docker ps -a. Compose names containers like project-web-1; with docker compose cp use the service name.
  • Could not find the file /app/config.yml in container app: wrong path in the container. When copying in, the folder you copy into is missing: for app:/app/conf/settings.yml that's /app/conf. Also shows up for paths relative to WORKDIR, globs and files in tmpfs.
  • no such directory or invalid output path: directory "…" does not exist: the destination folder is missing, on the host or in the container. Create it with mkdir -p, in the container with docker exec app mkdir -p /app/conf.
  • cannot copy directory: the destination is an existing file.
  • copying between containers is not supported: both arguments contain a colon. A local name with : needs ./ in front: ./backup:1.yml.
  • must specify at least one container source: neither argument has container:.
  • container rootfs is marked read-only: the container runs with --read-only. Its volumes still accept files.
  • The copy is a broken link: the source is a symlink, and Docker copied the link itself. Add -L to copy the file it points to.
I do product consulting, build MVPs and teach people to build products themselves. Work with me →