Shelby Sites — Static Website Hosting
Deploy static websites to immutable Shelby blobs — a Vercel-like deploy flow on decentralized storage. Every file in your ZIP becomes a content-addressed blob on Shelbynet, served at a public URL with full SHA-256 verification.
Quick Start
# 1. Create a site
curl -X POST https://your-app.vercel.app/api/sites \
-H "Content-Type: application/json" \
-H "X-Provenode-Token: $TOKEN" \
-d '{"name": "My Portfolio", "slug": "my-portfolio"}'
# 2. Zip your static build and deploy
zip -r site.zip dist/
curl -X POST https://your-app.vercel.app/api/sites/$SITE_ID/deploy \
-H "X-Provenode-Token: $TOKEN" \
-F file=@site.zip
# 3. Visit your site
open https://your-app.vercel.app/s/my-portfolio
How It Works
ZIP upload ──▶ unpack + MIME sniff ──▶ per-file SHA-256
│ │
▼ ▼
Shelby blob per file manifest blob (__manifest.json)
sites/<slug>/<depId>/<path> sites/<slug>/<depId>/__manifest.json
- Upload — POST a ZIP (or single HTML file) to
/api/sites/:id/deploy - Unpack — the server extracts entries, skips junk (
__MACOSX/,.DS_Store), and sniffs MIME types per extension - Store — each file is uploaded as an immutable Shelby blob at
sites/<slug>/<deploymentId>/<path>; a__manifest.jsonblob records every file's path, size, SHA-256, and objectId - Serve —
/s/:slug/*resolves the latest deployment's manifest, fetches the blob from Shelby, and streams it with the correctContent-Typeand a 60s edge cache - SPA fallback — unknown paths fall back to
path.html, thenpath/index.html, thenindex.html
Each deployment is an immutable snapshot — like a Vercel preview. Redeploy to the same slug to update the public URL.
API Reference
| Method | Route | Description |
|---|---|---|
GET | /api/sites | List all sites |
POST | /api/sites | Create site { name, slug?, description?, framework? } |
GET | /api/sites/:idOrSlug | Get site + deployment history |
DELETE | /api/sites/:idOrSlug | Delete site + all deployments |
POST | /api/sites/:id/deploy | Deploy ZIP / HTML / JSON files |
POST | /api/sites/:id/rollback | Promote an existing deployment to production |
GET | /api/sites/:id/deployments | List deployments |
GET | /api/sites/:id/serve/<path> | Serve latest deployment file |
GET | /api/sites/:id/preview/:depId/<path> | Preview a specific deployment |
Deploy Request Formats
Multipart ZIP (recommended):
-F file=@site.zip # ZIP containing index.html at root
-F entry=index.html # optional entry override
Single HTML file:
-F file=@index.html # becomes index.html automatically
JSON body:
{
"html": "<h1>Hello</h1>"
}
or
{
"files": [
{ "path": "index.html", "contentBase64": "..." },
{ "path": "assets/app.js", "contentBase64": "..." }
]
}
Instant Rollback
Every deployment is an immutable snapshot whose blobs stay on Shelby. Rolling back therefore does not re-upload anything — it only moves the production pointer, so it completes in milliseconds and is itself reversible.
In the console: Sites → your site → Deployments → Rollback to this. The live deployment is marked LIVE.
curl -X POST https://your-app.vercel.app/api/sites/$SITE_ID/rollback \
-H "Content-Type: application/json" \
-H "X-Provenode-Token: $TOKEN" \
-d '{"deploymentId": "dep_abc123"}'
Response includes the promoted deployment and the public URL. Each rollback is written to the audit log (site.rolled_back), dispatched to webhooks, and appended to the site's rollbackHistory (last 20 entries).
Limits
| Limit | Value |
|---|---|
| Upload size (ZIP) | 50 MB |
| Total per deployment | 40 MB |
| Files per deployment | 200 |
| Max single file | 10 MB |
| Blob lifetime | 90 days (auto-renewed by cron) |
Push-to-Deploy from GitHub
Deploy automatically on every git push — GitHub Actions builds, Provenode hosts on Shelby.
1. Create a deploy key
In the console: Sites → your site → GitHub → Create deploy key. Or via API:
curl -X POST https://your-app.vercel.app/api/sites/$SITE_ID/keys \
-H "X-Provenode-Token: $TOKEN"
# → { "token": "pvnd_..." } # shown ONCE — store it now
The key is site-scoped: it can only deploy that one site and is revoked by deleting it. Add it to your repo as the PROVENODE_DEPLOY_KEY secret (Settings → Secrets and variables → Actions).
2. Add the workflow
Save as .github/workflows/deploy.yml (the Sites console generates this pre-filled):
name: Deploy to Provenode
on:
push:
branches: [main]
pull_request:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # comments preview URLs on PRs
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- name: Build
run: npm ci && npm run build
# If your build outputs to dist/, zip that folder:
# cd dist && zip -r ../site.zip .
- name: Package
run: zip -r site.zip .
- name: Deploy to Provenode
env:
PROVENODE_KEY: ${{ secrets.PROVENODE_DEPLOY_KEY }}
run: |
curl -sf -X POST "$PROVENODE_URL/api/sites/$SITE_ID/deploy" \
-H "Authorization: Bearer $PROVENODE_KEY" \
-F file=@site.zip -o deploy_result.json
cat deploy_result.json
- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
URL=$(node -e "console.log(JSON.parse(require('fs').readFileSync('deploy_result.json')).previewUrl)")
gh pr comment "$PR_NUMBER" --body "Shelby preview: $PROVENODE_URL$URL"
3. Push
main→ deploys to production at/s/your-slug- Pull requests → immutable preview deployment, URL commented on the PR
- Redeploying to the same slug atomically flips production to the new snapshot
CI auth model
| Credential | Header | Scope |
|---|---|---|
Admin token (DEPLOY_SECRET) | X-Provenode-Token | all sites |
Site deploy key (pvnd_...) | Authorization: Bearer pvnd_... | one site only |
Key routes: POST /api/sites/:id/keys (create, returns token once) · GET /api/sites/:id/keys (list, masked) · DELETE /api/sites/:id/keys/:token (revoke — accepts masked prefix).
Verification
Every deployment manifest is itself a Shelby blob. To audit a live site:
# Fetch the manifest from Shelby
curl "$SHELBY_RPC/shelby/v1/blobs/$ADDRESS/sites/my-portfolio/$DEP_ID/__manifest.json"
# Check any served file against its recorded SHA-256
curl -s https://your-app.vercel.app/s/my-portfolio/app.js | sha256sum
A hash mismatch between served content and the manifest means the site was tampered with — this is impossible on traditional CDNs where the origin can be silently edited.
Frameworks
| Framework | Build command | ZIP root |
|---|---|---|
| Vite | vite build | dist/ |
| Next.js | next build (static export) | out/ |
| Astro | astro build | dist/ |
| CRA | npm run build | build/ |
| Plain HTML | — | . |
Console
The Sites page in the console (/app/sites) provides:
- Site creation with automatic slug preview
- Drag-and-drop ZIP deployment
- Deployment history with file counts and sizes
- Inline iframe preview of the live site
- Copyable public URLs
FAQ
Is this a Neon replacement? No. Shelby is blob storage, not Postgres. Use Shelby Sites for your frontend and immutable assets; pair it with Neon/Upstash for mutable state.
What happens after 90 days? The expiry cron re-anchors manifests before expiry and emails a warning 7 days ahead. Sites with traffic renew automatically.
Custom domains?
Roadmap — a CNAME-based gateway with automatic TLS is planned. Today every site lives at /s/<slug>.