DEV Community

Cover image for gghstats-selfhosted: production-shaped manifests for gghstats
Hermes Rodríguez
Hermes Rodríguez

Posted on

gghstats-selfhosted: production-shaped manifests for gghstats

gghstats-selfhosted: production-shaped manifests for gghstats

You already read gghstats: Keep GitHub traffic past 14 daysgghstats is the small Go service that keeps GitHub traffic history in SQLite instead of losing it after GitHub’s ~14-day window. The app ships binaries, a multi-arch GHCR image, and a focused README.

What lives outside the application repo is everything that answers: how do I run this for real on my box, VPS, or cluster?

That’s gghstats-selfhosted — a separate repository with deployment manifests only: docker run, Docker Compose (minimal, Traefik + HTTPS, optional observability), and a Helm chart for Kubernetes. No Go code here; it stays easy to fork, pin, and diff.

Live app demo (read-only): gghstats.hermesrodriguez.com

What the app looks like

Flat screenshots with a white backdrop, perspective, and soft shadow (local asset pipeline — not in the GitHub repos):

Main dashboard (repository list):
gghstats — main dashboard

Repository detail (charts and tables):
gghstats — repository detail


Why split “app” and “how to run it”?

  • gghstats = releases, security advisories, feature issues, container tags.
  • gghstats-selfhosted = Compose files, Helm templates, env patterns, and docs that change when deployment stories evolve (Traefik labels, persistence, layout under run/).

You can ignore the self-hosted repo forever and still run from GHCR with a one-liner — but if you want opinionated layouts (shared GGHSTATS_HOST_DATA, secrets outside the git clone, optional Prometheus/Grafana/Loki behind the same Traefik network), the split keeps each repo readable.

Who this is for

  • Self-hosters who already run a VPS, Compose, or a small Kubernetes cluster and want a repeatable layout instead of a one-off paste from Stack Overflow.
  • Operators who care about pinning an image tag, a single persistent path for SQLite, and secrets that never live in git — the same reasons larger teams split “app” and “platform,” just with a tiny footprint.

What this layout tries to spare you

  • Wiring Traefik + Let’s Encrypt from scratch on every new service.
  • Stuffing Compose samples, Helm, and env docs into the application repo (noisier release notes, harder security reviews).
  • Forgetting which volume holds gghstats.db when you bump the image six months later.

Context (other approaches)

GitHub’s Traffic view only goes back about 14 days. Other open-source projects chase “GitHub stats” with different goals (dashboards, exporters, hosted analytics). gghstats stays narrow: persist traffic via the API into SQLite, ship one Go binary or GHCR image, and let a separate repo own how you run it. If that trade-off fits you, these manifests are the glue.


What you get in run/

Path Roughly
run/standalone/{linux,macos,windows}/ Notes for the binary-only path
run/docker/ Single-container docker run
run/docker-compose/minimal/ One service, quick VPS
run/docker-compose/traefik/ HTTPS + Let’s Encrypt + edge network for the app
run/docker-compose/observability/ Optional Prometheus / Grafana / Loki (after Traefik)
run/kubernetes/helm/gghstats/ Helm chart gghstats (same name as the app; not the GitHub repo name)
run/kubernetes/manifests/ Plain YAML if you prefer not to use Helm

The table in the README is the fastest way to jump to the flow you want.


Quick starts (copy, adjust, run)

Pick one path. Replace ghp_xxx, host paths, your-github-user/*, image tags, and domains with yours. Pin ghcr.io/hrodrig/gghstats: to a tag that exists on GHCR / releases (example below uses v0.1.2).

GitHub token (scopes and safety)

GGHSTATS_GITHUB_TOKEN must be a Personal Access Token that can reach the repos matched by GGHSTATS_FILTER. Follow gghstats — Token setup:

Token type Scopes / access (summary)
Classic PAT public_repo — enough for public repos only. Use repo if you track private repositories (or use GGHSTATS_INCLUDE_PRIVATE=true).
Fine-grained PAT Grant access to the repositories you need; include whatever repository permissions GitHub requires for the Traffic and repo metadata APIs for those repos (the token wizard lists them per permission).

Safety: do not commit the token, put it in a public gist, or paste it into issues. Prefer env vars, Compose env_file, or Kubernetes Secrets. The app’s startup banner only shows a masked token. If the dashboard is empty after sync, verify filter rules and token scope (see Troubleshooting in the app README).

Binary (no Docker)

Grab a release binary from gghstats Releases, extract, then:

export GGHSTATS_GITHUB_TOKEN=ghp_xxx
./gghstats serve
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8080.

Docker (one container)

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:v0.1.2
Enter fullscreen mode Exit fullscreen mode

Docker Compose (minimal, one service)

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" — at least GGHSTATS_GITHUB_TOKEN, GGHSTATS_VERSION, GGHSTATS_HOST_DATA

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" \
  -f run/docker-compose/minimal/docker-compose.yml up -d
Enter fullscreen mode Exit fullscreen mode

Docker Compose + Traefik (HTTPS, production-shaped)

Needs DNS A/AAAA to this host and 80 / 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" — token, GGHSTATS_HOSTNAME, ACME_EMAIL, GGHSTATS_VERSION, GGHSTATS_HOST_DATA, …

docker compose --env-file "${GGHSTATS_HOST_DATA}/.env" \
  -f run/docker-compose/traefik/docker-compose.yml up -d
Enter fullscreen mode Exit fullscreen mode

Kubernetes (Helm)

helm repo add gghstats https://hrodrig.github.io/gghstats-selfhosted
helm repo update
helm show values gghstats/gghstats > my-values.yaml
# Edit my-values.yaml — e.g. image.tag, persistence, resources; keep githubToken.value empty (PAT goes in the Secret below)

kubectl create namespace gghstats
kubectl create secret generic gghstats-secret -n gghstats \
  --from-literal=github-token=ghp_xxx
helm install gghstats gghstats/gghstats -n gghstats -f my-values.yaml
Enter fullscreen mode Exit fullscreen mode

my-values.yaml: start from helm show values (above) so you inherit defaults and values.schema.json constraints (e.g. resources). Do not put the PAT in that file — use the Secret and leave githubToken.value empty. Details: README — Kubernetes Helm.

Example my-values.yaml fragment — token only in the Secret created above; the chart reads it via githubToken.existingSecret (or default secretName):

# Excerpt — always start from: helm show values gghstats/gghstats > my-values.yaml
image:
  tag: "v0.1.2"

githubToken:
  value: ""
  existingSecret: "gghstats-secret"

resources:
  requests: { cpu: "50m", memory: "128Mi" }
  limits: { cpu: "1", memory: "512Mi" }
Enter fullscreen mode Exit fullscreen mode

Helm chart security (defaults): the workload runs non-root (UID/GID 1000), with readOnlyRootFilesystem: true, capabilities.drop: [ALL], and a RuntimeDefault seccomp profile; SQLite lives under the /data mount and /tmp is a small emptyDir. Adjust only if your image requires it — see the chart README.

Reality check: 0.1.x is still early; pin tags, read CHANGELOG on upgrades, and expect manifests to evolve with releases.


Two repos, one story

How the two repos relate:

  gghstats (app repo)              gghstats-selfhosted (deploy repo)
           |                                      |
           | builds                               | Compose, Helm, run/
           v                                      v
      GHCR image ─────────────┬──────────── Manifests
                              │
                              v
                       your VPS / Kubernetes
Enter fullscreen mode Exit fullscreen mode

How the two repos relate


Links

What Where
Deployment manifests github.com/hrodrig/gghstats-selfhosted
Application github.com/hrodrig/gghstats
Helm index hrodrig.github.io/gghstats-selfhosted/index.yaml
Versioning, contributing, changelog README — Versioning · CONTRIBUTING · CHANGELOG

Closing

If you’re already self-hosting databases, proxies, and dashboards, gghstats is one more small service — and gghstats-selfhosted is the folder structure I wished existed when I wired mine up: copy run/common/.env.example, set GGHSTATS_HOST_DATA, choose Compose or Helm, and keep your PAT out of git.

Questions and PRs welcome on develop; merge and releases follow the repo docs.


Cross-posted from the author’s notes; exact commands, versioning, and release policy are always the repositories linked above.

Disclosure (Dev.to / transparency): The author used AI-assisted editing (e.g. drafting structure, wording, and Markdown) and reviewed and approved the final text. Technical claims are meant to match the linked repositories at publish time; if something drifts, trust the repos and CHANGELOG over this post.

Top comments (0)