# Whaler turns a Docker image back into the Dockerfile that built it

> Whaler is a Go program that reverse engineers a Docker image into the Dockerfile that created it, extracts the files added by ADD and COPY, searches the added filenames for anything that looks like a secret, and prints the ports, the user and the environment variables. It ships as a single static binary and a container image, and it needs access to the Docker socket to read the image.

**P3GLEG/Whaler** — Program to reverse Docker images into Dockerfiles

- Repository: https://github.com/P3GLEG/Whaler
- Stars: 1,195 · Forks: 105
- Language: Go
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/p3gleg-whaler

## What a Dockerfile leaves behind in an image

Whaler is a Go program designed to reverse engineer Docker images into the Dockerfile that created it, and the reason it is useful is that a Dockerfile is a lossy description of an image.

An image is a stack of layers. The build instructions that produced those layers are not stored in it, so what you have is the result and not the recipe. If you were handed an image and asked to reproduce it, you would have to infer the base, the sequence of commands and the files that were added, and the files are the hard part, because an ADD instruction leaves no trace of which instruction added what.

The list of what Whaler does is short. It generates a Dockerfile from an image. It searches added filenames for potential secret files. It extracts files that were added by the Docker ADD and COPY instructions. And it displays miscellaneous information such as ports open, the user it runs as and environment variables.

That last item is the one people forget to include in an image review. The exposed ports, the user the process runs as and the environment variables in the image configuration are the three things that decide how a container behaves when it starts, and they are all in the image metadata rather than in the layers, so they are easy to read without any of this.

The project is by pegleg, the licence is GPL-3.0, the language is Go, and the single tagged release is 1.0, the initial release, from 2018-06-27.

## Running it as a container with the socket mounted read only

The easiest way to run the tool is in a container, and the command is a good model of how to do this safely.

```bash
docker pull pegleg/whaler
docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock:ro pegleg/whaler -sV=1.36 nginx:latest
```

The socket mount is the part to look at. The tool needs to talk to the Docker daemon to pull and read the image, and mounting the socket gives it that access, so the read only suffix on the volume is what stops a mistake in the tool from becoming a write to the daemon. The container is also run with --rm so it leaves nothing behind.

The -sV parameter sets the Docker client ID to a specific version, and the readme says it is not always required, which is a hint that the tool was written against a particular client library version and the flag exists as an escape hatch.

The tool pulls the target image automatically, so there is no separate pull step. The second form of the command builds the image locally and then defines an alias for it.

```bash
docker build --rm -t pegleg/whaler .
alias whaler="docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock:ro pegleg/whaler"
whaler -sV=1.36 nginx:latest
```

The alias is a convenience that keeps the socket mount and the read only flag out of the command you type, which is the right way round.

## Building from source, and what the flags do

The build instructions are the old style, and they say so: git clone the project into your $GOPATH/src directory and perform the following command.

```bash
go get -u github.com/P3GLEG/Whaler
cd $GOPATH/src/github.com/P3GLEG/Whaler
go build .
```

That is the GOPATH era of Go, and it is worth noting against the module file, which declares the module as whaler, requires Go 1.23.0 and names a toolchain of go1.24.1. The instructions predate the module system and the go get in them will behave differently on a current Go installation, so the reliable route today is a clone and a go build inside the directory.

The usage output is the other half of the documentation, and it is short.

```go
-f string
   File containing images to analyze seperated by line
-filter
   Filters filenames that create noise such as node_modules. Check ignore.go file for more details (default true)
-sV string
   Set the docker client ID to a specific version -sV=1.36
-v  Print all details about the image
-x  Save layers to current directory
```

The -f flag lets you analyse a list of images in one run, which is what you want in a pipeline over a registry. The -v flag turns on the full detail output. The -x flag saves the layers to the current directory, which is how you get the extracted files for inspection rather than just a report.

## The filter exists because a real image has thousands of files

The -filter flag is on by default, and its description is the interesting part. It filters filenames that create noise such as node_modules, and the readme points at the ignore.go file for more details.

That is a small design decision with a large effect on usability. A modern application image contains a dependency tree, and a Node image contains a node_modules directory with tens of thousands of files. If Whaler listed every added file, the output would be unreadable and the interesting files would be buried. The ignore list is how the tool stays useful on real images rather than only on small ones.

The list lives in its own file, ignore.go, at the top level of the repository, which means it is a normal Go source file with a list in it rather than a configuration file. That has a consequence, which is that changing the ignore list is a code change rather than a setting, and the tool has to be rebuilt.

It also means the filter is a heuristic, and heuristics have a cost. If a file the reviewer needed was filtered out because its path matched a rule, the tool will not show it. For a security tool whose output is a list of things to look at, the safe assumption is that the report is a starting point rather than a complete inventory, and the -x flag exists so you can go deeper.

The secret filename search is subject to the same caveat. It searches added filenames for potential secret files, which means it is looking for names that look like secrets, and a file called config.yaml holding a key is not going to be flagged by its name.

## The container image is a two stage static build

The Dockerfile in the repository is a two stage build, and it is a good example of the modern way to ship a Go tool.

```dockerfile
FROM golang:1.24.1 AS builder
ADD ./ /root/whaler_build
WORKDIR /root/whaler_build
RUN export CGO_ENABLED=0 && go build .
RUN cp whaler /root/whaler

FROM alpine:3.21.3
WORKDIR /root/
COPY --from=builder /root/whaler .
ENTRYPOINT ["./whaler"]
```

CGO_ENABLED=0 is the line that matters. A Go binary built without cgo is statically linked, which means the runtime stage does not need a C library at all, so the final image is a few megabytes of Alpine rather than a Go toolchain image of more than a gigabyte.

The result is an image that is small, that has no shell tooling to speak of, and whose entrypoint is the tool itself, so the run command in the readme needs no arguments beyond the image to analyse.

The builder stage pins the Go version to 1.24.1, which matches the toolchain directive in the module file, and the runtime stage is Alpine 3.21.3. The whole build is six lines, and there is no dependency installation step, because a Go module with a small dependency set builds from its own cache.

## The repository and the dependency stack

The top level listing is small, which fits a program of this size.

There is main.go, which is the command line handling, scanner.go, which does the layer scanning, and ignore.go, which holds the filename filter. There is main_test.go and a testdata/ directory, plus a test-image/ directory, which is how you test a tool that reads images without needing a registry. The compiled binary is also checked in as whaler, which is unusual and probably a leftover.

There is a Dockerfile and a .gitignore, a LICENSE file, the README, and the module files.

The dependency list in go.mod is where the design shows. The Docker client is github.com/docker/docker at 28.0.1, which is the library that talks to the daemon. buger/jsonparser is the JSON parser, and it is there because the image configuration and the layer metadata are JSON, so the tool reads them directly rather than pulling in a full JSON library. fatih/color and moby/term are for the terminal output, so the report is coloured and the terminal handling is correct on Windows as well.

The indirect dependency list is long, and that is the Docker client pulling in the OpenTelemetry stack, the containerd logging packages and the OpenContainers image specification libraries, which is the cost of depending on the full Docker engine module rather than a thin API client.

The last push was on 2026-04-08, and the only tag is the 1.0 initial release from 2018. So the code is being worked on and the release discipline is quiet, which is worth knowing if you plan to depend on it rather than use it once.

## Conclusion

Adopt Whaler when you are auditing images you did not build, for example in a supply chain review or an incident response, and you need to see what a base image actually added. Do not adopt it as a supply chain scanner, because it reports what an image contains rather than what is wrong with it, and it only shows you the layers you can read. Verify first that the version of the Docker client you point it at matches the daemon, that the socket mount is read only, and that you review the extracted files yourself, since a filename that looks like a secret is a hint rather than a finding.

## FAQ

### What does Whaler do?

It is a Go program that reverse engineers Docker images into the Dockerfile that created them. It generates a Dockerfile from an image, searches added filenames for potential secret files, extracts the files added by ADD and COPY instructions, and displays information such as open ports, the user it runs as and environment variables.

### How do I run Whaler against an image?

Pull the image and run it with the Docker socket mounted read only, for example docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock:ro pegleg/whaler -sV=1.36 nginx:latest. The tool pulls the target image automatically and the -sV parameter is not always required.

### What are the Whaler command line flags?

-f takes a file of images to analyse one per line, -filter removes noisy filenames such as node_modules and is on by default, -sV sets the Docker client version, -v prints all details about the image and -x saves the layers to the current directory.

### Why does Whaler filter some filenames?

Because a real application image contains a dependency tree, and listing every added file would bury the interesting ones. The filter is defined in the ignore.go file at the top of the repository, and because it lives in code rather than in a config file, changing it means rebuilding the tool.

### How is the Whaler container image built?

In two stages. A golang:1.24.1 builder stage compiles the binary with CGO_ENABLED=0 so it is statically linked, and the runtime stage is alpine:3.21.3 with the binary copied in and set as the entrypoint.

### Is Whaler still maintained?

The repository is not archived and the last push was on 2026-04-08, but the only tagged release is 1.0, the initial release from 2018-06-27, and the module targets Go 1.23 with a 1.24.1 toolchain.

## Sources

- [Issues](https://github.com/P3GLEG/Whaler/issues)
- [License: GPL-3.0](https://github.com/P3GLEG/Whaler/blob/master/LICENSE)
- [P3GLEG/Whaler on GitHub](https://github.com/P3GLEG/Whaler)
- [README](https://github.com/P3GLEG/Whaler/blob/master/README.md)
- [Releases](https://github.com/P3GLEG/Whaler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/p3gleg-whaler
