Blog

Run OSINT tools in Docker without writing a Dockerfile

· OTInstaller

ot runs every registry tool in a read-only container with no capabilities, no host files and no network unless the tool declares it.

To run an OSINT tool in Docker without writing a Dockerfile, install ot and run the tool by name, for example ot run osintverse-community/holehe e:ada@example.com. The first run pulls the tool’s image by its digest and checks its signature. Every run then starts a fresh container that is read-only, has every Linux capability dropped, runs as an unprivileged user, sees none of your files, and has no network unless the tool’s manifest says it needs one. Python tools get the same container: ot builds their pinned package into a small image of its own. You need Docker Desktop or Podman running, and nothing else.

What you would otherwise write

Running a Python tool in Docker by hand starts with a Dockerfile like this one, which is what the registry’s holehe package is built from:

FROM python:3.12-slim
RUN useradd --uid 10001 --create-home app
RUN pip install --no-cache-dir holehe==1.61
USER app
ENTRYPOINT ["holehe"]

Then a docker build, a tag to remember, and a docker run line with the flags that keep the container in its place, which are long enough that most people leave them out. That is one tool. The next one has its own Dockerfile, and both need rebuilding when they release.

On the registry that work is done once, in the package’s own CI, and the image is signed there. Each tool’s manifest records the image by digest, the command, the inputs it takes and whether it needs the network. ot reads the manifest, so the one command above is the whole job.

What ot run starts

For every Docker tool, ot run starts the container with these flags (they come from ContainerArgs in the ot source, and the home page shows the same):

docker run --rm --name ot-<random> \
  --read-only --tmpfs /tmp:rw,size=256m \
  --cap-drop ALL --security-opt no-new-privileges \
  --pids-limit 256 --memory 1g --cpus 2 \
  --user 65534:65534 -e HOME=/tmp \
  --network none \
  -e <KEY_NAME> \
  --entrypoint <command> <image>@sha256:<digest> <args>

What each part does:

  • --rm and a random name. The container is removed when the run ends. If you press Ctrl-C, ot stops the container too, so nothing is left running.
  • --read-only with a 256 MB /tmp. The tool can write scratch files in /tmp and nowhere else. HOME points there, so tools that keep a cache still start.
  • --cap-drop ALL and no-new-privileges. The process has no Linux capabilities and cannot gain any, even through a setuid program in the image.
  • --pids-limit 256 --memory 1g --cpus 2. A tool that misbehaves cannot take over your machine.
  • --user 65534:65534. The unprivileged nobody user, whatever the image’s own USER line says.
  • No -v. None of your folders is mounted, so the tool cannot read your home directory, your other cases or your SSH keys. When a tool writes files you want to keep, --out-dir DIR mounts that one folder at /out.
  • --entrypoint and the arguments. The manifest’s command is a list, never a shell string, and each value you give fills its own place in that list. Nothing you type passes through a shell.

Network only when the tool declares it

--network none is left out only when the manifest says network: {egress: true}. A manifest without a network block runs offline. The smoke test the registry runs on every new release follows the same rule, so a tool behaves on your machine the way it did in its scan.

Most OSINT tools have to reach a website or an API, and their manifests say so: holehe, sherlock and checkdmarc all declare egress. Others don’t need it. email-header-parser reads the raw headers you paste in and runs with no network at all, so the headers of a phishing message never leave your machine:

ot run osintverse-community/email-header-parser --headers "$(cat headers.txt)" -f table

Every tool page says “Goes online” or “Runs offline”, next to whether the tool contacts the target, logs in to accounts or returns personal data.

Pinned, and signed by the package’s own build

A Docker tool’s manifest names its image by digest, as in ghcr.io/osintverse-community/holehe@sha256:<64 hex digits>, never by a tag that can be moved to other code. ot refuses an image that is not pinned (exit code 4). When it installs a tool, it pulls that digest and runs cosign verify against the signing identity the registry recorded at publish: for a package released from a repository, that repository’s own release workflow. If they don’t match, the install stops with signature verification failed for <image>; refusing to install. Every run afterwards starts the image by the same digest, so what runs is what was checked.

If cosign isn’t installed, ot warns that the signature was not verified locally and carries on; ot doctor tells you it is missing. When an AI agent asks through ot mcp, the rule is stricter: an unsigned image, or a missing cosign, is refused.

Before a release is published it is scanned: Trivy for vulnerabilities and leaked secrets, an SBOM, GuardDog for Python packages, and the smoke test. Each tool’s Security tab shows the results for every version. If GitHub’s container registry later refuses an image, ot can pull the same digest from the registry’s mirror at mirror.otinstaller.com and check the same signature. A network error never triggers that fallback, only a refusal.

Python tools get the same container

Many OSINT tools are Python packages on PyPI, such as sherlock and maigret. Their manifests pin one exact wheel by its SHA-256. ot downloads it, checks the hash, builds it into an image of its own on python:3.12-slim (tagged ot-local/<namespace>-<name>:<version>), and runs that image with exactly the flags above. Your own Python is never touched, and two tools with conflicting requirements never share an environment. The post on installing Sherlock walks through one.

Without a container engine, or with --native, ot installs a Python tool with pipx on your machine instead and runs it outside the sandbox. On Linux and macOS without Docker, otlite is the edition built for that.

Docker Desktop, Podman, OrbStack or Colima

ot uses docker when it finds one and podman otherwise, with the same flags. OrbStack and Colima provide the docker command, so they work too. ot doctor checks that the engine is running, and looks for cosign, Python and your OS keychain:

ot doctor

When Docker is installed but not running, ot says so, asks you to start it, and exits with code 5.

Keys stay out of the command line

When a tool declares an API key, ot reads it from your OS keychain and passes -e NAME without a value, so Docker copies it from ot’s own environment. The key never appears in the process list, in the run’s saved .meta.json or in a log, and the container receives only HOME and the keys that tool declares. ot keys set NAME stores one.

In short

This is the long form of the “Trust it” row on the home page: every tool pinned to one digest or wheel hash, scanned when it is published, and run in a container that can’t read your files. The security model lists exactly what ot refuses and why, and Getting started installs ot in a minute.

More posts · Changelog · This post as Markdown

Esc

Try

↑ ↓ to move↵ to open