How to Explore a Docker Container's File System: View Files, Understand Image Contents, and Access Without SSH

TutorialPedia Team

Date Updated

Docker containers have revolutionized how applications are packaged, tested, and deployed across environments, offering process isolation, consistency, and portability. However, during daily development and operations—whether debugging misconfigurations, verifying file placements, investigating crash loops, or understanding how an image is constructed—you often need to look inside a container’s file system.

Unlike traditional virtual machines, containers are lightweight and ephemeral. Running an SSH daemon inside a container is considered an anti-pattern: it introduces security attack surfaces, bloats image size, and breaks the single-responsibility principle.

Fortunately, Docker provides robust native commands alongside powerful ecosystem tools to inspect container file systems—whether a container is running, stopped, crashed, or even if the underlying image lacks a shell entirely.

In this comprehensive guide, we will explore practical, SSH-free methods to inspect container and image file systems. We will cover fundamental commands, layer analysis, distroless container debugging, host-level inspection, and operational best practices.


Prerequisites#

To follow along with the examples in this guide, make sure you have:

  • Basic familiarity with standard Docker CLI commands (such as docker run, docker ps, and docker images).
  • Docker Engine (version 20.10 or later; Docker Engine 24+ / 26+ recommended) or Docker Desktop.
  • Terminal access to a system running Linux, macOS, or Windows with WSL2.
  • Optional sudo access on the host system for advanced namespace and host-level storage driver exploration.

Table of Contents#

  1. Understanding Docker’s File System Architecture
  2. Method 1: Explore a Running Container
  3. Method 2: Explore Stopped Containers, Images, and Distroless Environments
  4. Method 3: Deep Image Inspection and Layer Analysis
  5. Method 4: Host-Level and Namespace Exploration (Advanced)
  6. Quick Reference & Comparison Matrix
  7. Best Practices for Container File System Exploration
  8. Troubleshooting Common Issues
  9. Conclusion
  10. References

Understanding Docker’s File System Architecture#

Before executing commands, understanding how Docker constructs and manages container file systems helps you accurately interpret what you find.

Images, Containers, and the Copy-on-Write Model#

A Docker file system operates on two distinct layers:

  • Docker Images (Read-Only): An image is an immutable stack of read-only layers. Each instruction in a Dockerfile (such as RUN, COPY, or ADD) creates a new layer containing only the diff of files added, modified, or deleted relative to previous layers.
  • Docker Containers (Read-Write Layer): When you instantiate a container from an image, Docker adds a thin, writable container layer (often referred to as the upper layer) on top of the underlying image layers.
+-------------------------------------------------------+
| Container Writable Layer (Read-Write: /tmp, logs, etc)|
+-------------------------------------------------------+
| Image Layer 3: Application Code (Read-Only)           |
+-------------------------------------------------------+
| Image Layer 2: Dependencies & Runtime (Read-Only)     |
+-------------------------------------------------------+
| Image Layer 1: Base OS Rootfs (Read-Only)             |
+-------------------------------------------------------+

When a process inside the container modifies an existing file that originates from a read-only image layer, Docker uses a Copy-on-Write (CoW) strategy. The file is copied from the underlying read-only layer into the writable container layer before the write takes place. The original image layer remains untouched.

Storage Drivers and OverlayFS (overlay2)#

Docker manages layer composition using pluggable storage drivers. In modern Linux distributions, overlay2 is the standard, highly optimized default storage driver (legacy drivers like AUFS and devicemapper have been deprecated and removed).

overlay2 merges multiple directories on the Linux host into a single unified mount point:

  • lowerdir: The read-only directories corresponding to the image layers.
  • upperdir: The writable directory representing the container's mutable layer.
  • workdir: An internal working directory used by Linux OverlayFS for atomic file operations.
  • merged: The unified mount view presented to processes running inside the container.

Method 1: Explore a Running Container#

When a container is actively running, Docker provides native commands to inspect files, execute commands, and transfer data without stopping the workload.

Interactive Shell Access with docker exec#

The standard and most direct method to explore a running container is docker exec, which runs a new process inside the container’s existing namespaces.

1. Identify the container#

First, list active containers to obtain the container ID or name:

docker ps

Example Output:

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                NAMES
8f1c49b6d8a2   nginx:alpine   "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   0.0.0.0:80->80/tcp   my-web-server

2. Open an interactive shell#

Run an interactive terminal session using -i (interactive / keep STDIN open) and -t (allocate a pseudo-TTY):

docker exec -it my-web-server /bin/sh

If the container image is based on Debian, Ubuntu, Fedora, or Red Hat, bash is typically available:

docker exec -it my-web-server /bin/bash

Tip: Minimal images like Alpine Linux do not include bash by default. If you encounter an error like exec: "/bin/bash": stat /bin/bash: no such file or directory, invoke /bin/sh instead.

3. Run non-interactive commands directly#

You do not have to spawn a full interactive shell just to inspect a directory or check a configuration:

# List files in the web root
docker exec my-web-server ls -la /usr/share/nginx/html
 
# Print configuration file contents
docker exec my-web-server cat /etc/nginx/nginx.conf
 
# Check disk space inside the container
docker exec my-web-server df -h

4. Override the execution user#

If files inside the container are restricted by permissions, you can execute commands as the root user with -u 0 or -u root:

docker exec -u root -it my-web-server /bin/sh

Tracking File System Changes with docker diff#

To quickly check which files have been created, modified, or deleted in the container's writable layer since it started, use docker diff. This avoids needing to manually scan directories.

docker diff my-web-server

Example Output:

C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
C /var/log
C /var/log/nginx
A /var/log/nginx/access.log
A /var/log/nginx/error.log
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
D /etc/nginx/conf.d/old_ssl.conf

docker diff outputs three status indicators:

  • A (Added): A file or directory was added to the writable layer.
  • C (Changed): A file or directory was modified.
  • D (Deleted): A file or directory was removed from the container's view.

Copying Files Between Container and Host with docker cp#

When you need to analyze log files, retrieve a heap dump, or inspect binaries using host tools (like IDEs or security scanners), use docker cp.

docker cp works on both running and stopped containers and does not require an active shell or network connection inside the container.

1. Copy from container to host#

# Copy a single configuration file
docker cp my-web-server:/etc/nginx/nginx.conf ./nginx.conf.backup
 
# Copy an entire directory
mkdir -p ./logs
docker cp my-web-server:/var/log/nginx/. ./logs/

2. Copy from host to container#

# Copy a local custom page into the container
docker cp ./index.html my-web-server:/usr/share/nginx/html/index.html

3. Preserve file ownership and timestamps#

Use the -a (archive) flag to preserve UID/GID and file mode permissions when copying:

docker cp -a my-web-server:/etc/ssl/certs ./extracted-certs

Method 2: Explore Stopped Containers, Images, and Distroless Environments#

Often you need to inspect containers that have stopped, exited unexpectedly, or analyze images without leaving persistent state behind.

Starting Ephemeral Exploration Containers with docker run --rm#

If you want to explore an image directly without starting its default service entrypoint (or if the default command crashes immediately), start an ephemeral container with an interactive shell:

docker run --rm -it alpine:latest /bin/sh

Overriding Default Entrypoints#

If an image defines a custom ENTRYPOINT that prevents shell access (e.g., automatically launching a Python app or database daemon), override the entrypoint with --entrypoint:

docker run --rm -it --entrypoint /bin/sh my-custom-app:latest

Key flags explained:

  • --rm: Automatically removes the container and its writable layer once you exit the shell, preventing disk clutter.
  • -it: Keeps STDIN open and attaches a terminal.
  • --entrypoint: Bypasses the preconfigured image entrypoint to directly invoke your chosen shell.

Exporting Container Root File Systems with docker export#

docker export flattens a container's entire file system (all image layers plus the writable container layer) into a single .tar archive. This technique works whether the container is running or stopped.

1. Export the container file system#

docker export my-web-server -o container_rootfs.tar

2. Inspect the archive contents#

You can list or extract the archive using standard host utilities:

# List files inside the tar archive without extracting
tar -tf container_rootfs.tar | grep nginx
 
# Extract to a local directory for full inspection
mkdir -p ./rootfs_inspect
tar -xf container_rootfs.tar -C ./rootfs_inspect
 
# Explore with host tools
cd ./rootfs_inspect
ls -la
cat etc/nginx/nginx.conf

docker export vs docker save:

  • docker export <container> flattens the container's live file system into a single rootfs tarball, discarding individual layer history and metadata.
  • docker save <image> exports an image repository archive including all individual layer tarballs, manifest files, and build history.

Inspecting Shell-Less (Distroless / Scratch) Containers#

Modern secure production containers often use minimal base images like Google's distroless or scratch. These hardened images contain only your compiled application and runtime dependencies—they contain no package managers and no shell (/bin/sh or /bin/bash do not exist).

If you attempt docker exec -it <container> /bin/sh, you will receive an error: OCI runtime exec failed: exec: "/bin/sh": stat /bin/sh: no such file or directory

To explore distroless containers without SSH or shells:

Option A: Extract with docker cp or docker export#

Because docker cp and docker export interact with the container file system via the Docker daemon rather than executing binaries inside the container, they work seamlessly on distroless containers:

docker cp distroless-app:/app/config.json ./config.json

Option B: Export the image rootfs via an ephemeral container#

# Create a stopped container instance without running it
docker create --name temp-inspect gcr.io/distroless/static-debian12
docker export temp-inspect -o distroless-fs.tar
docker rm temp-inspect
 
# Extract and inspect
tar -tf distroless-fs.tar

Troubleshooting with docker debug#

For Docker Desktop and modern Docker CLI installations, docker debug provides an integrated debugging environment. It attaches an ephemeral debug sandbox equipped with a full toolkit (including BusyBox, curl, text editors, and Nix package manager support) to any target container or image—even if it is stopped, crashed, or completely lacks a shell.

# Debug a running or stopped container
docker debug my-web-server
 
# Debug a static or distroless image directly
docker debug gcr.io/distroless/nodejs20-debian12

Inside the docker debug session:

  • The target container's root file system is mounted and accessible under /proc/1/root or the debug entry root.
  • You have access to built-in diagnostic tools without modifying the original container or installing debug packages into production images.

Method 3: Deep Image Inspection and Layer Analysis#

Understanding the structure of an image helps optimize build sizes, audit dependencies, and locate which build step introduced specific files.

Inspecting Image History and Layer Metadata#

1. View build history with docker history#

To see the Dockerfile commands that formed each layer along with their corresponding sizes:

docker history --no-trunc nginx:alpine

Example Output:

IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
a1b2c3d4e5f6   2 days ago     CMD ["nginx" "-g" "daemon off;"]                0B        buildkit.dockerfile.v0
<missing>      2 days ago     EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      2 days ago     COPY default.conf /etc/nginx/conf.d/default.conf 1.09kB   buildkit.dockerfile.v0
<missing>      2 days ago     RUN /bin/sh -c apk add --no-cache nginx ...     18.5MB    buildkit.dockerfile.v0
<missing>      3 weeks ago    /bin/sh -c #(nop) ADD file:alpine-rootfs.tar    7.38MB    

2. Inspect layer storage references#

To retrieve the cryptographic diff IDs for the root file system layers:

docker image inspect --format '{{json .RootFS.Layers}}' nginx:alpine

Visualizing Layers Interactively with Dive#

Dive is a popular open-source terminal utility designed for exploring Docker image layers, visualizing layer-by-layer file additions, and detecting wasted space.

# Run Dive directly via Docker without installing a local binary
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest nginx:alpine
┌ Layer Details ─────────────────────────┐ ┌ Image Details ──────────────────────────┐
│ ID: 3e8fa910d210                       │ │ Total Size: 26.5 MB                     │
│ Command: RUN apk add --no-cache ...    │ │ Efficiency: 98 %                        │
│ Size: 18.5 MB                          │ │ Wasted Space: 512 kB                    │
└────────────────────────────────────────┘ └─────────────────────────────────────────┘
┌ Layer Tree ────────────────────────────┐ ┌ Current Layer Contents ─────────────────┐
│ ● Layers (3)                           │ │ ├── etc                                 │
│   ├── [ 7.4 MB] ADD alpine-rootfs.tar  │ │ │   ├── nginx                           │
│   └── [18.5 MB] RUN apk add ...        │ │ │   │   └── nginx.conf [Modified]       │
│                                        │ │ └── usr/sbin/nginx [Added]              │
└────────────────────────────────────────┘ └─────────────────────────────────────────┘

Key features of Dive:

  • Interactive Layer Navigation: Use arrow keys to step through each layer and inspect the exact file modifications (Added, Modified, Removed).
  • File Aggregation View: Switch views to inspect the aggregate file system and identify large or redundant files.
  • CI/CD Integration: Evaluate an image's efficiency score to prevent container bloat during automated builds.

Method 4: Host-Level and Namespace Exploration (Advanced)#

On Linux hosts with administrative privileges, you can inspect container file systems directly from the host kernel level.

When Docker runs on Linux with the overlay2 storage driver, container files reside directly on the host's physical storage under /var/lib/docker/overlay2/.

1. Locate the storage driver paths#

Run docker inspect to inspect the GraphDriver data:

docker inspect --format '{{json .GraphDriver.Data}}' my-web-server

Example Output:

{
  "LowerDir": "/var/lib/docker/overlay2/abc123.../diff:/var/lib/docker/overlay2/def456.../diff",
  "MergedDir": "/var/lib/docker/overlay2/xyz789.../merged",
  "UpperDir": "/var/lib/docker/overlay2/xyz789.../diff",
  "WorkDir": "/var/lib/docker/overlay2/xyz789.../work"
}

2. Access the directories directly (Root Required)#

  • UpperDir: Contains all modifications written to the container's writable layer.
  • MergedDir: Contains the live unified view of the container's file system while the container is running.
# View all container modifications on the host
sudo ls -la /var/lib/docker/overlay2/xyz789.../diff
 
# View the live merged file system (while container is running)
sudo ls -la /var/lib/docker/overlay2/xyz789.../merged

Caution: Treat files in /var/lib/docker/ as read-only for manual operations. Modifying files directly in these directories bypasses the Docker daemon and can corrupt container state or cause synchronization errors.


Entering Container Mount Namespaces with nsenter#

If the Docker daemon is unresponsive or standard execution tools fail, you can enter the container’s Linux mount namespace directly using nsenter.

1. Retrieve the container's main Process ID (PID)#

PID=$(docker inspect --format '{{.State.Pid}}' my-web-server)
echo "Container PID: $PID"

2. Enter the namespace#

Run nsenter targeting the container's PID to execute a shell within its mount, IPC, UTS, and network namespaces:

sudo nsenter --target $PID --mount --uts --ipc --net --pid /bin/sh

You are now operating inside the container’s process and file system view directly through the Linux kernel.


Quick Reference & Comparison Matrix#

MethodRunning ContainersStopped ContainersShell-less (Distroless)Requires Host RootPrimary Use Case
docker execYesNoNoNoInteractive exploration & debugging
docker diffYesYesYesNoQuick check of modified files
docker cpYesYesYesNoCopying files to/from host for analysis
docker run --rmN/A (Creates new)N/A (Image only)NoNoOne-off image exploration
docker exportYesYesYesNoExtracting full flat rootfs tarball
docker debugYesYesYesNoComprehensive shell-less & crash debugging
diveN/A (Image only)N/A (Image only)YesNoLayer-by-layer size & build analysis
Host overlay2Yes (MergedDir)Yes (UpperDir)YesYes (Host)Low-level host storage inspection
nsenterYesNoNoYes (Host)Kernel-level emergency debugging

Best Practices for Container File System Exploration#

  1. Avoid Manual In-Place Production Edits
    Files modified in a container's writable layer are lost when the container is recreated. Use container file exploration strictly for diagnosis. Implement persistent changes by modifying the Dockerfile, using volumes, or updating configuration repositories.

  2. Always Use --rm for Exploration Containers
    When launching ad-hoc containers for testing (docker run -it <image> /bin/sh), always include the --rm flag. This ensures ephemeral containers and their file system layers are cleaned up upon exit.

  3. Prune Temporary Artifacts
    Exported tarballs (.tar) and extracted directories can consume substantial host storage. Remove temporary inspection files after analysis:

    rm -rf container_rootfs.tar ./rootfs_inspect
  4. Never Install SSH Daemons in Container Images
    Rely on docker exec, docker debug, and docker cp. Installing SSH inside containers adds unnecessary package bloat, requires key/credential management, and violates container security best practices.

  5. Protect Docker Daemon Sockets
    When using third-party inspection containers (such as Dive) that mount /var/run/docker.sock, only use trusted images, as access to the Docker socket grants root-equivalent control over the host.


Troubleshooting Common Issues#

1. exec: "/bin/bash": stat /bin/bash: no such file or directory#

  • Cause: The container is built on a minimal base image (such as Alpine Linux or BusyBox) that only includes /bin/sh.
  • Solution: Execute with /bin/sh instead:
    docker exec -it <container> /bin/sh

2. exec: "/bin/sh": stat /bin/sh: no such file or directory (Distroless Containers)#

  • Cause: The container uses a distroless or scratch base image without any shell binary.
  • Solution:
    • Use docker cp to copy target files to your host machine.
    • Export the container file system with docker export <container> -o rootfs.tar.
    • Use docker debug <container> to attach an external debugging shell.

3. Error response from daemon: Container <ID> is not running#

  • Cause: docker exec cannot run inside a stopped or crashed container.
  • Solution:
    • Inspect crash logs: docker logs <container>
    • Copy files out while stopped: docker cp <container>:/path/to/file ./
    • Export the file system: docker export <container> -o container.tar
    • Start an interactive container from the same image: docker run --rm -it --entrypoint /bin/sh <image>

4. Permission denied When Accessing Container Files#

  • Cause: The container runs under a non-root UID that cannot access restricted configuration or log paths, or host permissions prevent reading files copied with docker cp.
  • Solution:
    • Run the exec session as root:
      docker exec -u 0 -it <container> /bin/sh
    • On the host, verify permissions or adjust ownership of copied files:
      sudo chown -R $(id -u):$(id -g) ./copied_files

Conclusion#

Inspecting a Docker container's file system is an essential skill for troubleshooting application errors, verifying configuration values, and optimizing container image sizes.

By utilizing native Docker commands like docker exec, docker diff, and docker cp, you can quickly diagnose running workloads without the security risks and overhead of SSH. For stopped containers, distroless images, and deep layer audits, techniques like docker export, docker debug, and Dive provide comprehensive visibility into every layer of your container stack.


References#