Getting Started¶
Using Claude Code?
eng-metrics-suite (and eng-metrics-suite-pro) ship a
gitultra-setup skill that automates everything on this page
through Generating Reports -- clone the
repo, open it in Claude Code, and ask it to set up GitUltra. It
detects your tier, asks only for what it can't infer (org name,
provider tokens, which optional integrations to enable), and
verifies the install with a real generated report before calling
it done. The steps below are what it's automating, and remain the
reference if you'd rather do it by hand or the skill doesn't
behave as expected.
Clone eng-metrics-suite, then:
Team / Enterprise tiers¶
Clone eng-metrics-suite-pro
instead of eng-metrics-suite if you're on the Team or Enterprise
tier -- same setup, plus the paid add-ons (eng-api, gitultra-mcp)
already wired into the compose file. The repo itself is public and
free to clone, but running the paid add-ons needs a signed
GITULTRA_LICENSE_KEY -- email support@gitultra.com to get one.
See that repo's QUICKSTART.md
once you have a key.
The steps below are the same for both -- continue here first regardless of tier.
cp .env.example .env
# edit .env: set a POSTGRES_PASSWORD, and the token(s) for whichever
# provider(s) you're importing from (GitHub/GitLab/Bitbucket/Azure DevOps)
sudo mkdir -p /var/lib/eng-metrics-suite
sudo chown "$(id -u):$(id -g)" /var/lib/eng-metrics-suite
mkdir -p reports
docker compose up -d
Why the two mkdirs¶
/var/lib/eng-metrics-suite is where config files (not secrets — those
go in .env) live: discover_repos.py's --config YAML (see
Discovering Repos) and report.py's --team-map
CSV (see Generating Reports) both get read from
there, mounted read-only into the relevant containers at the same path.
It needs to exist before docker compose up — Docker will auto-create
it as root-owned otherwise, which then blocks you from writing to it
without sudo.
./reports needs the same up-front mkdir, for a related but different
reason: eng-reports runs as a non-root user (UID 1000) so its output
isn't root-owned on the host, but that only helps once the directory
already exists with the right owner. If Docker has to auto-create
./reports itself (e.g. because you skipped this step), it does so as
root, and the non-root container then can't write into it at all
(PermissionError: [Errno 13] Permission denied). If your host user
isn't UID 1000 (check with id -u), add --user "$(id -u):$(id -g)" to
the docker compose run commands in Generating
Reports instead.
On eng-metrics-suite-pro (Team/Enterprise): if you plan to use
the admin-only live CSV-upload endpoints for team_map.csv/
org_roles.csv (see API Access),
also create a writable subdirectory alongside the read-only one above:
sudo mkdir -p /var/lib/eng-metrics-suite/writable
sudo chown "$(id -u):$(id -g)" /var/lib/eng-metrics-suite/writable
eng-api mounts this one specific subdirectory read-write (everything
else under /var/lib/eng-metrics-suite stays read-only to it) --
point ENG_API_TEAM_MAP_PATH/ENG_API_ORG_ROLES_PATH at a file inside
it if you want either file updatable without a restart. Not needed if
you're happy hand-editing and restarting, same as any other config
file here.
What docker compose up actually starts¶
Postgres, plus the git-processor and pr-processor workers (and
issue-processor, if your compose bundle includes it — see
Jira Integration). The workers exit as soon as
they find nothing queued to do — that's expected, not a crash;
restart: unless-stopped just means they check again periodically
rather than sitting in a busy loop. Nothing happens until you queue
some repos — continue to Discovering Repos.
Using an external database¶
Running a very large number of repos? The bundled postgres:16
container is fine to start with, but a single Docker container on the
same host has real ceilings on storage, backup, and monitoring.
Set DATABASE_URL in .env to point every service at your own
managed Postgres instead — RDS, Cloud SQL, Azure Database for
PostgreSQL, or any other standard Postgres 12+ instance:
DATABASE_URL=postgres://user:pass@host:port/dbname?sslmode=require
Nothing here needs anything beyond standard Postgres (no custom
extensions), so any managed offering should work. Most providers
require ?sslmode=require (or their own equivalent) in the connection
string — check your provider's docs if the connection is refused.
Leave DATABASE_URL unset to keep using the bundled container, which
remains the default.
If you've pointed at an external database, the bundled postgres
container is no longer used for anything — you can leave it running
unused (harmless, just a little idle disk/CPU) or remove it entirely
from your own copy of docker-compose.yml: delete the postgres:
service block and the pgdata: entry under the top-level volumes:
section.