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,foldersandbackup- manage two named, long-lived containers,whygraph-portaland its database,whygraph-portal-postgres. They are the one exception to "ephemeral per command". - Every other command (
versionandinstall) 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-portalDocker 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
upintopostgres.passwordin the data directory (mode0600) and handed to both containers as a file, never as an environment value. upwaits for it. The container has apg_isreadyhealth check, andupwaits up to 60 seconds for it to report healthy before starting the portal; if it does not,upfails and starts no portal.- Pinned image. The shim pins an exact
postgresimage on the Debian trixie variant, baked into it at install time.WHYGRAPH_POSTGRES_IMAGEoverrides 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,updumps it intobackups/, and a failed dump stopsupwith both containers untouched;WHYGRAPH_SKIP_BACKUP=1skips 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.