Docker¶
The Linux ppee-cli needs glibc 2.38 or later, so the image is based on debian:trixie-slim (Debian 13):
- ~82 MB image: the slim base plus
ppee-cli, nothing else installed - respects container CPU limits (
--cpus, cgroup quotas, cpusets) - runs as a pure function: file in, report out
- also works as an MCP server
Build the image¶
Put the files from the Linux release archive (mzrst.com) in an empty folder, next to this Dockerfile:
FROM debian:trixie-slim
COPY ppee-cli Suspicious.txt THIRD-PARTY-NOTICES.txt /ppee/
ENTRYPOINT ["/ppee/ppee-cli", "--no-update-check"]
Suspicious.txtis the keyword list for suspicious strings; keep it next to the binary.THIRD-PARTY-NOTICES.txtholds the licence notices for the bundled libraries; keep it in redistributed images.- Anything after the image name is passed to
ppee-cli.
Use a base with glibc 2.38+
debian:bookworm-slim (glibc 2.36) fails with GLIBC_2.38' not found. debian:trixie-slim and ubuntu:24.04 work. Alpine (musl) does not.
Running as non-root
docker run --user 65534 works for analysis. The similarity database then can't be written in /ppee, so add --no-similarity.
x86-64 hosts
The release binary is x86-64, so the image runs on x86-64 Docker hosts. It analyzes x86, x64 and ARM64 PE files alike.
Analyze a file¶
Mount the folder that holds your samples, read-only, and pass the path as the container sees it:
Every CLI option works the same way, including --analysis for Go, Rust, .NET and NativeAOT files, and exit codes pass through docker run.
Shell alias
Read-only and hardened runs¶
docker run --rm --read-only --network none --cap-drop ALL --security-opt no-new-privileges \
-v "$PWD:/data:ro" ppee-cli --json --all --no-similarity /data/sample.exe
--network none: PPEE needs no network (the image already passes--no-update-check).--read-only: fine, as long as you add--no-similarity.
Similarity on a read-only root
Without --no-similarity, a read-only container can't create the database. --similarity and --all then print warning: similarity database '/ppee/ppee-cli.similarity.db' could not be opened … on stderr and report "available": false. The exit code is still 0. Pass --no-similarity to silence it, or persist the DB as shown below.
CPU limits¶
ppee-cli sizes its worker pool from the CPUs the container may actually use, from the cgroup CPU quota and affinity mask, not from the host's core count. docker run --cpus=2 therefore gets two workers. You can override it:
docker run --rm --cpus=2 -v "$PWD:/data:ro" ppee-cli --hashes /data/big.dll
docker run --rm -e PPEE_JOBS=1 -v "$PWD:/data:ro" ppee-cli --hashes /data/big.dll
Use PPEE_JOBS=1 when you already run many containers in parallel.
Persisting the similarity database¶
The database lives next to the binary at /ppee/ppee-cli.similarity.db. Bind-mount a file there to keep it between runs:
touch ppee-cli.similarity.db # must exist first, or Docker creates a directory
docker run --rm -v "$PWD:/data:ro" \
-v "$PWD/ppee-cli.similarity.db:/ppee/ppee-cli.similarity.db" \
ppee-cli --similarity /data/a.exe
$ docker run … ppee-cli --similarity /data/b.exe
Similarity DB: 2 record(s)
Match against 1 peer(s):
SHA256 identical - /data/a.exe
Note
Don't mount a volume over the whole /ppee directory. That hides the ppee-cli binary itself.
Batch scan a folder¶
The image's entry point is ppee-cli, so loop on the host:
find samples -type f \( -iname '*.exe' -o -iname '*.dll' \) -print0 |
xargs -0 -P 4 -I{} docker run --rm -e PPEE_JOBS=1 -v "$PWD:/data:ro" ppee-cli \
--json --hashes --imports --security --no-similarity "/data/{}" > report.jsonl
jq -r '[.path, .fileInfo.sha256, (.security.present|tostring)] | @tsv' report.jsonl
Docker Compose¶
services:
ppee:
build: .
image: ppee-cli
read_only: true
network_mode: none
cap_drop: [ALL]
volumes:
- ./samples:/data:ro
command: ["--json", "--all", "--no-similarity", "/data/sample.exe"]
Kubernetes Job¶
apiVersion: batch/v1
kind: Job
metadata:
name: ppee-scan
spec:
template:
spec:
restartPolicy: Never
containers:
- name: ppee
image: registry.example.com/ppee-cli:2.0.0
args: ["--json", "--hashes", "--security", "--no-similarity", "/data/app.exe"]
resources:
limits: { cpu: "2", memory: "512Mi" }
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65534
volumeMounts:
- { name: samples, mountPath: /data, readOnly: true }
volumes:
- name: samples
persistentVolumeClaim: { claimName: samples }
Publishing the image¶
See CI/CD → GitHub Actions to automate this.
Next: CI/CD integration · MCP in Docker
References¶
- docker container run reference (Docker): every flag used to run the image, including read-only and network options.
- Docker Engine security: isolation, capabilities and why a read-only, offline container suits untrusted samples.