Copy Files In and Out of Docker Containers

docker cp moves a file across the container boundary in one command, but ownership and symlinks behave differently enough to catch you out at 2am. The examples use Docker CLI 29.8.1 from docker-ce-cli 5:29.8.1-1~ubuntu.24.04~noble.

Allow about fifteen minutes. You need the Docker CLI, access to a container, read access to the source and write access to the destination. The examples do not need sudo when your account can talk to the Docker daemon. Do not add sudo automatically: Docker access is itself a powerful privilege.

1. Confirm the command and choose a container

Check the installed syntax first. This is a read-only command:

$ docker cp --help
Usage:  docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
        docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH

docker cp is an alias for docker container cp. The container may be running or stopped. Use a name or ID you have checked rather than copying to a guessed target:

$ docker ps -a --format 'table {{.Names}}\t{{.Status}}'
NAMES             STATUS
APP_CONTAINER     Up 2 minutes

The names in the output are host-specific. Set a shell variable only after replacing the placeholder with the exact name:

$ CONTAINER='APP_CONTAINER'

Checkpoint: You have a real container name and a source path that exists inside it or on the host.

2. Copy a file from the container to the host

Put the container and its path on the source side. The destination is an ordinary local path, interpreted relative to your current directory:

$ mkdir -p ./container-export
$ docker cp "$CONTAINER:/etc/app/app.conf" ./container-export/app.conf
$ test -f ./container-export/app.conf && echo 'copy verified'
copy verified

3. Copy a local file into the container

Reverse the two sides to copy from the host into a container:

$ test -f ./settings/app.conf
$ docker cp ./settings/app.conf "$CONTAINER:/tmp/app.conf"
$ docker exec "$CONTAINER" test -f /tmp/app.conf
$ echo 'container copy verified'
container copy verified

The docker exec check runs a harmless test inside the container and needs it to be running. For a stopped container, verify from the host with another docker cp back to a temporary directory, or start the container only through its normal operational procedure.

Files copied into a container are normally created with the container-side root user and primary group. Files copied to the host are normally created with the user and primary group of the account invoking Docker: a frequent source of confusing write failures for an application that runs as a non-root user.

4. Preserve source ownership only when you mean to

Use archive mode when the source UID and GID must be retained:

$ docker cp --archive ./release-data "$CONTAINER:/srv/"
$ docker exec "$CONTAINER" stat -c '%u:%g %n' /srv/release-data

-a or --archive copies UID and GID information. That can make files inaccessible to the account that should use them, especially when host and container user IDs do not match. Check the ownership before enabling it, and prefer the default behaviour for ordinary configuration or data transfers.

Reading files may need no elevated privilege, but writing into a protected host directory or a container path guarded by its filesystem permissions may fail. A failed copy does not justify broadening permissions or running an entire shell as root: identify which side rejected the operation, then use the narrowest approved change.

5. Handle symbolic links deliberately

Docker copies a symbolic link itself by default when the source path is a link. Add -L or --follow-link when you explicitly need the link target:

$ docker cp "$CONTAINER:/etc/app/current" ./container-export/current-link
$ docker cp --follow-link "$CONTAINER:/etc/app/current" ./container-export/current-target
$ test -L ./container-export/current-link && echo 'link preserved'
link preserved

Do not use --follow-link on an untrusted or changing path without checking where it resolves: following a link can copy data outside the directory you thought you selected. The same option matters when a local source is a symbolic link.

6. Stream a file as a tar archive

A dash means a tar stream, not plain file contents. Use it as the destination to send an archive to standard output:

$ docker cp "$CONTAINER:/var/log/app.log" - | tar -xO
first log line
second log line

This is useful for inspection or a pipeline, but the output is an archive stream. Do not redirect it to a file and treat that file as the original log. To extract a directory into a host directory, choose a destination and let tar unpack it:

$ mkdir -p ./logs-from-container
$ docker cp "$CONTAINER:/var/log/app" - | tar -xf - -C ./logs-from-container
$ test -d ./logs-from-container && echo 'archive extracted'
archive extracted

A dash as the source reads a tar archive from standard input and extracts it into a directory in the container. That destination must be a directory. Treat tar input as trusted data: extracting an archive can create or overwrite files in the selected destination.

7. Avoid special filesystem paths and destructive clean-up

Some paths cannot be copied reliably with docker cp, including resources under /proc, /sys, /dev, tmpfs and mounts created by the user in the container. For those cases, the Docker documentation's tar-through-docker exec pattern may work, but it runs a command inside the container and needs review for the specific path.

Copying changes the destination. Before replacing an existing configuration or deleting a test copy, inspect it and keep a backup if recovery matters:

$ cmp -s ./container-export/app.conf ./known-good/app.conf
$ echo "comparison status: $?"
comparison status: 1

Warning: Status 1 here means the files differ; it is not a safe signal to delete either one. If you deliberately need to undo a copy, remove only the exact destination you created, after checking it with pwd and ls -l. rm is irreversible unless you have a backup. Removing a file from the container also changes its state, so use the application's normal configuration or rollback process when the file is service-critical.

Done means