Skip to content

Run with Docker

Don't want Python, Node, gh, and CodeGraph on your machine? WhyGraph ships as a self-contained image. Your host needs only Docker. One command installs the whygraph shim from inside the image; whygraph up then starts the portal in it.

curl -fsSL https://raw.githubusercontent.com/mtrdesign/whygraph/v2.5.0/scripts/install.sh | sh

whygraph up          # start the portal and its database (two background containers, loopback only)

Then open http://localhost:8765, share the folder that holds your repos, and add them from the Projects page. See the Quickstart.

The tag in the URL picks the version - v2.5.0 installs 2.5.0. To install a different release with that same installer, pass it through the pipe: … | sh -s latest. Full override list and the no-curl alternative are in Installation.

How the shim works

The fetched script is a thin bootstrapper: it checks Docker is present and running, pulls the pinned image, then asks the image to generate the shims - docker run --rm IMAGE whygraph install prints them to stdout and the script executes that. Shim bodies therefore live in WhyGraph's own tested code, not in the shell script, and every failure (no Docker, dead daemon, unknown version) exits non-zero with a message instead of quietly installing nothing.

The result is a whygraph shim on your PATH, plus a whygraph-mcp stub that only prints a message that the stdio server was removed. The shim does two different things:

  • The portal verbs - up, down, status, logs, folders and backup - manage two named, long-lived containers, whygraph-portal and its database, whygraph-portal-postgres. They are the one exception to "ephemeral per command".
  • Every other command (version and install) runs the image against the current directory and exits:
exec docker run --rm -i $tty \
    --user "$(id -u):$(id -g)" -e HOME=/tmp \
    -v "$PWD:/workspace" -w /workspace \
    "$IMAGE" whygraph "$@"
  • Everything's in the image - Python and WhyGraph, git, the GitHub CLI, and Node with the CodeGraph CLI. CodeGraph indexes from the in-image binary, and the portal runs CodeGraph's MCP server from the same binary, so there's no docker-in-docker.
  • Files come back as yours. --user "$(id -u):$(id -g)" is what does it: a file the command writes isn't root-owned and git sees matching ownership.
  • Only the repo you stand in is visible to an ephemeral command. The portal sees only the folders you share.

The portal container

whygraph up starts whygraph-portal detached, with --restart unless-stopped, published to the loopback interface only and attached to its own Docker network. Your shared folders are mounted at the same path they have on the host, and the data directory (~/.local/share/whygraph) is mounted at /data. The container runs as your user, so files it writes come back owned by you.

It is recreated automatically when the shared folders, the port or the image change, and stopped cleanly by whygraph down (it gives a running scan time to be recorded as interrupted). The host commands are in Start the portal.

The database container

The portal keeps its own data - projects, settings, encrypted keys, scan history - in Postgres, in a second container, whygraph-portal-postgres, that whygraph up starts first:

  • Network-only. It publishes no port. It sits on the same whygraph-portal Docker network, so the portal reaches it there and nothing on your host or LAN can.
  • Its files are in the data directory, in postgres/ (bind-mounted at /var/lib/postgresql, one subdirectory per Postgres major version), and it runs as your user, so they are yours too.
  • Its password is generated on the first up into postgres.password in the data directory (mode 0600) and handed to both containers as a file, never as an environment value.
  • up waits for it. The container has a pg_isready health check, and up waits up to 60 seconds for it to report healthy before starting the portal; if it does not, up fails and starts no portal.
  • Pinned image. The shim pins an exact postgres image on the Debian trixie variant, baked into it at install time. WHYGRAPH_POSTGRES_IMAGE overrides it, for a registry mirror; it must be the same Postgres major version.
  • When it is recreated. Only when it is missing or stopped, or its image differs from the pin (a new release, or a changed WHYGRAPH_POSTGRES_IMAGE). Before recreating a running one, up dumps it into backups/, and a failed dump stops up with both containers untouched; WHYGRAPH_SKIP_BACKUP=1 skips the dump. Recreating it also recreates the portal, which is stopped first.
  • Idle footprint is about 30 MiB of RAM.

Its logs are docker logs whygraph-portal-postgres. Backups and restores are in Backup and restore.

Credentials

The portal container is started with none of your environment. Keys and tokens are entered in the portal's Settings and stored encrypted in its database - see the security model.

The one-shot shim path forwards none of them either: GH_TOKEN, GITHUB_TOKEN and the provider API keys in your shell reach nothing, because there is no headless scan to use them.

Never bake a token into the image

Pass credentials at run time, never at build time.

Agents

Agents do not launch anything in Docker any more. They connect over HTTP to the portal's per-project endpoint, which the portal writes into each agent's config when you initialize the project. See Connecting agents.

Build the image yourself

Building locally instead of pulling - say, while developing:

docker build -f docker/whygraph/Dockerfile -t whygraph:latest .
WHYGRAPH_IMAGE=whygraph:latest whygraph up

WHYGRAPH_IMAGE overrides the image the shim runs, so you can test a local build without touching the install. The image also carries the built Explorer bundle, so a local build serves the portal UI too.

A local build reports itself as latest

The release version is baked in at build time. Building yourself bakes latest, which is what whygraph version and the installer read back - expected, not a bug.