docker cp looks like a plain file copy, but its overwrite and directory rules have caught out plenty of people. This guide builds a repeatable way to copy files between a container and the host without the path, ownership and overwrite traps. The examples match Docker Engine CLI 29.8.1 from the installed docker-ce-cli package, version 5:29.8.1-1~ubuntu.24.04~noble.
Allow about fifteen minutes. You need Docker access and an existing container. The container may be running or stopped. These examples use ordinary Docker client commands, but access to the daemon may require sudo on your host. If you use sudo, apply it to the Docker command itself and remember that files written to the host may then be owned by root.
Start by identifying the container you intend to touch. This is a read-only check:
$ docker container ls -a --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID NAMES STATUS
... app-container Exited (0) ...
Replace app-container in the following examples with an exact name or ID from your output. Check the client version and syntax before copying anything:
$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker container cp --help
Checkpoint: the source or destination that contains a container path must have the form CONTAINER:/path. The leading slash is optional for container paths, but keeping it makes the boundary obvious. A local relative path is resolved from the directory where you run the command.
Use the container as the source and a local path as the destination. First create a destination directory so the result is unambiguous:
$ mkdir -p ./docker-export
$ docker container cp app-container:/var/log/app.log ./docker-export/app.log
$ test -f ./docker-export/app.log && wc -c ./docker-export/app.log
1842 ./docker-export/app.log
The byte count is only an example. Your value depends on the file. A successful command with no progress output is normal, especially when no terminal is attached or when --quiet is used.
If the local destination already exists as a file, Docker overwrites its contents. If it exists as a directory, Docker places the source basename inside it. Treat this as a destructive boundary: before copying over a valuable file, make a backup or choose a new destination. There is no undo operation in docker container cp; recovery means copying a backup back into place.
Directory copying is where the trailing path matters. With an existing local directory, a source without /. copies the source directory itself below the destination:
$ mkdir -p ./docker-export
$ docker container cp app-container:/etc/my-app ./docker-export
$ test -f ./docker-export/my-app/app.conf && echo 'directory copied'
directory copied
To copy the contents into the destination instead, put /. at the end of the source:
$ mkdir -p ./config-copy
$ docker container cp app-container:/etc/my-app/. ./config-copy
$ find ./config-copy -maxdepth 1 -type f -print
The destination parent must already exist when Docker needs it. Docker does not create missing parent directories for DEST_PATH. Create them explicitly, then verify the expected file or directory exists.
The same rule works in the other direction. This copies the contents of a local directory into an existing directory in the container:
$ docker container cp ./config-copy/. app-container:/etc/my-app
$ docker container exec app-container test -f /etc/my-app/app.conf
$ printf 'container file status: %s\n' "$?"
container file status: 0
The final check uses docker exec only to inspect the result. It does not restart the container or reload the application. If the application reads configuration only at start-up, arrange its normal reload or restart procedure separately.
By default, copying to a container creates files with the container destination user and primary group, commonly root. Copying to the host creates files for the user who invoked the Docker command. Permissions are preserved where possible, but ownership is a separate concern. Inspect the result rather than assuming it:
$ docker container exec app-container stat -c '%U:%G %a %n' /etc/my-app/app.conf
$ stat -c '%U:%G %a %n' ./config-copy/app.conf
Use --archive or -a when preserving source UID and GID is explicitly required. That can create awkward or unusable ownership on the other side, so do not add it as a reflex. A service may need a particular numeric UID, not the source machine's account name.
Symbolic links in SRC_PATH are copied as links by default. Add --follow-link or -L when you deliberately want the link target's contents instead:
$ docker container cp -L ./current-config app-container:/etc/my-app/config
$ docker container exec app-container stat -c '%F %n' /etc/my-app/config
Follow-link changes what data is read. Review the link target before using it, especially when the source is writable by another user.
A hyphen turns one side into a tar stream. To inspect a container file without creating a permanent local copy, stream it to standard output and extract or filter it deliberately:
$ docker container cp app-container:/var/log/app.log - | tar -xO | sed -n '1,20p'
For a stream from standard input, the source is the hyphen and the container destination must be a directory:
$ tar -C ./config-copy -cf - . | docker container cp - app-container:/etc/my-app
Inspect an archive before extracting untrusted content. Tar paths and links can write outside an intended directory when handled carelessly. Do not pipe an archive from an unknown producer straight into a sensitive container path. The ordinary copy command is safer when you do not need streaming.
The -q or --quiet option suppresses progress output. It changes display only, not copy semantics, and is useful when the command's standard output is part of a script's data flow.
A missing source path, a missing destination parent, or a directory-to-file mismatch is an error. Check each path independently before retrying:
$ docker container exec app-container test -e /etc/my-app
$ printf 'source status: %s\n' "$?"
$ test -d ./config-copy
$ printf 'local destination status: %s\n' "$?"
A colon separates the container name from its path. If a local filename itself contains a colon, make the local path explicit, such as ./file:name.txt or /tmp/file:name.txt. Quoting protects spaces and shell characters, but it does not remove the need for that explicit relative or absolute path.
Some container filesystems cannot be copied through this command, including resources under /proc, /sys and /dev, tmpfs content and user-created mounts. Do not treat a failed copy of one of these as evidence that ordinary paths are broken. If you genuinely need such data, Docker documents a carefully constructed tar pipeline through docker exec; review the exact source and destination before using it.
Finally, remember that docker container cp changes files but does not update an image. Changes in a container's writable layer disappear when the container is removed. For repeatable configuration, put the file in an image build or use a declared bind mount or volume, then copy only for inspection, one-off transfer or recovery.
/. deliberately when copying directory contents.-a or -L only for a stated reason.test, stat, wc or an equivalent read-only check.