Deconstruct Docker Images to Build Custom Minimal Ones
Export Docker images as tar, parse manifest.json to decompress ordered layers revealing filesystem diffs from Dockerfile instructions, modify contents like os-release, then rebuild tiny custom images using FROM scratch—no base image needed.
Unpack Image Anatomy via Export and Manifest
Docker images are tar archives containing metadata (manifest.json, index.json, oci-layout) and blobs/sha256/ directory with hashed layer tarballs plus configs. Use docker save <image> -o image.tar to export (e.g., redis.tar is 53M). Extract with tar -xvf image.tar to reveal structure: 3 directories (blobs, etc.), 17 files for redis:latest.
manifest.json lists Config (image build config) and Layers array of sha256 blobs in build order—first is base (e.g., Debian for redis), subsequent are diffs from each Dockerfile instruction like RUN or COPY. For redis:latest (docker.iranserver.com/redis:latest), 7 layers: ec781dee... (base), 312488b... (etc groupadd/useradd), up to 20994e17... (final mods). Non-layer blobs like f67c1d84... are runtime configs with Env (PATH=/usr/local/sbin:...), Entrypoint (docker-entrypoint.sh), Cmd (redis-server), ExposedPorts (6379/tcp), WorkingDir (/data).
index.json handles multi-platform (schemaVersion 2, one amd64 manifest). oci-layout declares {"imageLayoutVersion": "1.0.0"} for OCI compliance. Decompress layers script: loop jq ..Layers, mv blob, tar -xf to LAYER_0 (full base: bin->usr/bin, dev, etc, usr, var), LAYER_1 (etc mods), up to LAYER_6 (usr tweaks)—union forms final rootfs.
Inspect Layers to Reveal Dockerfile History
Layer tree for redis shows diffs: LAYER_0 (87.4MB Debian base via debuerreotype 0.17), LAYER_1 (41kB useradd redis), LAYER_2 (41kB tzdata), LAYER_3 (61.4MB Redis 8.6.1 build from github.com/redis/redis/archive/refs/tags/8.6.1.tar.gz SHA 88ff5661160bf4b12aba2dfc579b131c202e75a3ac1f0b1d06db05a9929d5a89 with gcc/make/jemalloc), LAYER_4 (8.19kB mkdir /data), LAYER_5 (4.1kB WORKDIR), LAYER_6 (24.6kB COPY entrypoint). Matches docker history: empty_layer for ARG/CMD/EXPOSE/ENTRYPOINT (0B size), non-data RUNs add minimal (e.g., WORKDIR 4.1kB).
Config rootfs.diff_ids confirm layer SHAs. Baking secrets like .env into layers exposes them permanently—pass at runtime instead. Unnecessary COPY/ADD bloats diffs; multi-stage drops build deps.
Build Minimal Custom Images from Modified Layers
For alpine:latest (one main layer), run decompress.sh (GitHub: 314arhaam/alpyne) to get LAYER_0 (bin/dev/etc full Alpine 3.23.3 rootfs) and metadata. Edit /etc/os-release: NAME="ALPYNE Linux", ID=alpyne, VERSION_ID=0.0.1, PRETTY_NAME="ALPYNE Linux v0".
In LAYER_0, add Dockerfile:
FROM scratch
COPY . .
CMD ["bin/sh", "-l"]
scratch skips base pull—mounts your user-space (rootfs, shell, bins) atop host kernel. Build docker build -t alpyne:latest ., run docker run -it --rm alpyne:latest—verify custom os-release. Use build.sh for automation. Trim further: remove unneeded /bin /sbin for tinier images. Demystifies Docker: containers share host kernel, images just layered user-space filesystems—no VM magic.