# Run OSINT tools in Docker without writing a Dockerfile

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

Published 25 Oct 2026 by OTInstaller.

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](https://otinstaller.com/osintverse-community/holehe) package is built from:

```dockerfile
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](https://otinstaller.com/docs/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](https://otinstaller.com/osintverse-community/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](https://otinstaller.com/osintverse-community/sherlock) and
[maigret](https://otinstaller.com/osintverse-community/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](https://otinstaller.com/blog/how-to-install-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](https://otinstaller.com/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](https://otinstaller.com/): 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](https://otinstaller.com/docs/security) lists exactly what `ot` refuses and why, and
[Getting started](https://otinstaller.com/docs/getting-started) installs `ot` in a minute.
