Skip to content

Operations

Everything after the first boot: creating workspaces and keys, adding people, proving tenant isolation still holds, taking a backup, upgrading, and deleting a tenant for good.

This page covers the two stacks that ship the operator CLI: the customer-local bundle and your own AWS account. The open source server has no CLI and no tenants. Its whole administrative surface is the MG_TOKENS list you start it with.

The CLI has no console script. It is a module, always invoked as python -m metergraph_app.cli, and it needs the database owner role, so it runs in a container that has one.

On the customer-local stack, use a one-shot bootstrap container. That service already carries the owner credentials and the LocalStack endpoints, and --no-deps keeps it from restarting anything that is already up. This is the same path bin/demo-ingest uses.

docker compose --env-file .env --env-file .runtime.env --env-file .images.env \
-f docker-compose.yml run --rm --no-deps -T bootstrap \
python -m metergraph_app.cli list-tenants

All three --env-file arguments are required, in that order. Drop .images.env and Compose has no image reference at all; drop .runtime.env and the least-privilege database password is missing.

On AWS, the same commands run as a container override on the admin task definition. Terraform emits its ARN as migration_admin_task_definition_arn, and the subnets and security groups as admin_network_configuration. The container is named admin. There is a worked example on Your own AWS account.

The examples below are written as the bare command. Wrap each one in whichever of those two forms applies to you.

CommandWhat it doesWhere it runs
list-tenantsEvery tenant, with its active key countAnywhere
create-tenant SLUGMigrates, creates the tenant, mints an initial ingest keyAnywhere
issue-key SLUGMints one more key for an existing tenantAnywhere
invite-user SLUG EMAILAdds an email-identity memberLocal and hosted
bootstrap-workspace SLUGCreates a workspace and its exact OIDC ownerBYOC
grant-member SLUGGrants an exact OIDC identity a roleBYOC
set-platform-admin EMAILGrants or revokes global catalog accessHosted
bootstrap-localCreates or updates the local password administratorLocal profile only
demo-ingest-key issue or revokeMints or revokes the disposable demo credentialLocal profile only
setup-localMigrates and aligns the Metergraph development workspaceDevelopment checkouts only
verify-rlsProves row-level tenant isolation on the live databaseAnywhere
verify-ingest --url URLProves the ingest path end to endAnywhere
purge-tenant SLUG --confirm SLUGPermanently removes a tenant everywhereAnywhere
verify-tenant-purge --url URLProves a purge really removed live resourcesHosted and BYOC

bootstrap-local and demo-ingest-key check METERGRAPH_DEPLOYMENT_PROFILE before they read a password or touch anything, and exit 2 on any other profile. They cannot be pointed at a hosted deployment by accident.

python -m metergraph_app.cli create-tenant acme --name "Acme Corp"

It runs migrations first, then creates the tenant and mints one initial key. The token is printed once, and only its hash is kept. Save it before you close the terminal. If the slug already exists the command exits 1 and changes nothing.

--zero-raw-retention makes the tenant delete raw objects as soon as the normalized rows commit.

Which command you use depends on how the deployment authenticates.

On the customer-local stack, sign-in is an email and password, and the admin was created by bootstrap-local from .env. To change that account, edit .env and run ./bin/start again: it re-runs bootstrap, which updates the administrator in place.

python -m metergraph_app.cli invite-user acme person@example.com \
--name "Person Name" --role member

invite-user upserts the user by email and sets their role, so running it again is how you change someone’s role. Roles are owner, member and viewer.

On AWS, identity comes from Cognito and email grants nothing. Use bootstrap-workspace for the first owner and grant-member afterwards, both keyed on the immutable (identity_issuer, identity_subject) pair. See Your own AWS account.

python -m metergraph_app.cli issue-key acme --label "checkout service"

The token is printed and never stored in the clear. Scopes default to ingest and read, which is what the SDK needs. --scope is repeatable and accepts ingest, read and agent:read.

Keys created in the dashboard are ingest only. Everything about key format and scope is on Identity model.

python -m metergraph_app.cli verify-rls

This is a live test, not a schema inspection. It creates two throwaway tenants with a batch, a key, a user and a membership each, connects as the least-privilege runtime role, and asserts two things: that the role has none of rolsuper, rolcreaterole, rolbypassrls or rolinherit, and that with no tenant context set it can see zero tenants, zero batches and zero keys. It needs METERGRAPH_RUNTIME_DB_PASSWORD, which the bootstrap container already has. Run it after any upgrade that touched migrations.

python -m metergraph_app.cli verify-ingest --url https://your-host --timeout 120

Add --zero-raw-retention when the tenant under test has it set, so the check expects the raw object to be gone.

./bin/status prints a JSON readiness report from inside the api container: API health, the deployment profile, the LocalStack bucket and queue, and analysis-run counts. ./bin/verify runs that and then sends one demo trace through the real ingestion API with a disposable key that is revoked either way. Neither reads trace content, and both work before any provider key is set.

Your data is in Postgres. On the customer-local stack that is the pgdata volume, holding database metergraph owned by role metergraph, and nothing else in the stack is stateful in a way you need to keep: LocalStack holds only raw ingested payloads, which are already normalized into the database.

docker compose --env-file .env --env-file .runtime.env --env-file .images.env \
-f docker-compose.yml exec -T postgres \
pg_dump -U metergraph metergraph | gzip > metergraph-$(date +%F).sql.gz

On AWS, RDS handles this: point-in-time recovery is on, and backup_retention_days controls the window. Deletion protection defaults on, so an ordinary terraform destroy leaves the database, the buckets and the key in place unless break_glass_allow_destroy is set.

Customer-local. Download the new bundle and manifest, verify them the way Verifying a release describes, extract the bundle over the old directory, and run ./bin/start. It resolves the new digest-pinned images and bootstrap re-runs migrations without touching your data. Your .env is not part of the bundle, so it survives.

Log in to the registry again first if your pull credential has rotated.

AWS. Upgrades change image and analysis_runner_image to new digest-pinned references and re-apply. Both variables refuse anything that is not @sha256: followed by 64 hex characters, so an upgrade cannot silently follow a moving tag. The migration runs as the admin task, not as part of the service deployment. Your release package carries the version-specific order.

After any upgrade, verify-rls and verify-ingest are the two checks worth repeating.

python -m metergraph_app.cli purge-tenant acme --confirm acme

The purge is ordered so that nothing can slip through behind it. The tenant is marked purging_at first, which fails new authorization closed. Then a shared advisory lock waits out any API or worker operation that was already authorized when the mark committed. Only then does deletion run: every object version under the tenant’s prefix, then the database rows and any dashboard user left with no other membership, in one transaction.

It is resumable, so running it again after an interruption is safe. It prints a JSON summary naming exactly what it removed, including the object-version count and the number of orphaned users deleted.

Vector indexes, inference profiles and Cognito users are hosted-only resources. Under byoc-core those steps are skipped, because the deployment never created them.

To prove a purge on a deployment that had live cloud resources, verify-tenant-purge creates real resources, purges them, and asserts they are gone:

python -m metergraph_app.cli verify-tenant-purge --url https://your-host \
--timeout 240

Two commands exist for working on Metergraph itself, not for running it.

setup-local migrates and aligns the dev-local workspace in a development checkout. It refuses before migrating or connecting unless every local-only safety condition holds, and it never seeds reports or touches Docker. seed-dev-reports and seed-dev-traces load fixture data into that same workspace.

Neither belongs on a machine holding real traffic.