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.
- Pick a path
- Standalone binary
- Docker single container
- Docker Compose minimal
- Docker Compose Traefik HTTPS
- Observability optional
- Kubernetes Helm
- Persistent data and secrets
- Repository layout
- Versioning
- Community and policies
- Community standards
- License
| You want… | Section |
|---|---|
| Binary only (no Docker) | Standalone binary |
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 |
| Kubernetes | Kubernetes Helm |
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.
Goal: run the app without Docker.
- Download a release asset for your OS/arch from gghstats Releases.
- Extract the binary, then:
export GGHSTATS_GITHUB_TOKEN=ghp_xxx # classic PAT with repo scope as needed
./gghstats serveCheck: open http://localhost:8080 (or the port you set).
Stop: Ctrl+C. SQLite path depends on your config — see gghstats .env.example.
More: run/standalone/linux · macos · windows
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:v0.1.3Use 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 gghstatsMore: run/docker/README.md
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 -dCheck: 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 downMore: run/docker-compose/minimal/README.md
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 -dCheck: 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 downMore: run/docker-compose/traefik/README.md
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):
-
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
-
DNS: point
GRAFANA_HOSTNAMEto this host (A/AAAA or CNAME), same idea asGGHSTATS_HOSTNAMEfor the main app. -
Start the stack with both Compose files (the second file adds Traefik labels only; it does not add another Traefik container). Use both
-flines on everyup/pull/downthat 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 -dLocal / 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 -dCheck / 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 -vRecommended: 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.6.tgz); Git tags for this repo use v<semver> (e.g. v0.1.7) 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.yamlGitHub 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_TOKENThen 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.yamlSee 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 gghstatsMore: run/kubernetes/helm/gghstats/README.md · run/kubernetes/manifests
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 (./run/scripts/compose-stack.sh --help).
run/
├── 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/
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; onmain, tag withv<semver>(e.g.v0.2.0). This number is not tied to the Helm chart on every bump.- Helm chart (
run/kubernetes/helm/gghstats/Chart.yaml→version:) — semver of the chart package published to GitHub Pages / Releases. Bumpversion:when the chart itself changes (templates,values, etc.). It may lag behindVERSION(e.g. repo0.2.0, chart0.1.5until you edit the chart). chart-releaser may skip publishing ifrun/kubernetes/helm/did not change — expected for docs-only repo releases. Chart.yaml→appVersion— gghstats application / image line; align with gghstats releases when you bump the deployed image story.GGHSTATS_VERSIONin${GGHSTATS_HOST_DATA}/.env(or the env file you pass to Compose) — container image tag on GHCR (gghstats releases), not the same asVERSION.
| Document | Purpose |
|---|---|
| CHANGELOG.md | Release history and notable changes to this repository (manifests, docs, layout). |
| CONTRIBUTING.md | How to open issues/PRs, branch policy (develop → main), 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.
- License:
LICENSE - Contributing:
CONTRIBUTING.md - Code of conduct:
CODE_OF_CONDUCT.md - Security policy:
SECURITY.md - Changelog:
CHANGELOG.md - Agent guidelines:
AGENTS.md
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.
MIT — see LICENSE.
