Run your own shieldcn badge engine with Docker.
Run your own shieldcn badge engine — no reliance on shieldcn.dev infrastructure.
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:


All query parameters work — variant, size, mode, theme, logo, color, gradient, etc.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | ✅ | PostgreSQL connection string |
GITHUB_TOKEN | — | Single GitHub token fallback (5,000 req/hr) |
GITHUB_OAUTH_CLIENT_ID | — | OAuth app for token pool |
GITHUB_OAUTH_CLIENT_SECRET | — | OAuth app secret |
YOUTUBE_API_KEY | — | YouTube Data API key |
UPSTASH_REDIS_REST_URL | — | Upstash Redis for persistent cache |
UPSTASH_REDIS_REST_TOKEN | — | Upstash Redis token |
NEXT_PUBLIC_URL | — | Engine base URL (default: http://localhost:3000) |
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.
The self-hosted engine serves all badge endpoints:
/api/health/The engine does not include:
These are only in the packages/web site at shieldcn.dev.
The token pool distributes GitHub API requests across multiple OAuth tokens to stay under rate limits.
Set GITHUB_TOKEN to a personal access token with no scopes (public data only).
{NEXT_PUBLIC_URL}/api/auth/github/callbackGITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET{NEXT_PUBLIC_URL}/api/auth/githubIf you want to serve badges from a custom domain:
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;
}
}
badges.example.com {
reverse_proxy localhost:3000
}
docker compose pull
docker compose up -d
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.