Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Dependencies and build outputs (rebuilt inside the image)
**/node_modules
**/build
**/dist
**/coverage
**/.turbo

# Vercel-specific scaffolding, not used in the container image
**/.vercel
apps/backend/_dot_vercel_copy
apps/backend/.vercelignore
apps/backend/vercel.json
vercel-preparation.sh

# VCS and tooling
.git
.github
.husky
.vscode
.devcontainer

# Local env and logs
**/.env
**/.env.*
**/*.log
npm-debug.log*

# Docs and misc (not needed at runtime)
**/*.md
docs

# The image files themselves
Dockerfile
.dockerignore
docker-compose.yml
79 changes: 79 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# syntax=docker/dockerfile:1

# ------------------------------------------------------------------------------
# github-stats-extended -- container image for self-hosting the backend.
#
# This image runs the backend as an HTTP server (apps/backend/express.js),
# so the cards can be served anywhere, not only on Vercel. The design keeps
# room for a future static-SVG generation mode (see the issue for context):
# additional entrypoints can be added as separate CMD targets without changing
# the build stages below.
# ------------------------------------------------------------------------------

ARG NODE_VERSION=24
ARG PNPM_VERSION=10.34.1

# ------------------------------------------------------------------------------
# Stage 1: base -- pin Node and enable pnpm via corepack.
# ------------------------------------------------------------------------------
FROM node:${NODE_VERSION}-slim AS base
ARG PNPM_VERSION
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
WORKDIR /app

# ------------------------------------------------------------------------------
# Stage 2: build -- install the whole workspace and build the core package.
# The backend itself runs from source (.js), so only packages/core needs a
# compile step (tsc -> build/). A pnpm store cache mount speeds up rebuilds.
# ------------------------------------------------------------------------------
FROM base AS build

# Copy the manifests first so the dependency layer can be cached independently
# of source changes.
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY turbo.json tsconfig.base.json tsconfig.json ./
COPY packages/core/package.json packages/core/
COPY apps/backend/package.json apps/backend/
COPY apps/frontend/package.json apps/frontend/

# Full install (dev deps included) so turbo/tsc are available for the build.
# --frozen-lockfile keeps the image reproducible against the committed lockfile.
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile

# Bring in the sources and build the core package.
COPY . .
RUN pnpm run build:packages

# Produce a self-contained, production-only bundle for the backend. `pnpm deploy`
# resolves the `workspace:^` link to packages/core and copies everything the
# backend needs into /app/deploy, with no dev dependencies.
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm --filter=@stats-organization/github-readme-stats-backend deploy \
--prod --legacy /app/deploy

# ------------------------------------------------------------------------------
# Stage 3: runtime -- minimal image running the backend as a non-root user.
# ------------------------------------------------------------------------------
FROM node:${NODE_VERSION}-slim AS runtime
ENV NODE_ENV=production
# The server reads PORT (falling back to 9000). We default it to 9000 and expose
# that port; override at run time with -e PORT=... if needed.
ENV PORT=9000
WORKDIR /app

# node:*-slim already ships an unprivileged `node` user. Use it.
COPY --from=build --chown=node:node /app/deploy ./
USER node

EXPOSE 9000

# Lightweight liveness probe against the built-in status endpoint.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||9000)+'/api/status/up').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"

# HTTP-server mode. A future static-generation mode can be added as an
# alternative command (e.g. CMD ["node", "generate-svg.js"]).
CMD ["node", "express.js"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ GitHub-Stats-Extended aims to be fully compatible with github-readme-stats. For
The [GitHub-Stats-Extended Wizard](https://github-stats-extended.vercel.app/frontend) offers some essential customization options. For more advanced customization check out the [advanced documentation](docs/advanced_documentation.md).

# Run It Yourself
If you want to run GitHub-Stats-Extended on your own, there are two main deployment options: you can use [github-readme-stats-action](https://github.com/stats-organization/github-readme-stats-action) to generate cards in your own GitHub Actions workflow. Or you can self-host GitHub-Stats-Extended on Vercel.
If you want to run GitHub-Stats-Extended on your own, there are a few deployment options: you can use [github-readme-stats-action](https://github.com/stats-organization/github-readme-stats-action) to generate cards in your own GitHub Actions workflow, self-host GitHub-Stats-Extended on Vercel, or run it as a container with [Docker](docs/docker.md).

See [Run It Yourself](docs/deploy.md) for detailed instructions.
See [Run It Yourself](docs/deploy.md) for detailed instructions, or [Running with Docker](docs/docker.md) for the container setup.

# Acknowledgements
This project is based on [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). On top of that project's functionality GitHub-Stats-Extended adds several new features and improvements. See [Fork Information](docs/fork.md) for a list of changes. The frontend added to GitHub-Stats-Extended is based on [GitHub Trends](https://github.com/avgupta456/github-trends). Big thanks to [@anuraghazra](https://github.com/anuraghazra), [@avgupta456](https://github.com/avgupta456), [@rickstaa](https://github.com/rickstaa), [@qwerty541](https://github.com/qwerty541) and everyone else who worked on these projects! ❤️
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"private": true,
"main": "index.js",
"scripts": {
"start": "node express.js",
"test": "vitest",
"test:update:snapshot": "vitest -u",
"test:e2e": "vitest --config vitest.config.e2e.ts",
Expand All @@ -17,13 +18,13 @@
},
"devDependencies": {
"axios-mock-adapter": "2.1.0",
"express": "5.2.1",
"jsdom": "catalog:default",
"vitest": "catalog:default"
},
"dependencies": {
"@stats-organization/github-readme-stats-core": "workspace:^",
"axios": "catalog:default",
"express": "5.2.1",
"pg": "^8.22.0"
}
}
66 changes: 66 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# github-stats-extended -- self-hosting via Docker Compose.
#
# Minimal usage (no database, cards only):
# PAT_1=ghp_your_token docker compose up backend
#
# With the optional Postgres backend (enables request analytics, the
# authenticate/user-access endpoints, and repeat-recent):
# PAT_1=ghp_your_token docker compose --profile with-db up
#
# All configuration is passed through environment variables. At least one
# PAT_* variable (a GitHub personal access token) is required to render cards.

services:
backend:
build:
context: .
dockerfile: Dockerfile
image: github-stats-extended:local
ports:
- "${PORT:-9000}:9000"
environment:
# --- Required: at least one GitHub token ---
PAT_1: ${PAT_1:?set PAT_1 to a GitHub personal access token}
# Add more tokens to spread the rate limit (PAT_2, PAT_3, ...):
# PAT_2: ${PAT_2:-}

# --- Optional core configuration (see packages/core config) ---
WHITELIST: ${WHITELIST:-}
GIST_WHITELIST: ${GIST_WHITELIST:-}
EXCLUDE_REPO: ${EXCLUDE_REPO:-}
FETCH_MULTI_PAGE_STARS: ${FETCH_MULTI_PAGE_STARS:-}

# --- Optional Postgres connection. Leave unset to run without a DB. ---
# When using the `with-db` profile below, this points at the db service.
POSTGRES_URL: ${POSTGRES_URL:-}
restart: unless-stopped

# Variant that wires the backend to the bundled Postgres service.
backend-with-db:
extends:
service: backend
profiles: ["with-db"]
environment:
POSTGRES_URL: postgres://postgres:postgres@db:5432/stats
depends_on:
db:
condition: service_healthy

db:
image: postgres:17-alpine
profiles: ["with-db"]
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: stats
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d stats"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

volumes:
pgdata:
83 changes: 83 additions & 0 deletions docs/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Running with Docker

The backend can run as a standalone HTTP server inside a container, so you can
host the cards anywhere instead of relying on Vercel. This is useful for
self-hosting, air-gapped environments, or local testing.

## What the image does

The image builds the `core` package and runs `apps/backend/express.js`, which
starts an HTTP server that answers the same routes as the Vercel deployment
(`/api`, `/api/pin`, `/api/top-langs`, `/api/gist`, `/api/wakatime`, and the
status/auth endpoints). The server listens on `0.0.0.0` and reads the `PORT`
variable, falling back to `9000`.

A database is **optional**. Without `POSTGRES_URL`, all database-backed features
(request analytics, `repeat-recent`, the authenticate / user-access flow) are
skipped and card rendering works normally.

## Requirements

- Docker (with BuildKit, the default in current Docker versions).
- At least one GitHub personal access token, exposed as `PAT_1`. Additional
tokens (`PAT_2`, `PAT_3`, ...) spread the requests across the GitHub API rate
limit.

## Quick start (Docker)

Build the image:

```bash
docker build -t github-stats-extended:local .
```

Run it:

```bash
docker run --rm -p 9000:9000 -e PAT_1=ghp_your_token github-stats-extended:local
```

Then request a card:

```bash
curl "http://localhost:9000/api?username=anuraghazra"
```

## Quick start (Docker Compose)

Cards only, no database:

```bash
PAT_1=ghp_your_token docker compose up backend
```

With the bundled Postgres (enables analytics and the auth endpoints):

```bash
PAT_1=ghp_your_token docker compose --profile with-db up
```

## Configuration

All configuration is passed through environment variables.

| Variable | Required | Description |
| ------------------------ | -------- | ------------------------------------------------------------------ |
| `PAT_1`, `PAT_2`, ... | Yes | GitHub personal access tokens. At least one is required. |
| `PORT` | No | HTTP port. Defaults to `9000`. |
| `POSTGRES_URL` | No | Postgres connection string. Unset means the app runs without a DB. |
| `WHITELIST` | No | Comma-separated list of allowed usernames. |
| `GIST_WHITELIST` | No | Comma-separated list of allowed gist ids. |
| `EXCLUDE_REPO` | No | Comma-separated list of repositories to exclude. |
| `FETCH_MULTI_PAGE_STARS` | No | Enables multi-page star counting. |

## Health check

The image ships a `HEALTHCHECK` that polls `/api/status/up`. You can inspect it
with `docker ps` (the `STATUS` column shows `healthy` once the server is up).

## Future: static SVG generation

The image is structured so a static-generation mode can be added later as an
alternative command, without changing the build stages. Such a mode would render
cards to `.svg` files that can be served by any static file host.
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.