Self-Hosting

Run your own shieldcn badge engine with Docker.

Run your own shieldcn badge engine — no reliance on shieldcn.dev infrastructure.

Quick Start

Create a docker-compose.yml file:

services:
  engine:
    image: ghcr.io/jal-co/shieldcn/engine:latest
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://shieldcn:shieldcn@postgres:5432/shieldcn
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

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

volumes:
  pgdata:

Then run:

docker compose up -d

# Verify it's working
curl http://localhost:3000/api/health/
curl http://localhost:3000/badge/self--hosted-green.svg

Badges are served at http://localhost:3000. Use them the same way as shieldcn.dev — just swap the domain:

![npm](http://localhost:3000/npm/v/react.svg)
![stars](http://localhost:3000/github/stars/facebook/react.svg)

All query parameters work — variant, size, mode, theme, logo, color, gradient, etc.

Environment Variables

VariableRequiredDescription
DATABASE_URLPostgreSQL connection string
GITHUB_TOKENSingle GitHub token fallback (5,000 req/hr)
GITHUB_OAUTH_CLIENT_IDOAuth app for token pool
GITHUB_OAUTH_CLIENT_SECRETOAuth app secret
YOUTUBE_API_KEYYouTube Data API key
UPSTASH_REDIS_REST_URLUpstash Redis for persistent cache
UPSTASH_REDIS_REST_TOKENUpstash Redis token
NEXT_PUBLIC_URLEngine base URL (default: http://localhost:3000)

GitHub Badges

Without a GITHUB_TOKEN, GitHub badges are limited to 60 requests/hour. Add a token to raise this to 5,000/hour.

GitHub badges hit the GitHub API which has a rate limit of 60 requests/hour without authentication. To raise this to 5,000/hour, add a GITHUB_TOKEN to your environment:

services:
  engine:
    image: ghcr.io/jal-co/shieldcn/engine:latest
    environment:
      - DATABASE_URL=postgresql://shieldcn:shieldcn@postgres:5432/shieldcn
      - GITHUB_TOKEN=ghp_your_token_here

Create a token at github.com/settings/tokens — no scopes needed (public data only).

For higher throughput, set up the token pool to distribute requests across multiple tokens.

What's Included

The self-hosted engine serves all badge endpoints:

  • All 30+ badge providers (npm, GitHub, PyPI, Docker Hub, etc.)
  • SVG, PNG, and JSON output formats
  • All variants, themes, gradients, and customization options
  • GitHub token pool with OAuth flow
  • Memo badges (persistent badges with Bearer auth)
  • Health check at /api/health/

What's Not Included

The engine does not include:

  • Documentation site
  • Gallery, showcase, or landing pages
  • Analytics (OpenPanel)
  • shadcn component registry

These are only in the packages/web site at shieldcn.dev.

Token Pool Setup

The token pool distributes GitHub API requests across multiple OAuth tokens to stay under rate limits.

Option 1: Single Token (Simple)

Set GITHUB_TOKEN to a personal access token with no scopes (public data only).

Option 2: OAuth Token Pool (Scalable)

  1. Create a GitHub OAuth App at github.com/settings/developers
  2. Set the callback URL to {NEXT_PUBLIC_URL}/api/auth/github/callback
  3. Set GITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET
  4. Users can authorize at {NEXT_PUBLIC_URL}/api/auth/github

Reverse Proxy

If you want to serve badges from a custom domain:

Nginx

server {
    listen 80;
    server_name badges.example.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Caddy

badges.example.com {
    reverse_proxy localhost:3000
}

Upgrading

docker compose pull
docker compose up -d

Data Source

The engine connects to the same upstream APIs as shieldcn.dev — npm registry, GitHub API, Docker Hub, etc. No data is proxied through shieldcn.dev.