CDN Service Deployment
Deploy the CDN Service to Cloudflare Workers or as a Docker container.
Prerequisites
- Node.js 20+
- Cloudflare account (for Workers deployment)
- Docker (for Docker deployment)
Cloudflare Workers Deployment
1. Clone the Repository
bash
git clone https://github.com/epinnock/scry-cdn-service.git
cd scry-cdn-service
npm install2. Create R2 Bucket
bash
wrangler r2 bucket create scry-static-sites3. Create KV Namespace
bash
# Production namespace
wrangler kv:namespace create CDN_CACHE
# Preview namespace (for local development)
wrangler kv:namespace create CDN_CACHE --previewNote the IDs returned for each namespace.
4. Configure wrangler.toml
toml
name = "scry-cdn-service"
main = "cloudflare/index.ts"
compatibility_date = "2024-01-01"
# R2 Bucket Binding
[[r2_buckets]]
binding = "STATIC_SITES"
bucket_name = "scry-static-sites"
preview_bucket_name = "scry-static-sites-staging"
# KV Namespace Binding
[[kv_namespaces]]
binding = "CDN_CACHE"
id = "your-production-kv-id"
preview_id = "your-preview-kv-id"
# Worker Routes
routes = [
{ pattern = "view.scrymore.com/*", zone_name = "scrymore.com" }
]
# Environment Variables
[vars]
PLATFORM = "cloudflare"
CACHE_CONTROL = "public, max-age=31536000, immutable"
ALLOWED_ORIGINS = "*"5. Set Secrets (if needed)
bash
wrangler secret put FIREBASE_PROJECT_ID
wrangler secret put FIREBASE_CLIENT_EMAIL
wrangler secret put FIREBASE_PRIVATE_KEY
wrangler secret put PREVIEW_TOKEN_SECRETBindings and secrets
| Name | Kind | Purpose |
|---|---|---|
STATIC_SITES | R2 binding | Bucket holding the build archives |
CDN_CACHE | KV binding | Cached ZIP central directories |
FIREBASE_PROJECT_ID / FIREBASE_CLIENT_EMAIL / FIREBASE_PRIVATE_KEY | Secrets | Verify sessions for private projects |
PREVIEW_TOKEN_SECRET | Secret | Verifies signed preview tokens minted by the dashboard |
PREVIEW_TOKEN_SECRET_PREVIOUS | Secret | Optional. Lets the secret rotate without invalidating live tokens |
CORS_ALLOWED_ORIGINS | Var | Origins allowed to embed a story |
CORS_FORCE_WILDCARD | Var | Development escape hatch; leave off in production |
PREVIEW_TOKEN_SECRET must match the dashboard's, since the dashboard mints the token and the CDN verifies it. Rotate by setting the new value and moving the old one into PREVIEW_TOKEN_SECRET_PREVIOUS until outstanding tokens expire.
Deploy stamps (SCRY_ENV, SCRY_COMMIT, SCRY_BRANCH, SCRY_BUILD_TIME, SCRY_DEPLOY_ID, SCRY_ACTOR) are injected at build time and reported by the health endpoints.
6. Deploy
bash
npm run build:cloudflare
wrangler deployDocker Deployment
1. Build Image
bash
npm run docker:build
# or
docker build -t scry-cdn-service .2. Run Container
bash
docker run -d \
-p 3000:3000 \
-e PLATFORM=docker \
-e STORAGE_TYPE=filesystem \
-e STORAGE_PATH=/data \
-v /var/data/static-sites:/data \
scry-cdn-service:latestUsing R2 with Docker
bash
docker run -d \
-p 3000:3000 \
-e PLATFORM=docker \
-e STORAGE_TYPE=r2 \
-e R2_BUCKET=scry-static-sites \
-e R2_ACCOUNT_ID=your-account-id \
-e R2_ACCESS_KEY_ID=your-access-key \
-e R2_SECRET_ACCESS_KEY=your-secret-key \
scry-cdn-service:latestDocker Compose
yaml
version: '3.8'
services:
cdn-service:
build: .
ports:
- "3000:3000"
environment:
- PLATFORM=docker
- STORAGE_TYPE=r2
- R2_BUCKET=${R2_BUCKET}
- R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
- R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
restart: unless-stoppedDNS Configuration
Cloudflare DNS
The CDN routes by path (/{projectId}/{versionId}/{file}), so it needs one hostname, not a wildcard:
| Type | Name | Content | Proxy |
|---|---|---|---|
| AAAA | view | 100:: | Enabled |
Other DNS Providers
Point your domain to Cloudflare nameservers, then configure DNS in Cloudflare.
Environment Variables
| Variable | Default | Description |
|---|---|---|
PLATFORM | - | cloudflare or docker |
STORAGE_TYPE | filesystem | filesystem or r2 (Docker only) |
STORAGE_PATH | /data | Path for filesystem storage |
R2_BUCKET | - | R2 bucket name |
R2_ACCOUNT_ID | - | Cloudflare account ID |
R2_ACCESS_KEY_ID | - | R2 access key |
R2_SECRET_ACCESS_KEY | - | R2 secret key |
CACHE_CONTROL | public, max-age=31536000 | Cache-Control header |
ALLOWED_ORIGINS | * | CORS allowed origins |
Verification
Health Check
bash
curl https://view-test.scrymore.com/healthExpected response:
json
{
"status": "healthy",
"service": "scry-cdn-service",
"platform": "cloudflare",
"timestamp": "2024-01-15T10:30:00.000Z"
}Test File Serving
bash
# Upload a test ZIP
wrangler r2 object put STATIC_SITES/test-project.zip --file=test.zip
# Access by path
curl http://localhost:8787/test-project/v1/index.htmlCI/CD Deployment
GitHub Actions
yaml
name: Deploy CDN Service
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build:cloudflare
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: cloudflareMonitoring
Cloudflare Dashboard
- Workers → Analytics
- View request counts, errors, latency
- Monitor KV and R2 usage
Real-time Logs
bash
wrangler tailDocker Logs
bash
docker logs -f scry-cdn-serviceScaling
Cloudflare Workers
- Automatic global distribution
- No configuration needed
- Scales to millions of requests
Docker
- Use Kubernetes or Docker Swarm
- Add load balancer
- Configure horizontal pod autoscaler
Troubleshooting
"Central directory not found"
- Ensure ZIP file exists in R2/storage
- Check file path format:
{project}.zip
"KV namespace not found"
- Verify KV namespace ID in wrangler.toml
- Ensure KV binding name matches code
"Route not matching"
- Check DNS configuration
- Verify route pattern in wrangler.toml
- Ensure zone_name matches your domain
CORS Errors
- Check
ALLOWED_ORIGINSconfiguration - Verify CORS headers in response
Next Steps
- Architecture - Technical details
- ZIP Extraction - How extraction works
- Upload Service Deployment - Deploy the upload service