惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
M
MIT News - Artificial intelligence
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
罗磊的独立博客
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
美团技术团队
F
Fortinet All Blogs
I
Intezer
IT之家
IT之家
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Know Your Adversary
Know Your Adversary
P
Proofpoint News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
AWS News Blog
AWS News Blog
P
Palo Alto Networks Blog
S
Securelist
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Project Zero
Project Zero
U
Unit 42
博客园_首页
博客园 - 司徒正美
S
Security Affairs
V
V2EX
T
Threatpost
T
Tailwind CSS Blog
GbyAI
GbyAI
O
OpenAI News
K
Kaspersky official blog
Y
Y Combinator Blog
宝玉的分享
宝玉的分享
H
Hacker News: Front Page
V2EX - 技术
V2EX - 技术
Security Latest
Security Latest

Ivan on Containers, Kubernetes, and Server-Side

A grounded take on agentic coding for production environments Server-Side Playgrounds Reimagined: Build, Boot, and Network Your Own Virtual Labs [not a] Kubernetes 101 - Pods, Deployments, and Services As an Attempt To Automate Age-Old Infra Patterns JavaScript or TypeScript? How To Benefit From the Dichotomy On Software Design... and Good Writing Building a Firecracker-Powered Course Platform To Learn Docker and Kubernetes How To Publish a Port of a Running Container What Actually Happens When You Publish a Container Port A Visual Guide to SSH Tunnels: Local and Remote Port Forwarding Debugging Containers Like a Pro Docker: How To Debug Distroless And Slim Containers In Pursuit of Better Container Images: Alpine, Distroless, Apko, Chisel, DockerSlim, oh my! How To Start Programming In Go: Advice For Fellow DevOps Engineers Kubernetes Ephemeral Containers and kubectl debug Command How To Develop Kubernetes CLIs Like a Pro Docker Container Commands Explained: Understand, Don't Memorize | iximiuz Labs Learning Docker with Docker - Toying With DinD For Fun And Profit How To Extend Kubernetes API - Kubernetes vs. Django The Influence of Plumbing on Programming How To Call Kubernetes API from Go - Types and Common Machinery How To Call Kubernetes API using Simple HTTP Client Kubernetes API Basics - Resources, Kinds, and Objects OpenFaaS - Run Containerized Functions On Your Own Terms Learning Containers From The Bottom Up Docker Containers vs. Kubernetes Pods - Taking a Deeper Look | iximiuz Labs Learn-by-Doing Platforms for Dev, DevOps, and SRE Folks How HTTP Keep-Alive can cause TCP race condition How to Work with Container Images Using ctr | iximiuz Labs Multiple Containers, Same Port, no Reverse Proxy... Exploring Go net/http Package - On How Not To Set Socket Options Disposable Local Development Environments with Vagrant, Docker, and Arkade DevOps, SRE, and Platform Engineering My Choice of Programming Languages Prometheus Is Not a TSDB How to learn PromQL with Prometheus Playground Prometheus Cheat Sheet - Basics (Metrics, Labels, Time Series, Scraping) Rust - Writing Parsers With nom Parser Combinator Framework pq - parse and query log files as time series Prometheus Cheat Sheet - Moving Average, Max, Min, etc (Aggregation Over Time) Prometheus Cheat Sheet - How to Join Multiple Metrics (Vector Matching) The Need For Slimmer Containers Understanding Rust Privacy and Visibility Model Bridge vs. Switch: Takeaways from a Real Data Center Tour | iximiuz Labs From LAN to VXLAN: Networking Basics for Non-Network Engineers | iximiuz Labs KiND - How I Wasted a Day Loading Local Docker Images Go, HTTP handlers, panic, and deadlocks Exploring Kubernetes Operator Pattern Making Sense Out Of Cloud Native Buzz Service Discovery in Kubernetes: Combining the Best of Two Worlds API Developers Never REST How Container Networking Works: Building a Bridge Network From Scratch | iximiuz Labs Traefik: canary deployments with weighted load balancing Service Proxy, Pod, Sidecar, oh my! You Need Containers To Build Images You Don't Need an Image To Run a Container Not Every Container Has an Operating System Inside Working with container images in Go Master Go While Learning Containers Implementing Container Runtime Shim: Interactive Containers How to use Flask with gevent (uWSGI and Gunicorn editions) My 10 Years of Programming Experience Implementing Container Runtime Shim: First Code Implementing Container Runtime Shim: runc Kubernetes Repository On Flame Dealing with process termination in Linux (with Rust examples) conman - [the] Container Manager: Inception Journey From Containerization To Orchestration And Beyond Linux PTY - How docker attach and docker exec Commands Work Inside Illustrated introduction to Linux iptables From Docker Container to Bootable Linux Disk Image Пишем свой веб-сервер на Python: протокол HTTP 9001 способ создать веб-сервер на Python Explaining async/await in 200 lines of code Explaining event loop in 100 lines of code Save the day with gevent Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O Truly optional scalar types in protobuf3 (with Go examples) Node.js Writable streams distilled Node.js Readable streams distilled How to on starting processes (mostly in Linux) Дайджест интересных ссылок – Июль 2016 Пишем свой веб-сервер на Python: сокеты Наследование в JavaScript Мастерить!
How To Extract Container Image Filesystem Using Docker | iximiuz Labs
Ivan Velichko · 2022-09-19 · via Ivan on Containers, Kubernetes, and Server-Side

Even though technically container images are represented as layers of cumulative filesystem changes, from a mere developer's standpoint, they are just simple holders of future container files. And developers often want to explore the contents of container images accordingly - with familiar tools like cat, ls, or find. In this tutorial, we'll see how to extract the filesystem of a container image using nothing but the standard Docker means.

Container image to filesystem.

The not so helpful docker save command

The docker help output has just a few entries that look relevant for our task. The first one in the list is the docker save command:

Usage:  docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Trying it out quickly shows that it's not something we need:

The docker save command, also known as docker image save, dumps the content of the image in its storage (i.e. layered) representation while we're interested in seeing the final filesystem the image would produce when the container is about to start.

The almost working docker export command

The second command that looks relevant is docker export. Let's try our luck with it:

Usage:  docker export [OPTIONS] CONTAINER

Export a container's filesystem as a tar archive

Seems like a good candidate. However, an attempt to export the filesystem of the nginx:alpine image fails:

docker export ghcr.io/iximiuz/labs/nginx:alpine -o nginx.tar.gz
Error response from daemon: No such container: ghcr.io/iximiuz/labs/nginx:alpine

The problem with the docker export command is that it works with containers and not with their images. An obvious workaround would be to start an nginx:alpine container and repeat the export attempt:

CONT_ID=$(docker run -d ghcr.io/iximiuz/labs/nginx:alpine)

docker export ${CONT_ID} -o nginx.tar.gz

What's inside?

mkdir rootfs

tar -xf nginx.tar.gz -C rootfs

ls -l rootfs
total 68
lrwxrwxrwx  1 root root    7 Mar 11 00:00 bin -> usr/bin
drwxr-xr-x  2 root root 4096 Jan 28 21:20 boot
drwxr-xr-x  4 root root 4096 Apr  9 09:43 dev
drwxr-xr-x  2 root root 4096 Mar 12 01:55 docker-entrypoint.d
...
drwxrwxrwt  2 root root 4096 Mar 11 00:00 tmp
drwxr-xr-x 12 root root 4096 Mar 11 00:00 usr
drwxr-xr-x 11 root root 4096 Mar 11 00:00 var

💡 Pro Tip: By default, extracting files from a tar archive sets the file ownership to the current user and may silently override file permissions and unset file capabilities. If the original file ownership, permissions, and capabilities need to be preserved, you can use the --numeric-owner, --same-permissions, and --xattrs flags while extracting the archive. Beware that you'll have to be sufficiently privileged for that. Example:

sudo tar -xf nginx.tar.gz \
    --numeric-owner \
    --same-permissions \
    --xattrs --xattrs-include='*' \
    -C rootfs`

Well, the output does look like what we need - just a regular folder with a bunch of files inside that we can explore as any other filesystem. However, running a container just to see its image contents has significant downsides:

  • The technique might be unnecessarily slow (e.g., heavy container startup logic).
  • Running arbitrary containers is potentially insecure.
  • Some files can be modified upon startup, spoiling the export results.
  • Sometimes, running a container is simply impossible (e.g., a broken image).

The working docker create + docker export combo

Containers are stateful creatures - they are as much about files as about processes. In particular, it means that when a containerized process dies, its execution environment, including the filesystem, is preserved on disk (unless you ran the container with the --rm flag, of course). Thus, using docker export for a stopped container should be possible, too. However, this approach suffers from pretty much the same set of drawbacks as exporting a filesystem of a running container - to get a stopped container, you need to run it first...

But wait a second! There is another type of not-running containers - the ones that were created but haven't been started yet.

The well-known docker run command is actually a shortcut for two less frequently used commands - docker create <IMAGE> and docker start <CONTAINER>. And since containers aren't (only) processes, the docker create command, in particular, prepares the root filesystem for the future container.

So, here is the trick:

CONT_ID=$(docker create ghcr.io/iximiuz/labs/nginx:alpine)

docker export ${CONT_ID} -o nginx.tar.gz

And a handy oneliner (assuming the target folder has already been created):

docker export $(docker create ghcr.io/iximiuz/labs/nginx:alpine) | tar -xC <dest>

Don't forget to docker rm the temporary container after the export is done 😉

The more accurate docker build -o alternative

Most of the time, the docker create + docker export combo produces satisfactory results. However, you may still notice some tiny artifacts in the resulting filesystem. For instance, the exported filesystem may have the /etc/hosts file even when the original image would not have one. This is because the docker create command actually performs some additional modifications on top of the extracted original filesystem.

But what if we want to get the original filesystem, without any modifications?

Turns out that starting with Docker 18.09 (released ~early 2019), it's possible to specify a custom output location for the docker build command using the --output|-o flag. So, here is the trick:

echo 'FROM ghcr.io/iximiuz/labs/nginx:alpine' > Dockerfile

# DOCKER_BUILDKIT=1 if you're running Docker < 23.0
docker build -o rootfs .

ls -l rootfs
total 84
drwxr-xr-x  2 vagrant vagrant 4096 Aug 22 00:00 bin
drwxr-xr-x  2 vagrant vagrant 4096 Jun 30 21:35 boot
drwxr-xr-x  4 vagrant vagrant 4096 Sep 12 14:07 dev
drwxr-xr-x  2 vagrant vagrant 4096 Aug 23 03:59 docker-entrypoint.d
...
drwxr-xr-x  2 vagrant vagrant 4096 Aug 23 03:59 tmp
drwxr-xr-x 11 vagrant vagrant 4096 Aug 22 00:00 usr
drwxr-xr-x 11 vagrant vagrant 4096 Aug 22 00:00 var

Generally speaking, building container images involves running intermediate containers, but if the Dockerfile has no RUN instructions, as the one above, no containers will be spun up. So, the docker build command will just copy the FROM image contents into the anonymous image, and then save the result to the specified output location.

The --output flag works only if BuildKit is used as a builder engine, so if you're still on Docker < 23.0 (released ~early 2023), you'll either need to use docker buildx build or set the DOCKER_BUILDKIT=1 environment variable.

⚠️ Caveat: It might be impossible to preserve the file ownership information using the docker build -o approach.

The bonus ctr image mount method

As you probably know, Docker delegates more and more some of its container management tasks to another lower-level daemon called containerd. It means that if you have a dockerd daemon running on a machine, most likely there is a containerd daemon somewhere nearby as well. And containerd often comes with its own command-line client, ctr, that can be used, in particular, to inspect images.

The cool part about containerd is that it provides a much more fine-grained control over the typical container management tasks than Docker does. For instance, you can use ctr to mount a container image to a local folder, without even mentioning any containers.

First, pull the image to the local containerd daemon:

sudo ctr image pull ghcr.io/iximiuz/labs/nginx:alpine

Next, mount the image to a temporary directory:

sudo ctr image mount ghcr.io/iximiuz/labs/nginx:alpine
/run/containerd/io.containerd.mount-manager.v1.bolt/t/1/1

Listing files in the above directory will show the extracted filesystem of the nginx:alpine image:

sudo ls -l /run/containerd/io.containerd.mount-manager.v1.bolt/t/1/1

The mounted directory will contain the extracted filesystem of the nginx:alpine image, without any docker create-like artifacts and without potentially confusing docker build tricks.

The downside of this approach is/was that you may need to pull the image explicitly before mounting it, even if it was already pulled by Docker. Historically, dockerd and containerd used different image storage backends, and it was not possible to use ctr to access images owned by dockerd. However, since Docker 29 (released ~Q4 2025), Docker by default delegates image storage to containerd, so if you use ctr with the --namespace moby flag, you should be able to access images owned by dockerd. I.e., no extra pull required.

Practice time 🧪

Summarizing

In this article, we've learned how to extract the filesystem of a container image using standard Docker commands. As usual, there are multiple ways to achieve the same goal, and it's important to understand the trade-offs of each one. Here is a quick summary of the methods we've covered:

  • docker save is unlikely the command you're looking for.
  • docker export works but requires a container in addition to the image.
  • docker create + docker export is a way to export the filesystem w/o starting the container.
  • docker build -o is a potentially surprising but a more accurate way to export the filesystem.
  • ctr image mount is a clever alternative method that also produces artifact-free results.

Keep playing with containers, folks. It's fun!