How to Explore a Docker Container's File System: View Files, Understand Image Contents, and Access Without SSH
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, anddocker 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
sudoaccess on the host system for advanced namespace and host-level storage driver exploration.
Table of Contents#
- Understanding Docker’s File System Architecture
- Method 1: Explore a Running Container
- Method 2: Explore Stopped Containers, Images, and Distroless Environments
- Method 3: Deep Image Inspection and Layer Analysis
- Method 4: Host-Level and Namespace Exploration (Advanced)
- Quick Reference & Comparison Matrix
- Best Practices for Container File System Exploration
- Troubleshooting Common Issues
- Conclusion
- 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 asRUN,COPY, orADD) 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 psExample 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-server2. 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/shIf the container image is based on Debian, Ubuntu, Fedora, or Red Hat, bash is typically available:
docker exec -it my-web-server /bin/bashTip: Minimal images like Alpine Linux do not include
bashby default. If you encounter an error likeexec: "/bin/bash": stat /bin/bash: no such file or directory, invoke/bin/shinstead.
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 -h4. 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/shTracking 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-serverExample 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.confdocker 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.html3. 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-certsMethod 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/shOverriding 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:latestKey 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.tar2. 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 exportvsdocker 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.jsonOption 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.tarTroubleshooting 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-debian12Inside the docker debug session:
- The target container's root file system is mounted and accessible under
/proc/1/rootor 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:alpineExample 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:alpineVisualizing 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.
Navigating the Storage Driver Directory (/var/lib/docker/overlay2)#
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-serverExample 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.../mergedCaution: 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/shYou are now operating inside the container’s process and file system view directly through the Linux kernel.
Quick Reference & Comparison Matrix#
| Method | Running Containers | Stopped Containers | Shell-less (Distroless) | Requires Host Root | Primary Use Case |
|---|---|---|---|---|---|
docker exec | Yes | No | No | No | Interactive exploration & debugging |
docker diff | Yes | Yes | Yes | No | Quick check of modified files |
docker cp | Yes | Yes | Yes | No | Copying files to/from host for analysis |
docker run --rm | N/A (Creates new) | N/A (Image only) | No | No | One-off image exploration |
docker export | Yes | Yes | Yes | No | Extracting full flat rootfs tarball |
docker debug | Yes | Yes | Yes | No | Comprehensive shell-less & crash debugging |
dive | N/A (Image only) | N/A (Image only) | Yes | No | Layer-by-layer size & build analysis |
Host overlay2 | Yes (MergedDir) | Yes (UpperDir) | Yes | Yes (Host) | Low-level host storage inspection |
nsenter | Yes | No | No | Yes (Host) | Kernel-level emergency debugging |
Best Practices for Container File System Exploration#
-
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 theDockerfile, using volumes, or updating configuration repositories. -
Always Use
--rmfor Exploration Containers
When launching ad-hoc containers for testing (docker run -it <image> /bin/sh), always include the--rmflag. This ensures ephemeral containers and their file system layers are cleaned up upon exit. -
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 -
Never Install SSH Daemons in Container Images
Rely ondocker exec,docker debug, anddocker cp. Installing SSH inside containers adds unnecessary package bloat, requires key/credential management, and violates container security best practices. -
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/shinstead: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
distrolessorscratchbase image without any shell binary. - Solution:
- Use
docker cpto 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.
- Use
3. Error response from daemon: Container <ID> is not running#
- Cause:
docker execcannot 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>
- Inspect crash logs:
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
- Run the exec session as root:
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#
- Docker Documentation:
docker execCLI Reference - Docker Documentation:
docker cpCLI Reference - Docker Documentation:
docker diffCLI Reference - Docker Documentation:
docker exportCLI Reference - Docker Documentation:
docker debugCLI Reference - Docker Documentation: Docker Storage Drivers Overview
- Dive: A tool for exploring a docker image and layer contents