Bruno Schaatsbergen website Mastodon PGP Key email A drawing of an astronaut in space The Netherlands

Cek (Container Exploration Kit)

in
writing
date
5/16/2026

cek is a small CLI I built to read and explore OCI container images without running them, and without needing a container daemon. It talks directly to any registry that implements the OCI Distribution Spec, pulls the manifest and layer blobs that make up an image, and walks them in memory.

$ cek cat nginx:latest /etc/nginx/nginx.conf

Under the tooling, an OCI image is a JSON manifest pointing at a stack of gzipped tar layers. What a running container sees is those layers merged top-down. cek reads from that same stack, just without the container.

The subcommands

The subcommands are ls, cat, tree, tags, inspect, export. I built on top of those programs’ interfaces to keep cek as intuitive as possible to use.

List the contents of a directory inside an image:

$ cek ls nginx:latest /etc
/etc/adduser.conf
/etc/alternatives
/etc/apt
/etc/bash.bashrc
/etc/ca-certificates
/etc/ca-certificates.conf
/etc/debian_version
/etc/default
/etc/nginx
/etc/os-release
...

Read a single file out of an image:

$ cek cat alpine:latest /usr/lib/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.21.3
PRETTY_NAME="Alpine Linux v3.21"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

Show the top-level directories of an image:

$ cek tree nginx:latest -L 1
/
├── bin -> usr/bin
├── boot
├── dev
├── docker-entrypoint.d
├── docker-entrypoint.sh
├── etc
├── home
├── lib -> usr/lib
├── proc
├── root
├── run
├── sbin -> usr/sbin
├── srv
├── sys
├── tmp
├── usr
└── var

It’s built to pipe. Diffing a config file between two image versions, for example, is a regular diff over two cek cat invocations:

$ diff <(cek cat nginx:1.28 /etc/nginx/nginx.conf) \
       <(cek cat nginx:1.26 /etc/nginx/nginx.conf)

Layers

An image is a stack of tarballs. What a running container sees is the result of those layers merged top-down, with upper layers shadowing lower ones. I default cek to that merged view because it’s what users see inside a running container, and almost always what they actually mean to read.

When you do want to dig into a specific layer, most commands takes a --layer flag. Layer indices come from cek inspect:

$ cek inspect nginx
Image: nginx
Digest: sha256:ec0ee8695f2f71addca9b40f27df0fdfbde460485a2b68b834e18ea856542f1e
<!-- OS/Arch: linux/arm64 -->
Size: 55.6 MB

Layers:
#  Digest                                                                   Size
1  sha256:f626fba1463b32b…                                                28.7 MB
2  sha256:89d0a1112522e6e…                                                26.8 MB
3  sha256:1b7c70849006971…                                                  627 B
...
$ cek ls --layer 4 nginx:latest
$ cek cat --layer 2 nginx:latest /etc/nginx/nginx.conf

By default, cek walks the layer stack top-down and returns the first non-whiteout match for a given path. That’s the same resolution an overlayfs performs at runtime, so the result is exactly what a running container would see. With --layer, cek skips that walk and reads directly from the tarball entries of the named layer, ignoring any shadowing and whiteouts contributed by the layers above it.

The daemon, when it’s around

When a daemon is around, cek uses it as a cache over the Unix socket addressed by DOCKER_HOST. The path differs per runtime (Docker, Podman, containerd, and Colima each have their own) and falls back to the default Docker socket if DOCKER_HOST is unset. Anything already pulled by docker, podman, or nerdctl is immediately visible.

The --pull flag controls cache behaviour. if-not-present (the default) checks the daemon first and falls back to the registry, always skips the cache and re-fetches every time, and never keeps you offline.

How it differs from dive

dive is a TUI. It requires Docker to be running, and it isn’t built for piping or plugging into shell workflows. It does give you layer-efficiency analysis, which I deliberately left out of cek, and it’s still what I reach for when I want to explore an image visually.

Install

$ brew install cek

or

$ go install github.com/bschaatsbergen/cek@latest

Source is at github.com/bschaatsbergen/cek.

/cek