Skip to content

Repository files navigation

gghstats-selfhosted

Version Release License: MIT App image on GHCR gghstats app gghstats clones

gghstats-selfhosted — Docker, Compose, Helm, run/

Deployment manifests for gghstats — Compose, Helm, docker run, optional observability. App source and releases: github.com/hrodrig/gghstats.

Demo: gghstats.hermesrodriguez.com · Observability example: gghstats-obs.hermesrodriguez.com

Policies: Community and policies, community standards — changelog, contributing, security, code of conduct, agent guidelines.


Table of contents


Pick a path

You want… Section
Binary only (no Docker) Standalone binary — Linux: systemd, .deb/.rpm, config
Linux .deb / .rpm + systemd run/standalone/linux/README.md
Single container (docker run) Docker single container
Compose, one service (quick VPS) Docker Compose minimal
HTTPS + domain (Traefik + Let’s Encrypt) Docker Compose Traefik HTTPS
Prometheus / Grafana / Loki (after Traefik) Observability optional
Authelia SSO + 2FA (advanced, after Traefik) Authelia SSO
Kubernetes Kubernetes Helm
Harden a fresh VPS (optional, agnostic Ansible; recommendations only) Recommended VPS baseline
Simpler or custom dashboard look (CSS overlay) Custom UI theme (optional)

Shared env template for Compose: copy run/common/.env.example to ${GGHSTATS_HOST_DATA}/.env, set GGHSTATS_HOST_DATA inside that file, and pass --env-file "${GGHSTATS_HOST_DATA}/.env" to Compose. Deeper walkthroughs: run/README.md.

↑ Contents


Standalone binary

Goal: run the app without Docker — tarball, .deb, .rpm, or systemd on Linux.

All implementation detail (env file paths, systemctl, package install, filters on a server) is in run/standalone/ — start with Linux for packages and systemd.

Smoke test (UI only) — minimal path to try the dashboard; not production. Same commands as gghstats Quick start:

curl -fsSL https://raw.githubusercontent.com/hrodrig/gghstats/main/scripts/install.sh | sh
export GGHSTATS_GITHUB_TOKEN=ghp_xxx
gghstats run --open

run is an alias for serve; --open opens the browser when the dashboard is ready. Background sync starts automatically. Open http://localhost:8080 if you did not use --open. Other install options (Homebrew, .deb, tarball): gghstats Install.

macOS / Windows: run/standalone/macos · run/standalone/windows

↑ Contents


Docker single container

Goal: one container, no Compose file.

export GGHSTATS_HOST_DATA=/home/gghstats/gghstats-data
mkdir -p "$GGHSTATS_HOST_DATA"

docker run -d \
  -e GGHSTATS_GITHUB_TOKEN=ghp_xxx \
  -e GGHSTATS_FILTER="your-github-user/*" \
  -p 8080:8080 \
  -v "${GGHSTATS_HOST_DATA}:/data" \
  --name gghstats \
  ghcr.io/hrodrig/gghstats:v1.0.1

Use an image tag that exists on GHCR (releases); match GGHSTATS_VERSION in run/common/.env.example.

Check: curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ (expect 200 or 3xx).

Remove:

docker stop gghstats && docker rm gghstats

More: run/docker/README.md

↑ Contents


Docker Compose minimal

Goal: quick stack from this repo (single service, GHCR image).

git clone https://github.com/hrodrig/gghstats-selfhosted.git
cd gghstats-selfhosted
export GGHSTATS_HOST_DATA=/home/gghstats/gghstats-data
mkdir -p "$GGHSTATS_HOST_DATA"
cp run/common/.env.example "${GGHSTATS_HOST_DATA}/.env"
# Edit "${GGHSTATS_HOST_DATA}/.env": GGHSTATS_GITHUB_TOKEN, GGHSTATS_VERSION, and GGHSTATS_HOST_DATA (same path as above)

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" -f run/docker-compose/minimal/docker-compose.yml up -d

Check: curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ (or your GGHSTATS_PORT).

Remove:

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" -f run/docker-compose/minimal/docker-compose.yml down

More: run/docker-compose/minimal/README.md

↑ Contents


Docker Compose Traefik HTTPS

Goal: production-style TLS on your domain (ports 80 / 443).

Prerequisites: DNS A/AAAA for GGHSTATS_HOSTNAME → this host; 80 and 443 reachable.

git clone https://github.com/hrodrig/gghstats-selfhosted.git
cd gghstats-selfhosted
export GGHSTATS_HOST_DATA=/home/gghstats/gghstats-data
mkdir -p "$GGHSTATS_HOST_DATA"
cp run/common/.env.example "${GGHSTATS_HOST_DATA}/.env"
# Edit "${GGHSTATS_HOST_DATA}/.env": GGHSTATS_GITHUB_TOKEN, GGHSTATS_HOSTNAME, ACME_EMAIL, GGS_UID, GGS_GID,
# GGHSTATS_VERSION, and GGHSTATS_HOST_DATA (same absolute path as above — SQLite lives next to this file)

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" -f run/docker-compose/traefik/docker-compose.yml up -d

Check: curl -sS -o /dev/null -w '%{http_code}\n' https://your-hostname/ after DNS and TLS succeed.

Remove:

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" -f run/docker-compose/traefik/docker-compose.yml down

More: run/docker-compose/traefik/README.md

Rate limiting (defence in depth): gghstats ≥ 0.7.5 applies built-in per-IP rate limiting (120 req/min, burst 20). The Traefik compose keeps its own rate-limit middleware at the edge — together they provide layered protection. If you tune the in-app limits, you can relax the Traefik layer. See gghstats README — Rate limiting.

Trusted proxies (≥ 0.10.2): behind Traefik, set GGHSTATS_TRUSTED_PROXIES to the Docker network CIDR Traefik uses when dialing gghstats (often a private range such as 172.16.0.0/12) so in-app rate limit / IP whitelist see the real client via X-Forwarded-For. Empty = ignore forwarded headers (safe default). Passed through by this Compose stack from ${GGHSTATS_HOST_DATA}/.env.

IP whitelist (≥ 0.7.6): restrict access by IP/CIDR with GGHSTATS_WHITELIST and GGHSTATS_WHITELIST_PATHS. Non-matching IPs receive 403. Scope to specific paths (e.g. /api/) while keeping the dashboard public. See gghstats README — IP whitelist.

Remote sync (≥ 0.7.10): when GGHSTATS_API_TOKEN is set, dashboard Sync all sends x-api-token and bypasses the IP whitelist on protected paths — you can keep /api/ whitelisted without opening it to the whole internet.

/metrics is public by default. The Traefik compose excludes it from the public router (!PathPrefix). Prometheus scrapes internally via http://gghstats:8080/metrics on the Docker network. If you use minimal Compose or docker run, protect /metrics with a firewall or set GGHSTATS_METRICS=false.

↑ Contents


Observability optional

Goal: Prometheus, Grafana, Loki, etc. Requires the Traefik stack above so network gghstats_edge exists. Use the same GGHSTATS_HOST_DATA as your main ${GGHSTATS_HOST_DATA}/.env (SQLite and secrets in one host directory).

export GGHSTATS_HOST_DATA=/home/gghstats/gghstats-data
mkdir -p "$GGHSTATS_HOST_DATA"
cp run/docker-compose/observability/observability.env.example "${GGHSTATS_HOST_DATA}/.env.observability"
# Edit "${GGHSTATS_HOST_DATA}/.env.observability" — set GRAFANA_ADMIN_PASSWORD at minimum (must match GGHSTATS_HOST_DATA used for Traefik / main .env)

Expose Grafana on HTTPS via Traefik (public hostname) — use this if you want Grafana on the internet with the same Traefik / Let’s Encrypt as gghstats (recommended once DNS is ready):

  1. In "${GGHSTATS_HOST_DATA}/.env.observability", set a dedicated FQDN and matching root URL (must match what users open in the browser):

    GRAFANA_HOSTNAME=gghstats-obs.example.com
    GRAFANA_ROOT_URL=https://gghstats-obs.example.com
  2. DNS: point GRAFANA_HOSTNAME to this host (A/AAAA or CNAME), same idea as GGHSTATS_HOSTNAME for the main app.

  3. Start the stack with both Compose files (the second file adds Traefik labels only; it does not add another Traefik container). Use both -f lines on every up / pull / down that recreates Grafana, or HTTPS routing breaks until you fix it.

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env.observability" -p gghstats-obs \
  -f run/docker-compose/observability/docker-compose.observability.yml \
  -f run/docker-compose/observability/docker-compose.observability.traefik.yml \
  up -d

Local / LAN only (no Traefik route for Grafana) — Grafana on http://localhost:${GRAFANA_PORT:-3000}; omit docker-compose.observability.traefik.yml:

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env.observability" -p gghstats-obs \
  -f run/docker-compose/observability/docker-compose.observability.yml up -d

Check / troubleshoot / SSH tunnel: run/docker-compose/observability/README.md (curl checks, down -v).

Remove (containers + stack volumes): use the same -f list you used for up. Examples:

# If you started with Traefik overlay (two files), remove with two files:
docker compose --env-file "${GGHSTATS_HOST_DATA}/.env.observability" -p gghstats-obs \
  -f run/docker-compose/observability/docker-compose.observability.yml \
  -f run/docker-compose/observability/docker-compose.observability.traefik.yml \
  down -v

# If you started with only the base file:
docker compose --env-file "${GGHSTATS_HOST_DATA}/.env.observability" -p gghstats-obs \
  -f run/docker-compose/observability/docker-compose.observability.yml down -v

↑ Contents


Kubernetes Helm

Recommended: install from the Helm repository on GitHub Pages (index.yaml; chart packages are attached to GitHub Releases as gghstats-<version>.tgz).

GitHub Pages: The Pages URL serves index.yaml for Helm and includes a short HTML landing for humans. helm repo add only needs the HTTPS base URL — you do not have to open the site in a browser.

Naming (this repo vs the chart): This GitHub repository is gghstats-selfhosted (deployment manifests only). The Helm chart lives under run/kubernetes/helm/gghstats/ — the final directory name gghstats is the chart name (see name: in Chart.yaml), the same name as the application the chart deploys. It is not the repository name. Published chart packages and chart-releaser GitHub Releases use the pattern gghstats-<chart-version> (e.g. gghstats-0.1.7.tgz); Git tags for this repo use v<semver> (e.g. v0.1.8) per VERSION.

helm repo add gghstats https://hrodrig.github.io/gghstats-selfhosted
helm repo update
helm install gghstats gghstats/gghstats -n gghstats --create-namespace -f my-values.yaml

GitHub token (recommended): do not put the PAT in my-values.yaml. Create a secret that matches the chart defaults (secretName: gghstats-secret, secretKey: github-token — see githubToken in values.yaml). Replace YOUR_GITHUB_TOKEN with your classic or fine-grained PAT (repo scope as needed):

kubectl create namespace gghstats
kubectl create secret generic gghstats-secret \
  -n gghstats \
  --from-literal=github-token=YOUR_GITHUB_TOKEN

Then run helm install (omit --create-namespace if the namespace already exists). Keep githubToken.value: empty in my-values.yaml so Helm does not embed the token in a release manifest.

Use helm show values gghstats/gghstats (after helm repo update) or the copy in the repo browser to build my-values.yaml (image tag, persistence, resources, etc.). Pick any namespace with -n (here gghstats); if you use another name, use the same namespace in kubectl and helm and adjust my-values.yaml if you reference the secret explicitly.

If helm repo add fails (network, Pages outage, or first minutes after a new release), try again later or install from this repository below.

From this repository (sources, templates, contributing): the chart under run/kubernetes/helm/gghstats/ is the same chart; clone it to inspect YAML, open issues, or install without the published repo:

git clone https://github.com/hrodrig/gghstats-selfhosted.git
cd gghstats-selfhosted
helm install gghstats ./run/kubernetes/helm/gghstats -n gghstats --create-namespace -f my-values.yaml

See values.yaml in-tree for defaults.

Check: kubectl get pods -n gghstats -l app.kubernetes.io/name=gghstats and your Ingress/Service URL.

Remove:

helm uninstall gghstats -n gghstats

More: run/kubernetes/helm/gghstats/README.md · run/kubernetes/manifests

↑ Contents


Recommended VPS baseline (optional)

Goal: generic host hygiene on a Debian/Ubuntu VPS — not gghstats containers or Traefik labels. The playbook can optionally install Docker Engine (CE) for Compose; skip that tag for binary-only or Helm nodes.

If the attack vector is a misconfigured or compromised VPS, HTTP headers on the app will not help; operators secure SSH, firewall, and automatic security updates themselves. This repo ships an optional, agnostic Ansible playbook you run from your laptop against your server inventory.

Operator responsibility: content under run/vps-recommended/ is recommendations only — not a managed service. You must analyze, validate, and choose what to apply on your VPS; outcomes are your responsibility. See the full disclaimer in that README.

Note: step-by-step guides below often use Docker Compose because it is the usual path on a VPS; the baseline here is not Compose-specific — the same practices apply to docker run, a standalone binary, or Helm on Kubernetes (plus cluster-level controls where relevant).

Topic Where
Philosophy, tags, dry-run run/vps-recommended/README.md
Playbook run/vps-recommended/ansible/playbook.yml

Typical order: (1) VPS baseline → (2) pick any install path (Compose is the common example; others are equivalent from a host-security view) → (3) store secrets and SQLite outside the clone (persistent data — Compose uses ${GGHSTATS_HOST_DATA}/.env; Helm uses Secrets/PVC; binary uses host paths you choose).

↑ Contents


Authelia SSO (advanced, optional)

Goal: Single Sign-On with 2FA (TOTP/WebAuthn) in front of gghstats. Protects /api/ and /h2h — dashboard stays public.

Prerequisites: Traefik stack running on gghstats_edge.

Setup guide →


Opt-in alerts

gghstats v0.10.0+ sends traffic/ops alerts to Slack, generic webhooks (Discord, Teams, n8n), or Loki; v0.10.1+ adds SMTP email. Alerts remain off unless GGHSTATS_ALERTS_ENABLED=true.

Compose (minimal / Traefik): keep provider secrets in ${GGHSTATS_HOST_DATA}/.env; both stacks explicitly pass the standard GGHSTATS_* sink variables. Configure GGHSTATS_ALERT_SINKS and GGHSTATS_ALERT_RULES using the examples in run/common/.env.example, then recreate with up -d (not restart alone).

Validate Slack delivery

Minimal Slack-only config (replace the webhook URL; never commit real secrets):

GGHSTATS_ALERTS_ENABLED=true
GGHSTATS_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/T…/B…/…'
GGHSTATS_ALERT_SINKS='[{"type":"slack","webhook_url_env":"GGHSTATS_SLACK_WEBHOOK_URL"}]'
# Optional rules — evaluated after a successful sync, not by alert test:
# GGHSTATS_ALERT_RULES='[{"scope":"all_repos","metric":"clones","window":"lifetime","op":"gte","value":21000,"fire":"once"}]'

Smoke-test from the clone root (Traefik stack; use minimal if that is your compose file):

./run/scripts/compose-stack.sh traefik up -d --pull always
docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" \
  -f run/docker-compose/traefik/docker-compose.yml \
  exec gghstats gghstats alert test --sink slack

Expected console output:

alert test: sent kind "traffic" to 1 sink(s)

Exit 0 = delivered; 1 = config; 4 = delivery failure. The Slack channel should show a synthetic payload (example/repo, rule: delivery check) — not your real repos:

Slack alert test — synthetic delivery check

gghstats alert test only checks sink delivery. Real rules fire after the next successful sync (or use the dashboard Sync now button if GGHSTATS_SYNC_ON_STARTUP=false).

Other families: --sink smtp, --sink webhook, or --sink loki.

Helm: set alerting.enabled, alerting.sinks, and alerting.rules. Put provider credentials in an existing Secret whose keys are the exact environment-variable names referenced by the sink JSON, then set alerting.existingSecret to that Secret name. Example:

kubectl create secret generic gghstats-alerts -n gghstats \
  --from-literal=GGHSTATS_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...'
alerting:
  enabled: true
  existingSecret: gghstats-alerts
  sinks: '[{"type":"slack","webhook_url_env":"GGHSTATS_SLACK_WEBHOOK_URL"}]'
  rules: '[{"scope":"all_repos","metric":"clones","window":"lifetime","op":"gte","value":21000,"fire":"once"}]'

Full rule/sink contract: gghstats SPEC §8.

↑ Contents


Persistent data and secrets

Recommended on servers: colocate SQLite and env files outside the clone (see below).

Keep SQLite, ${GGHSTATS_HOST_DATA}/.env, and ${GGHSTATS_HOST_DATA}/.env.observability in one host directory (e.g. /home/gghstats/gghstats-data/). Set GGHSTATS_HOST_DATA inside the main .env to that absolute path. Run Compose from the clone root with --env-file "${GGHSTATS_HOST_DATA}/.env" for the app stacks and --env-file "${GGHSTATS_HOST_DATA}/.env.observability" for observability (-p gghstats-obs). See run/common/.env.example and run/docker-compose/observability/observability.env.example. Optional helper: run/scripts/compose-stack.sh — e.g. ./run/scripts/compose-stack.sh full restart for Traefik + gghstats + observability without remembering compose order (--help for prod vs full).

↑ Contents


Custom UI theme (optional)

Requires a gghstats image 0.2.0 or newer (this repo’s Compose defaults use v1.0.1). The app serves an extra stylesheet at GET /theme/custom.css when GGHSTATS_CUSTOM_CSS points at a regular .css file readable inside the container.

Where the theme file must live (bind mount vs PVC)

The path in GGHSTATS_CUSTOM_CSS / env.customCss is always inside the container. Only the tree mounted at /data is the stable, writable place operators should use for SQLite and a custom CSS file in our manifests.

Docker Compose (minimal or Traefik)
Each stack bind-mounts the host directory ${GGHSTATS_HOST_DATA} (or the repo’s data/ fallback when unset) to /data in the container. On the host, put custom-theme.css next to your database directory contents — e.g. ${GGHSTATS_HOST_DATA}/custom-theme.css — so it appears as /data/custom-theme.css in the container. Set GGHSTATS_CUSTOM_CSS=/data/custom-theme.css in ${GGHSTATS_HOST_DATA}/.env. You do not need an extra volume line for the theme if it lives under that same host path.

docker run (single container)
Mount a host directory the same way: -v /path/on/host:/data, store the .css file under /path/on/host/, then set GGHSTATS_CUSTOM_CSS=/data/your-theme.css (see run/docker/README.md).

Helm / Kubernetes
When persistence.enabled is true, the chart mounts a PVC at /data (see templates/deployment.yaml and values.yamlpersistence). env.customCss must be a path on that PVC, e.g. /data/custom-theme.css, not somewhere under the read-only container root (the chart uses readOnlyRootFilesystem: true). The SQLite file from env.dbPath is also under /data.
If persistence.enabled is false, /data is still a writable emptyDir, but anything you put there (including a theme file) is lost when the Pod is deleted unless you repopulate it.
After install, common options are kubectl cp a file into the running pod’s /data/ (fix ownership/read bits for UID 1000 if your storage driver requires it), or your own Job / initContainer / GitOps pattern to populate /data. The chart does not bundle theme files; copy from upstream contrib/themes or supply your own.

Compose / Traefik or minimal (steps)

  1. Pin the image: set GGHSTATS_VERSION=v1.0.1 in ${GGHSTATS_HOST_DATA}/.env (see run/common/.env.example).
  2. Copy a starter from gghstats contrib/themes/ (or write your own) into the host directory that is bind-mounted to /data (same as Persistent data and secrets), e.g. ${GGHSTATS_HOST_DATA}/custom-theme.css.
  3. Set GGHSTATS_CUSTOM_CSS=/data/custom-theme.css in that .env.
  4. Recreate the app container so env and mounts apply: docker compose … up -d (not restart alone if you also changed GGHSTATS_VERSION — see Versioning).

Helm (values)

Set env.customCss to a path under /data (e.g. /data/custom-theme.css) and ensure that file exists on the PVC as described above. Leave env.customCss empty to keep the stock neo-brutalist UI.

Upstream reference: gghstats README — Custom UI theme (optional).

↑ Contents


Repository layout

run/
├── vps-recommended/             # Optional agnostic VPS Ansible (not gghstats install)
├── common/.env.example          # Shared vars for Compose + image tag
├── scripts/                     # compose-stack.sh (docker compose helper)
├── standalone/{linux,macos,windows}/
├── docker/                      # docker run
├── docker-compose/
│   ├── minimal/
│   ├── traefik/
│   └── observability/
└── kubernetes/
    ├── helm/gghstats/           # Helm chart named "gghstats" (app); not the repo name
    └── manifests/
testing/
├── platforms/                   # Ansible: minimal Compose on real VPS (make test-compose-platforms)
├── kind/                        # kind + Helm smoke test docs
└── scripts/                     # test-helm-kind.sh
Makefile                         # release-check, test-compose-platforms, test-helm-kind

Maintainers: make release-check before tagging; optional testing/platforms and make test-helm-kind — see CONTRIBUTING.md.

↑ Contents


Versioning

  • VERSION — semver of this repository (Compose, docs, run/, etc.). When you change it, align the Version badge in this README and (if you keep a release entry) CHANGELOG.md; on main, tag with v<semver> (e.g. v0.1.12). This number is not tied to the Helm chart on every bump.
  • Helm chart (run/kubernetes/helm/gghstats/Chart.yamlversion:) — semver of the chart package published to GitHub Pages / Releases. Bump version: when the chart itself changes (templates, values, etc.). It may lag behind VERSION (e.g. repo 0.2.0, chart 0.1.5 until you edit the chart). chart-releaser may skip publishing if run/kubernetes/helm/ did not change — expected for docs-only repo releases.
  • Chart.yamlappVersiongghstats application / image line; align with gghstats releases when you bump the deployed image story.
  • GGHSTATS_VERSION in ${GGHSTATS_HOST_DATA}/.env (or the env file you pass to Compose) — container image tag on GHCR (gghstats releases), not the same as VERSION.
  • Optional (gghstats ≥ 0.6.0): GGHSTATS_DEFAULT_LOCALE and GGHSTATS_ENABLED_LOCALES for dashboard UI languages (API/CLI stay English). See gghstats — Web UI languages (i18n) and run/common/.env.example.

Upgrading the app image: (1) Set GGHSTATS_VERSION in ${GGHSTATS_HOST_DATA}/.env. (2) Pull the image from GHCR (so the new tag exists locally). (3) Run up -d so Compose recreates the service with that tag — e.g. ./run/scripts/compose-stack.sh traefik pull then … traefik up -d, or … traefik up -d --pull always. Optionally traefik down before pull/up if you want everything stopped first. docker compose restart / compose-stack.sh … restart only restart the existing container — they do not apply a new image tag. See run/common/.env.example.

Validate gghstats image upgrade (Compose / Traefik)

Use this checklist from the repository clone root after editing GGHSTATS_VERSION (Traefik + gghstats stack; same idea for minimal with minimal instead of traefik in the commands below).

  1. GGHSTATS_HOST_DATA points at the directory that contains your .env (same as for normal Compose).
  2. Confirm the tag in the env file:
    grep GGHSTATS_VERSION "${GGHSTATS_HOST_DATA}/.env"
  3. Confirm Compose resolves the expected image line (no need to start containers):
    docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" \
      -f run/docker-compose/traefik/docker-compose.yml config \
      | grep -E 'image:|gghstats'
    You should see ghcr.io/hrodrig/gghstats:<your-tag> (e.g. v1.0.1).
  4. Pull and recreate the service (do not rely on restart alone):
    ./run/scripts/compose-stack.sh traefik pull
    ./run/scripts/compose-stack.sh traefik up -d
    Or one step:
    ./run/scripts/compose-stack.sh traefik up -d --pull always
  5. Confirm the running container uses that image:
    docker ps --format 'table {{.Names}}\t{{.Image}}' | grep gghstats
  6. Smoke check — UI: open GGHSTATS_HOSTNAME in the browser (HTTPS if Traefik + ACME). The gghstats web UI shows the app version in the sidebar (must match the release you deployed).

Optional — observability stack: if you run Prometheus / Grafana / Loki on the same host, after the Traefik stack is healthy run ./run/scripts/compose-stack.sh --traefik observability ps and open your Grafana URL (e.g. GF_SERVER_ROOT_URL / GRAFANA_HOSTNAME) to confirm dashboards load. Observability images are separate from gghstats; they only need a full cycle if you change those compose images.

↑ Contents


Community and policies

Document Purpose
CHANGELOG.md Release history and notable changes to this repository (manifests, docs, layout).
CONTRIBUTING.md How to open issues/PRs, branch policy (developmain), and checks before submitting.
CODE_OF_CONDUCT.md Community standards (Contributor Covenant).
SECURITY.md How to report security vulnerabilities responsibly.
AGENTS.md Guidelines for AI coding agents (Cursor, etc.) working in this repo.

Application issues (bugs, features in the Go app or UI) belong in gghstats — not here.

↑ Contents


Community standards

Thanks for self-hosting gghstats with these manifests. We would love to hear how easy or difficult it was to run gghstats self-hosted (Compose, Helm, docker run, observability, or anything in run/). Share feedback in GitHub Issues or, if enabled for this repository, Discussions.

↑ Contents


License

MIT — see LICENSE.

↑ Contents

About

Deployment manifests and docs for self-hosted gghstats (Compose, Helm, observability)

Resources

Code of conduct

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages