Making A Website For Tracking My GitHub Stats
GitHub only keeps 14 days of traffic data. If you don’t capture it, it’s gone. I got tired of losing that data, so I built a self-hosted dashboard that collects stats daily and keeps them forever. I also got tired of running reports every day.
It tracks stars, forks, traffic, clones, and release download counts across multiple repos, and displays everything on a single page with interactive charts. The whole thing is a Python script, a static HTML file, and a cron job. No framework, no database, no build step.
You can see my live version at stats.erikdarling.com, where I track PerformanceMonitor, PerformanceStudio, and DarlingData.
All the code is on GitHub: erikdarlingdata/github-stats-dashboard
What you need
- A Linux server (any cheap VPS — mine runs on a $5/month Hetzner box alongside other stuff)
- Python 3 (no pip packages, just the standard library)
- Nginx
- A GitHub fine-grained personal access token
Step 1: Create a GitHub token
Go to GitHub → Settings → Developer settings → Fine-grained tokens → Generate new token.
Scope it to only the repos you want to track. Under repository permissions, you need:
- Metadata: Read-only (selected by default)
- Administration: Read-only (required for the traffic and clones API)
Save the token somewhere safe. You’ll put it on your server in a moment.
Step 2: Set up the server
SSH into your server and install nginx and certbot:
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
Save your GitHub token:
echo "ghp_your_token_here" | sudo tee /etc/github-stats-token > /dev/null
sudo chmod 600 /etc/github-stats-token
Create the web root and grab the dashboard files:
sudo mkdir -p /var/www/stats/data
# Clone the repo (or just download the two files you need)
git clone https://github.com/erikdarlingdata/github-stats-dashboard.git /tmp/stats-dashboard
sudo cp /tmp/stats-dashboard/index.html /var/www/stats/
sudo cp /tmp/stats-dashboard/collect.py /opt/github-stats-collect.py
sudo chmod +x /opt/github-stats-collect.py
Step 3: Configure the collector
The collector needs to know which repos to track. You can either edit the DEFAULT_CONFIG dict at the top of collect.py, or create a config file:
sudo tee /etc/github-stats.json > /dev/null <<'EOF'
{
"data_dir": "/var/www/stats/data",
"token_file": "/etc/github-stats-token",
"repos": [
"your-org/repo-one",
"your-org/repo-two",
"your-org/repo-three"
]
}
EOF
Replace the repo list with your own. Use the full owner/repo format.
Step 4: Configure the dashboard
Open /var/www/stats/index.html and find the REPO_DISPLAY object near the top of the script block:
const REPO_DISPLAY = {
"repo-one": { short: "R1", color: "#58a6ff" },
"repo-two": { short: "R2", color: "#3fb950" },
"repo-three": { short: "R3", color: "#d2a8ff" },
};
The keys must match the repo names from your config (the part after the /). The short value is what shows up in the tab buttons and chart legends. Pick whatever colors you like.
Step 5: Set up nginx and SSL
Point your domain’s DNS A record to your server’s IP address, then configure nginx:
sudo tee /etc/nginx/sites-available/stats.example.com > /dev/null <<'EOF'
server {
listen 80;
server_name stats.example.com;
root /var/www/stats;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /data/ {
add_header Cache-Control "no-cache";
}
}
EOF
sudo ln -s /etc/nginx/sites-available/stats.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Then get a free SSL certificate:
sudo certbot --nginx -d stats.example.com
Step 6: Run the first collection and schedule daily runs
# First run
python3 /opt/github-stats-collect.py --config /etc/github-stats.json
# Schedule daily at 6:15 AM UTC
(crontab -l 2>/dev/null; echo '15 6 * * * /usr/bin/python3 /opt/github-stats-collect.py --config /etc/github-stats.json >> /var/log/github-stats.log 2>&1') | crontab -
Visit your domain. You should see stat cards, charts, and a release download table. The charts will fill in over the coming days as snapshots accumulate. If you want it to run hourly, you can do this instead:
# Schedule hourly at :15 past the hour
(crontab -l 2>/dev/null; echo '15 * * * * /usr/bin/python3 /opt/github-stats-collect.py --config
/etc/github-stats.json >> /var/log/github-stats.log 2>&1') | crontab -
How it works
The collector is a single Python file with zero external dependencies. It calls the GitHub API for each repo and writes three JSON files:
data/YYYY-MM-DD.json— the full daily snapshot, kept foreverdata/history.json— a rolling summary with one entry per day, used by the chartsdata/current.json— the latest snapshot, used by the stat cards and traffic/clone bar charts
The dashboard is a single static HTML file. No build tools, no framework. It fetches the JSON files and renders everything client-side with Chart.js. When you click a repo tab, it swaps the stat cards and per-repo charts without reloading the page.
The daily traffic and clone charts show the 14-day rolling window from GitHub’s API. The stars and downloads charts plot from history.json, so they grow over time as you collect more snapshots.
Things worth knowing
Start collecting early. You can only capture traffic data that GitHub still has. If you set this up today, you get today’s 14-day window. Tomorrow you get tomorrow’s. But you can’t backfill what’s already gone.
Download counts are cumulative. GitHub reports total all-time downloads per release asset. The collector stores the current total each day, so the dashboard can plot the growth over time.
Safe to re-run. If you run the collector multiple times in a day, it replaces that day’s entry in history.json rather than duplicating it.
No database. Everything is flat JSON files. Easy to back up (just tar the data directory), easy to inspect, easy to move to a new server.
The traffic API requires admin-level access. With fine-grained tokens, that means the Administration: Read-only permission. Without it, you’ll still get stars, forks, and downloads — just not traffic or clones.
The code
Everything is at erikdarlingdata/github-stats-dashboard. MIT licensed. Four files that matter:
collect.py— the collector scriptindex.html— the dashboardconfig.example.json— example confignginx.example.conf— example nginx config
Fork it, change the repos, deploy it, done.
Going Further
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. Blog readers get 25% off the Everything Bundle — over 100 hours of performance tuning content. Need hands-on help? I offer consulting engagements from targeted investigations to ongoing retainers. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.
Nice one, Erik! And so simple. Amazing traffic, btw.
Why thank you! Hope you find it easy enough to use on your site.