Skip to content

Run it locally

Two different things run on one machine, and they are not the same product. Conflating them is what made the old instructions unfollowable, so this page keeps them apart. Pick one before you copy a command.

Open source serverCustomer-local bundle
SourcePioneerSquareLabs/metergraph, Apache-2.0A signed release bundle from Metergraph
Start it withdocker compose up./bin/start
Serves onhttp://localhost:8787http://127.0.0.1:8080
Databasepostgres:16postgres:17
You getThe performance dashboardDashboard, alerts, and optimization analysis
ContentStripped at ingest, alwaysCaptured unless you turn it off
Account neededNoneA registry credential from Metergraph

If you are still choosing between running it yourself and the hosted service, read Choosing a deployment first.

Apache-2.0, no account, no key minting, and no egress. It is the genuinely one-command path: a FastAPI server with the React dashboard served by the same process, in front of Postgres 16.

It records and prices traffic. It does not run alerts or optimization analysis, and it never stores content: the ingest handler projects rows through a column allowlist, so prompts and completions cannot reach the database whatever the SDK is configured to send.

  1. git clone https://github.com/PioneerSquareLabs/metergraph
    cd metergraph
    MG_TOKENS=dev-token docker compose up

    The compose file already defaults MG_TOKENS to dev-token, so a bare docker compose up works too. Set the variable when you want a token of your own:

    MG_TOKENS=$(openssl rand -hex 16) docker compose up -d

    Tokens are whatever you say they are. MG_TOKENS takes a comma-separated list, so you can give each producer its own and rotate them independently.

  2. export METERGRAPH_INGEST_URL=http://localhost:8787
    export METERGRAPH_APP_TOKEN=dev-token

    Moving between a self-hosted server and the hosted service later is METERGRAPH_INGEST_URL in either direction, and nothing else. See Install the SDK.

  3. http://localhost:8787, and enter the same token. The server runs its migrations on startup, so there is no separate schema step.

  4. python -m pip install 'metergraph>=0.4,<1'
    MG_TOKEN=dev-token python scripts/seed_demo.py

    The demo wraps local OpenAI-, Anthropic- and Google-shaped clients with the published SDK, so it exercises the real session exchange and batched ingest without a provider key and without spending anything. Seeded calls land in the demo environment, which you can deselect in the dashboard.

Each of these is read from the environment by the server process.

VariableDefaultWhat it does
DATABASE_URLpostgresql://metergraph:metergraph@localhost:5432/metergraphPostgres DSN. The compose file points it at the postgres service.
MG_TOKENSempty, which rejects every requestComma-separated static bearer tokens
MG_PRICES_PATHthe bundled prices.yamlMount a newer price catalog without rebuilding
MG_REGIONglobalPreferred price region, falling back to * then global
MG_MAX_BODY_BYTES8388608Ingest body cap
MG_MAX_ROWS5000Rows per ingest request
MG_SESSION_TTL_SECONDS300, clamped to 60 through 3600Ingest session lifetime
MG_DASHBOARD_DIST/app/static in the imageWhere the built dashboard lives

Authentication is a static bearer token, and the dashboard keeps its token in browser local storage. Run the server inside a network boundary you trust, and treat dashboard access as trusted-network access.

The full product on one machine: dashboard, alerts, and optimization analysis, with traces that never leave the machine. This is a signed release you receive from Metergraph, not a repository you clone. Six services come up together.

ServiceRole
postgresYour data, on the pgdata volume
localstackThe S3 and SQS APIs the application calls, kept local
bootstrapOne shot: migrates the schema, creates the admin, exits
apiDashboard and HTTP API on http://127.0.0.1:8080
workerIngestion and scheduled work
local_runnerRuns analyses. The only service given a provider key
  • Docker with Compose, on macOS or Linux. On Windows, run the bundle inside WSL with Docker’s WSL integration on.
  • cosign. bin/start verifies the release with it and refuses to run without it.
  • A pull-only registry credential for the private images, which comes with your invitation.

A model provider key is needed only to run an analysis. Sign-in, ingestion, the dashboard, the readiness check and the demo ingestion all work without one.

Verify the bundle before you extract it. That procedure is on Verifying a release, and it has to happen first: bin/start ships inside the archive being checked, so it cannot check itself.

  1. echo "$METERGRAPH_PULL_TOKEN" | docker login ghcr.io \
    -u "$METERGRAPH_PULL_USER" --password-stdin
  2. cp .env.example .env

    Three values in .env must be set before the first boot.

    local/.env
    METERGRAPH_LOCAL_ADMIN_EMAIL=admin@example.com
    METERGRAPH_LOCAL_ADMIN_PASSWORD=<choose a strong password>
    METERGRAPH_DB_PASSWORD=<choose a strong password>

    Compose needs two more variables, METERGRAPH_APP_IMAGE and METERGRAPH_RUNNER_IMAGE, and neither is yours to set. bin/start resolves both from the signed release manifest into a generated .images.env, and passes that file to Compose last so a stray value in .env cannot win. A third generated file, .runtime.env, holds METERGRAPH_RUNTIME_DB_PASSWORD: the least-privilege database login the API and worker use. It is machine-generated on first boot and reset by every migration, so it is never your input either.

  3. ./bin/start

    It verifies the manifest and both image digests with cosign, resolves those digests, runs Compose, and waits until every service is healthy and bootstrap has exited zero. Then it prints a JSON readiness report, and on the first boot of a new database it sends one demo trace through the real ingestion API under environment local-demo.

    If bootstrap fails, the API, worker and runner never start, and bin/start exits non-zero naming the log to read.

  4. Open http://localhost:8080 and use the admin email and password from .env.

Create an ingest key under Keys in the dashboard, then point an application at the machine:

export METERGRAPH_INGEST_URL=http://localhost:8080
export METERGRAPH_APP_TOKEN=mg_...

To load traffic you already record elsewhere, the same two variables drive Importing existing traffic.

An analysis makes two kinds of model call, and they are configured separately. Only local_runner ever receives a key.

Its own calls, which classify workloads, build the evaluations and judge the results, go out on the channel you set in .env:

METERGRAPH_ANALYSIS_CONTROL_CHANNELKey it reads
vercel-ai-gateway (default)AI_GATEWAY_API_KEY
anthropic-apiANTHROPIC_API_KEY
openai-apiOPENAI_API_KEY

Until that channel’s key is set, an analysis request is refused up front with a message naming it. No gateway account is required: set the channel to whichever provider you already have.

The candidate calls, which try your workload on other models, use every key you have set. A gateway key compares the widest field, FIREWORKS_API_KEY adds the models Fireworks publishes, and a direct provider key compares that provider’s own models. GOOGLE_API_KEY routes no candidate on its own and is read only by search-enabled workloads.

Automatic weekly analysis is off by default. Turn it on once a manual analysis has succeeded, with METERGRAPH_AUTOMATIC_ANALYSIS_ENABLED=true in .env and another ./bin/start.

./bin/status prints the readiness report on demand. ./bin/verify runs it and then sends a demo trace. Both are content-free and work with no provider key.

Use stop and start between sessions rather than down. Your database survives down, but the raw ingested payloads held in local object storage do not.

docker compose --env-file .env --env-file .runtime.env --env-file .images.env \
-f docker-compose.yml stop

Backups, upgrades, workspace and key management, and the tenant purge are on Operations.