Copying Files to Container
A Docker container may look like a complete filesystem, but under the hood, it is not a copy of the image it came from. In this article, we'll see how files move between the host and containers using
docker cp, and why a container can appear surprisingly small compared to its image.
We'll also use Docker's own tools to verify what is actually stored in the container's writable layer.Why Copy Files to Containers?
When working with containers, you will often need to move files between your host machine and a running container.
For example, you may want to:
- Copy a configuration file into a container.
- Retrieve a generated file from a container.
- Copy application files during development.
- Transfer scripts or test data.
- Inspect or troubleshoot files inside a container.
Docker provides the docker cp command for this purpose.
The basic idea is:
Host
↕
docker cp
↕
ContainerThe copy operation does not require an SSH server or any other service to be running inside the container. Because docker cp doesn’t copy files by connecting to a service inside the container.
Docker itself has access to the container’s filesystem through the container runtime and storage layers.
How to Copy Files
The basic syntax is:
docker container cp <source> <destination>The source or destination can be either on the host or inside the container.
Copy From Host to Container
Suppose we have a file on the host:
./index.htmlWe can copy it into an nginx container:
docker container cp ./index.html nginx:/usr/share/nginx/html/index.htmlThe general pattern is:
docker container cp <host-file> <container>:<path>For example:
docker container cp ./app.conf mycontainer:/etc/app/app.confCopy From Container to Host
The direction can also be reversed.
docker container cp nginx:/usr/share/nginx/html/index.html ./index.htmlThe general pattern is:
docker container cp <container>:<path> <host-path>For example:
docker container cp mycontainer:/var/log/app.log ./app.logCopy Directories
docker cp also works with directories.
Copy a directory from the host:
docker container cp ./config nginx:/etc/myapp/Copy a directory from the container:
docker container cp nginx:/etc/nginx ./nginx-configNote
docker cp works with both running and stopped containers. The container does not need to be running for Docker to copy files from or to its filesystem.
Ownership and Permissions
When copying files, pay attention to file ownership and permissions.
You can use the -a option to preserve ownership and permissions as much as possible:
docker container cp -a ./config nginx:/etc/myapp/Without -a, copied files can have ownership behavior that differs from the original files.
For production images, it is usually better to create the required files as part of the image build process rather than relying on docker cp as a deployment mechanism.
Why Is the Container Size Much Smaller Than the Image?
If you compare Docker images and containers, you may notice something interesting.
For example:
docker image lsmight show:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest abc123... ... 192MBBut:
docker container ls -a -smight show:
CONTAINER ID IMAGE COMMAND SIZE
abc456... nginx "/docker-entrypoint..." 12B (virtual 192MB)At first, this can look confusing.
How can an image be 192MB while the container appears to be only 12B?
The reason is Docker’s layered filesystem.
Reason
A Docker image is made up of one or more read-only layers.
When Docker creates a container from an image, it does not make a complete copy of all those image files.
Instead, Docker adds a small writable container layer on top of the image layers.
Conceptually:
Container
┌──────────────────────────────┐
│ Writable Container Layer │ ← Changes made by container
├──────────────────────────────┤
│ Image Layer │
├──────────────────────────────┤
│ Image Layer │
├──────────────────────────────┤
│ Image Layer │
└──────────────────────────────┘The image layers are shared and remain read-only.
The container gets its own writable layer where changes are stored.
This means creating a container from a large image does not require copying the entire image into a new directory.
For example:
Image
└── 192 MB read-only layers
Container
├── 192 MB shared image layers
└── 12 B writable layerThe 12B in this example represents the container’s writable layer, not the total amount of data available to the container.
Understanding SIZE and VIRTUAL SIZE
Run:
docker container ls -a -sYou may see:
CONTAINER ID IMAGE COMMAND SIZE
abc456... nginx "/docker-entrypoint..." 12B (virtual 192MB)The important part is:
12B (virtual 192MB)The first value represents the container’s writable layer.
The virtual size represents the writable layer plus the size of the image layers it is based on.
You can make the output easier to read with:
docker container ls -a -s \
--format "{{.Names}}: {{.Size}}"For example:
nginx1: 12B (virtual 192MB)
nginx2: 12B (virtual 192MB)This also demonstrates an important property of Docker images:
Multiple containers created from the same image can share the same underlying image layers.
For example:
┌── Container A
│ Writable Layer
│
nginx Image ─────┼── Container B
Layers │ Writable Layer
│
└── Container C
Writable LayerEach container gets its own writable layer, while the read-only image layers can be shared.
Reason: Copy-on-Write
The mechanism behind this behavior is commonly described as copy-on-write (CoW).
Initially, the container can read files from the image layers.
If a process modifies an existing file, the filesystem driver may copy the relevant file data into the container’s writable layer before applying the modification.
If a process creates a new file, that file is stored in the writable layer.
For example, suppose the image contains:
/etc/app/config.confA container starts with that file available through the read-only image layers.
If a process modifies it:
Image Layer
└── config.conf
↓ modification
Container Writable Layer
└── modified config.confThe exact implementation details depend on the storage driver, but the important concept is that containers don’t begin with a full physical copy of the image.
Verification
We can verify this behavior rather than simply trusting the size output.
Step 1: Compare Image and Container Sizes
First, list the images:
docker image lsThen list containers with their writable sizes:
docker container ls -a -sFor easier reading:
docker container ls -a -s \
--format "{{.Names}}: {{.Size}}"You may see:
nginx1: 12B (virtual 192MB)The difference between the writable size and virtual size demonstrates that most of the container’s filesystem comes from the underlying image layers.
Step 2: Create a File Inside the Container
Let’s create a relatively large file inside the container:
docker container exec nginx1 \
sh -c 'dd if=/dev/zero of=/tmp/testfile bs=1M count=10'Now check the size again:
docker container ls -a -s \
--format "{{.Names}}: {{.Size}}"You should see the writable size increase by roughly the amount of data that was created, subject to filesystem and reporting details.
Conceptually:
Before:
nginx1: 12B (virtual 192MB)
After:
nginx1: ~10MB (virtual ~202MB)The exact numbers can differ.
Step 3: Use docker diff
Docker also provides a useful command for seeing filesystem changes made inside a container:
docker container diff nginx1You may see something similar to:
A /tmp/testfiledocker diff reports changes to the container’s filesystem relative to the image it was created from.
The status letters mean:
A = Added
C = Changed
D = DeletedFor example:
A /tmp/testfile
C /etc/nginx/nginx.conf
D /some/fileThis is particularly useful when investigating what a container has changed since it was created.
Step 4: Copy the File Out
We can combine this with docker cp:
docker container cp nginx1:/tmp/testfile ./testfileNow the file exists on the host as well.
This demonstrates two different concepts:
docker cp
↓
Move data between host and container
docker diff
↓
See filesystem changes made by the containerStep 5: Inspect the Container
You can also inspect the container’s metadata:
docker container inspect nginx1The output contains information about the container’s storage configuration, including its storage-layer identifiers and paths where applicable.
However, the exact low-level storage details are dependent on Docker’s storage driver and platform, so they should not generally be relied upon by applications or scripts.
A Useful Mental Model
The easiest way to remember all of this is:
Docker Image
┌──────────────────────────┐
│ Read-only image layers │
│ │
│ 192 MB │
└──────────────────────────┘
│
│ shared
┌─────┴─────┐
↓ ↓
Container A Container B
┌───────────┐ ┌───────────┐
│ Writable │ │ Writable │
│ 10 MB │ │ 2 MB │
└───────────┘ └───────────┘The image provides the initial filesystem.
Each container adds its own writable layer.
That is why a container’s reported writable size can be tiny even when the image it uses is hundreds of megabytes or several gigabytes in size.
Important
docker container ls -s should not be interpreted as “the total disk space occupied by this container.”
The writable size tells you about the container’s writable layer, while the virtual size includes the image layers as well. Those image layers may be shared with other containers.
For actual disk-usage investigation across Docker’s images, containers, volumes, and build cache, docker system df is often a better high-level tool:
docker system dfYou can get more detailed information with:
docker system df -v