Self-hosting
Three depths of self-hosting, depending on how much of SkillOx you want in-house. The scanner engine is Apache-2.0, every dependency is OSS-friendly, and the full stack runs on a single Hetzner CX22 box.
Three depths
- CLI-only — install one binary, scan local SKILL.md files, never talk to skillox.io. The simplest deployment.
- Scanner-as-library — embed
@skillox/scannerin your own Node app. Same engine the hosted service runs; no telemetry. - Full stack — run the web app + API + worker + Postgres + Redis on your own infra. Sign in with your own GitHub OAuth app, point the CLI at your endpoint, mirror the public catalog or skip the crawler entirely.
1. CLI-only
npm i -g skillox # Scan a single SKILL.md skillox audit ./skills/my-skill/SKILL.md # Scan everything under a directory skillox audit ./skills/ # Lint frontmatter only (no network calls) skillox lint ./skills/my-skill/SKILL.md
The CLI runs the scanner locally. The only network traffic is the optional skillox install command (which fetches the skill from its source URL — coming soon). For air-gapped environments:
# In an environment with no outbound network: SKILLOX_OFFLINE=1 skillox audit ./skills/
The full ruleset + grading thresholds are documented at /docs/rules + /docs/grading. Both run in the local binary exactly as they run in the hosted scanner.
2. Scanner-as-library
Embed the scanner in your own Node app. Useful when you have a custom skill format wrapper or you want the audit to run alongside other analysis in the same process.
import { scan } from '@skillox/scanner';
const content = await Deno.readTextFile('./SKILL.md');
const result = await scan({ content, repoMeta: null });
console.log(result.grade, result.score);
for (const f of result.findings) {
console.log(` [${f.severity}] ${f.title} (line ${f.line})`);
}The @skillox/scanner package has zero runtime deps beyond a markdown parser and is published to your private registry as part of the Apache-2.0 release. Pin against a tag rather than main — new rules ship with each minor version.
3. Full stack
Topology
The minimum production topology — five processes, two stateful services, one reverse proxy:
┌──────────────────┐
│ Cloudflare (TLS) │ optional — bring your own edge
└─────────┬────────┘
│ :443
┌─────────▼────────┐
│ Caddy │ reverse proxy, ACME, gzip/br
└───┬─────────┬────┘
│ │
┌───▼───┐ ┌───▼────┐
│ web │ │ api │ Next.js 16, Hono
│ :3000 │ │ :3001 │
└───────┘ └────┬───┘
│
┌──────▼───────┐ ┌──────────┐
│ Postgres 17 │ │ Redis 7 │
│ + B2 backups │ │ ephemeral│
└──────────────┘ └──────────┘Environment
# .env.local — only the keys the stack actually needs DATABASE_URL=postgresql://skillox:strong-pw@localhost:5432/skillox_prod REDIS_URL=redis://localhost:6379 NEXT_PUBLIC_SITE_URL=https://skillox.your-domain.com NEXT_PUBLIC_API_BASE=https://api.skillox.your-domain.com # Anti-abuse on the public scan endpoint. Use Cloudflare's always-pass dev # token for staging; real site key for prod. TURNSTILE_SECRET_KEY=… # Random 24+ char string. Hashes IPs for rate limiting. IP_HASH_SALT=… # Auth.js v5 — required only if you want sign-in / creator portal AUTH_SECRET=$(openssl rand -base64 32) GITHUB_OAUTH_CLIENT_ID=… GITHUB_OAUTH_CLIENT_SECRET=… # Optional — only set if you want the Pro tier surfaces enabled STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= # Lifts the unauthenticated 60-req/hr GitHub ceiling to 5,000/hr for the # crawler. Use a no-scope PAT. GITHUB_TOKEN=
STRIPE_SECRET_KEY is intentionally optional. When unset, every billing route soft-fails with a 503 response and the pricing surface stays hidden — useful for internal deployments where you don't need the Pro tier surfaces.
Deploy
# 1. Provision a server with at least 4 GB RAM (Hetzner CX22 is what we use) # 2. Clone the source git clone https://git.skillox.io/skillox/skillox.git cd skillox/app # 3. Install pnpm + node 20+ corepack enable pnpm install # 4. Create .env.local (copy the example block above) cp .env.example .env.local $EDITOR .env.local # 5. Run the DB migrations pnpm --filter @skillox/db db:migrate # 6. Build + start under systemd pnpm build sudo cp deploy/systemd/*.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now skillox-web skillox-api skillox-worker # 7. Point Caddy or another reverse proxy at :3000 (web) + :3001 (api)
Caddy is what we use in prod (one binary, automatic Let's Encrypt, gzip + brotli). The repo includes a working deploy/Caddyfile and systemd units for web + api + worker under deploy/systemd/.
Backups
Postgres is the only thing that needs durable backups. Our default is a nightly pg_dump piped through age (client-side encryption) into a Backblaze B2 EU bucket. Logs of the backup job land in the same systemd journal as the worker so failures show up in your standard log aggregator.
Redis state is recoverable from cold (the queue refills from scans.status = 'pending'); no separate backup needed.
Running your own catalog
The crawler is an opt-in script, not a daemon. Trigger it manually:
pnpm --filter @skillox/api crawl -- --source github --limit 5000 pnpm --filter @skillox/api crawl -- --source clawhub --limit 5000 pnpm --filter @skillox/api crawl -- --source skillssh --limit 5000 pnpm --filter @skillox/api crawl -- --source all --limit 10000
Or skip the crawler entirely and rely on creator submissions + manual scans through the public endpoint. The catalog table is populated by whichever ingest path you run — both work.
What you give up self-hosting
- Shared catalog data. Your instance starts empty; run the crawler (or a subset) to populate the catalog.
- Auto-updates to the rule set. Hosted users get new rules + grading-threshold tweaks as soon as we push. Self-hosters pull from
mainon whatever cadence makes sense for your team — usually weekly is fine. - The Report Card's public URL. Your self-hosted Report Cards live on your domain, not
skillox.io/c/[name]. Badges + GitHub Action need pointing at yourapi-base.
Support
Community support via the issue tracker at git.skillox.io. Production support contracts available for Team + Enterprise (see /pricing) including custom rule packs + private channel response SLAs.
hello@skillox.io — we keep an informal log of deployments and pass relevant security advisories to operators directly when something needs fixing fast.