Deployment
Hosting the static output and multi-tenant domain routing.
Deployment Guide
Pagenary produces static files that can be served by any web server, CDN, or object storage.
Quick Deploy
# Build a tenant to a specific directory
node scripts/build-tenants.js my-tenant --target /var/www/docs
# The output is ready to serve - no server setup required
Build Options
Custom Output Directory
# Override target for all tenants
node scripts/build-tenants.js --target /var/www/html
# Build specific tenant to custom location
node scripts/build-tenants.js my-docs --target /opt/docs/my-docs
Build from Git Repository
Configure a git source in your registry:
{
"tenants": [
{
"id": "my-docs",
"source": {
"type": "git",
"url": "https://github.com/org/my-docs.git",
"ref": "main",
"path": "docs/"
},
"target": {
"type": "local",
"path": "/var/www/my-docs"
}
}
]
}
Then build:
node scripts/build-tenants.js my-docs
External Registry
Use a registry file from any location:
node scripts/build-tenants.js --registry /etc/pagenary/tenants.json
Or via environment variable:
TENANT_REGISTRY=/etc/pagenary/tenants.json node scripts/build-tenants.js
Git Authentication
For private repositories:
# SSH key
GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key" node scripts/build-tenants.js
# HTTPS token
GIT_CREDENTIALS="username:token" node scripts/build-tenants.js
# Disable interactive prompts (recommended for CI)
GIT_TERMINAL_PROMPT=0 node scripts/build-tenants.js
CI/CD Integration
GitHub Actions
name: Build Docs
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Build documentation
run: node scripts/build-tenants.js my-docs --target ./output
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: docs
path: output/
GitLab CI
build-docs:
image: node:20
script:
- npm ci
- node scripts/build-tenants.js my-docs --target ./public
artifacts:
paths:
- public/
Incremental Builds
For faster CI builds with git-based sources:
# Only rebuild changed content
node scripts/build-tenants.js my-docs --incremental --keep-cache
# Show what changed without building
node scripts/build-tenants.js my-docs --diff-only
Hosting Patterns
Static File Servers
The build output is self-contained. Serve with any static server:
# Python
python -m http.server 8080 -d /var/www/my-docs
# Node.js (npx)
npx serve /var/www/my-docs
# Caddy
caddy file-server --root /var/www/my-docs --listen :8080
Nginx
server {
listen 80;
server_name docs.example.com;
root /var/www/my-docs;
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Caddy
docs.example.com {
root * /var/www/my-docs
file_server
try_files {path} /index.html
@static path *.js *.css *.png *.jpg *.ico
header @static Cache-Control "public, max-age=31536000, immutable"
}
S3 + CloudFront
1. Create S3 bucket with static website hosting
2. Upload build output:
aws s3 sync /var/www/my-docs s3://my-docs-bucket/
3. Create CloudFront distribution pointing to S3
4. Set default root object to `index.html`
5. Configure error pages to return `index.html` for 404s (for hash routing)
Netlify / Vercel
1. Point to your repository
2. Build command: `node scripts/build-tenants.js my-docs --target ./dist`
3. Publish directory: `dist/`
Multi-Tenant Deployment
Single Server, Multiple Tenants
Build all tenants to subdirectories:
node scripts/build-tenants.js --target /var/www/docs
# Creates /var/www/docs/tenant-a/, /var/www/docs/tenant-b/, etc.
Nginx config:
server {
listen 80;
root /var/www/docs;
location ~ ^/([^/]+)/ {
try_files $uri $uri/ /$1/index.html;
}
}
Domain-Based Routing
Each tenant gets its own domain:
tenant-a.example.com {
root * /var/www/docs/tenant-a
file_server
try_files {path} /index.html
}
tenant-b.example.com {
root * /var/www/docs/tenant-b
file_server
try_files {path} /index.html
}
Cache Strategy
| Asset | Cache Policy |
|---|---|
| `index.html` | `no-cache` or short TTL (1 minute) |
| `.js`, `.css` | Long TTL with immutable (`max-age=31536000, immutable`) |
| `sections/*.js` | Long TTL (content-addressed) |
The hash-based router means all navigation works client-side, so `index.html` is the only file that needs frequent updates.
Monitoring
Health Check
Create a simple health endpoint by checking for `index.html`:
curl -f https://docs.example.com/ || exit 1
Build Verification
After deployment, verify the build:
# Check response
curl -I https://docs.example.com/
# Verify content
curl -s https://docs.example.com/ | grep -q "manifest.js"