1. Overview

When we work with Docker, sometimes we need to check configuration or log files inside a container.

In this quick tutorial, we’ll see how to inspect the filesystem of a Docker container to help us address such situations.

2. Interactive Exploring

We can explore the filesystem interactively for most containers if we get shell access to them.

2.1. Running a Container With Shell Access

Let’s start a container directly with shell access using the docker run command with the -it option:

$ docker run -it alpine
/# ls -all
...
-rwxr-xr-x    1 root     root             0 Mar  5 13:21 .dockerenv
drwxr-xr-x    1 root     root           850 Jan 16 21:52 bin
drwxr-xr-x    5 root     root           360 Mar  5 13:21 dev
drwxr-xr-x    1 root     root           508 Mar  5 13:21 etc
drwxr-xr-x    1 root     root             0 Jan 16 21:52 home
....

Here, we launched the Alpine Linux container in interactive mode and connected to its shell.

But what happens if we want to explore something that’s not directly a Linux distribution?

$ docker run -it cassandra
 ... 
INFO [MigrationStage:1] 2020-03-05 13:44:36,734 - Initializing system_auth.resource_role_permissons_index 
INFO [MigrationStage:1] 2020-03-05 13:44:36,739 - Initializing system_auth.role_members 
INFO [MigrationStage:1] 2020-03-05 13:44:36,743 - Initializing system_auth.role_permissions 
INFO [MigrationStage:1] 2020-03-05 13:44:36,747 - Initializing system_auth.roles 
INFO [main] 2020-03-05 13:44:36,764 - Waiting for gossip to settle... 
...

The Cassandra docker container comes with a default startup command, which runs Cassandra. As a result, we’re no longer connected to a shell.

Instead, we just see the standard output populated with log messages of the application.

However, we can bypass the default startup command.

Let’s pass the /bin/bash additional argument to the docker run command:

$ docker run -it cassandra /bin/bash
root@a71f71e98598:/# ls -all
total 4
...
-rwxr-xr-x   1 root root    0 Mar  5 13:30 .dockerenv
drwxr-xr-x   1 root root  920 Aug 14  2019 bin
drwxr-xr-x   1 root root    0 Mar 28  2019 boot
drwxr-xr-x   5 root root  360 Mar  5 13:30 dev
lrwxrwxrwx   1 root root   34 Aug 14  2019 docker-entrypoint.sh -> usr/local/bin/docker-entrypoint.sh
drwxr-xr-x   1 root root 1690 Mar  5 13:30 etc
...

Unfortunately, this has a nasty side-effect. The actual Cassandra application is no longer started, and we have to do this from the shell manually.

When we use this approach, we assume that we can control the startup of the container. In a production environment, this might not be possible.

2.2. Spawning a Shell in a Running Container

Fortunately, we can use the docker exec command, which allows us to connect to running containers.

Let’s first start the container we want to explore:

$ docker run cassandra
...
INFO  [MigrationStage:1] 2020-03-05 13:44:36,734 - Initializing system_auth.resource_role_permissons_index
INFO  [MigrationStage:1] 2020-03-05 13:44:36,739 - Initializing system_auth.role_members
INFO  [MigrationStage:1] 2020-03-05 13:44:36,743 - Initializing system_auth.role_permissions
INFO  [MigrationStage:1] 2020-03-05 13:44:36,747 - Initializing system_auth.roles
INFO  [main] 2020-03-05 13:44:36,764 - Waiting for gossip to settle...
...

Next, we identify the container id with docker ps:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             
00622c0645fb        cassandra           "docker-entrypoint.s…"   2 minutes ago  

Then, we pass /bin/bash as the argument with -it option to docker exec:

$ docker exec -it 00622c0645fb /bin/bash
root@00622c0645fb:/# ls -all
...
-rwxr-xr-x   1 root root    0 Mar  5 13:44 .dockerenv
drwxr-xr-x   1 root root  920 Aug 14  2019 bin
drwxr-xr-x   1 root root    0 Mar 28  2019 boot
drwxr-xr-x   5 root root  340 Mar  5 13:44 dev
lrwxrwxrwx   1 root root   34 Aug 14  2019 docker-entrypoint.sh -> usr/local/bin/docker-entrypoint.sh
drwxr-xr-x   1 root root 1690 Mar  5 13:44 etc
...

Here, we used Bash as our shell of choice. This can vary depending on which Linux distribution the container is based on.

By contrast, our first example uses Alpine Linux, which comes with the Bourne Shell by default:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED            
8408c85b3c57        alpine              "/bin/sh"           3 seconds ago  

Since Bash isn’t available, we pass /bin/sh as the argument to docker exec:

$ docker exec -it 8408c85b3c57 /bin/sh
/ # ls -all
...
-rwxr-xr-x    1 root     root             0 Mar  5 14:19 .dockerenv
drwxr-xr-x    1 root     root           850 Jan 16 21:52 bin
drwxr-xr-x    5 root     root           340 Mar  5 14:19 dev
drwxr-xr-x    1 root     root           508 Mar  5 14:19 etc
drwxr-xr-x    1 root     root             0 Jan 16 21:52 home
...

3. Non-Interactive Exploring

Sometimes, the container is stopped, and we can’t run it interactively, or it simply does not have a shell.

For example, the hello-world is a minimal container that starts from scratch. As a result, shell access is not possible.

Lucky for us, in both situations, we can dump the filesystem to our host machine for further exploration.

Let’s see how we can do this.

3.1. Exporting the Filesystem

We can export the filesystem of a container into a tar file by using the docker export command.

Let’s first run the hello-world container:

$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
....

Similarly, we first obtain the container id of a stopped container by passing the -a flag to docker ps:

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             
a0af60c72d93        hello-world           "/hello"                 3 minutes ago       
...

Then we dump the filesystem into the hello.tar file using the -o option of docker export:

$ docker export -o hello.tar a0af60c72d93

Finally, we print the contents of the archive using the tar utility with the -tvf flags:

$ tar -tvf hello.tar
-rwxr-xr-x root/0            0 2020-03-05 16:55 .dockerenv
....
drwxr-xr-x root/0            0 2020-03-05 16:55 dev/pts/
drwxr-xr-x root/0            0 2020-03-05 16:55 dev/shm/
....
-rwxr-xr-x root/0            0 2020-03-05 16:55 etc/resolv.conf
-rwxrwxr-x root/0         1840 2019-01-01 03:27 hello
...

Alternatively, we can use any archive explorer to see what’s inside.

3.2. Copying the Filesystem

We can also copy the entire filesystem using the docker cp command.

Let’s try this as well.

First, we copy the complete filesystem starting from the root (/) from our container to the test directory:

$ docker cp a0af60c72d93:/ ./test

Next, let’s print the contents of the test directory:

$ ls -all test/
total 28
..
drwxr-xr-x  4 baeldung users 4096 Mar  5 16:55 dev
-rwxr-xr-x  1 baeldung users    0 Mar  5 16:55 .dockerenv
drwxr-xr-x  2 baeldung users 4096 Mar  5 16:55 etc
-rwxrwxr-x  1 baeldung users 1840 Jan  1  2019 hello

4. Conclusion

In this quick tutorial, we discussed how to explore the filesystem of a Docker container.

We can start most containers with shell access directly with the docker run command. In addition, we can spawn a shell for running containers with the help of docker exec.

When it comes to stopped containers or minimal containers, we can simply export or even copy the entire filesystem locally.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.