Skip to content

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
Container

The 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.html

We can copy it into an nginx container:

docker container cp ./index.html nginx:/usr/share/nginx/html/index.html

The general pattern is:

docker container cp <host-file> <container>:<path>

For example:

docker container cp ./app.conf mycontainer:/etc/app/app.conf

Copy From Container to Host

The direction can also be reversed.

docker container cp nginx:/usr/share/nginx/html/index.html ./index.html

The general pattern is:

docker container cp <container>:<path> <host-path>

For example:

docker container cp mycontainer:/var/log/app.log ./app.log

Copy 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-config

Note

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 ls

might show:

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    abc123...      ...           192MB

But:

docker container ls -a -s

might 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 layer

The 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 -s

You 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 Layer

Each 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.conf

A 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.conf

The 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 ls

Then list containers with their writable sizes:

docker container ls -a -s

For 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 nginx1

You may see something similar to:

A /tmp/testfile

docker 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 = Deleted

For example:

A /tmp/testfile
C /etc/nginx/nginx.conf
D /some/file

This 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 ./testfile

Now 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 container

Step 5: Inspect the Container

You can also inspect the container’s metadata:

docker container inspect nginx1

The 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 df

You can get more detailed information with:

docker system df -v
Last updated on