Self-Hosting Guide
Self-host EdgeBase using the protected Docker or pack launcher. edgebase dev
and the server package's dev:raw command are local-development tools, not
production self-host launchers.
Why Self-Hosting is Still Fast
EdgeBase runs on workerd, an open-source JavaScript runtime built on the V8 engine. The exact same code that runs on the cloud edge also runs in Docker/Node.js.
| Traditional BaaS | EdgeBase (Self-Hosted) | |
|---|---|---|
| DB Access | Network round-trip (ms) | In-process SQLite (μs) |
| JS Performance | Node.js (V8) | workerd (V8) — same engine |
| Cold Start | Container boot (seconds) | Always running (0ms) |
| WebSocket | Per-connection memory | Hibernation API — $0 idle memory |
SQLite runs in the same thread as the application, so single-query latency is significantly lower than BaaS platforms using network databases. Self-hosting doesn't come with a performance penalty — it can even be faster due to zero network hops.
Deployment Methods
| Cloud Edge | Docker | Pack artifact | |
|---|---|---|---|
| Command | npx edgebase deploy | npx edgebase docker run | npx edgebase pack --format portable |
| Requires | Cloudflare account | Docker | Target-platform host |
| Pros | Global edge, auto-scale, no server management | Single container, data sovereignty | Protected launcher without a container |
| Cons | Cloud account required | Docker required | Build separately for each target platform |
| Cost | ~$5/mo | VPS only (~$5/mo) | VPS only |
| Data Location | Edge data centers | Local server | Local server |
1. Running with Docker
Quick Start
# Build image
npx edgebase docker build
# Run container (background) — auto-generates .env.release with JWT secrets
npx edgebase docker run -d
# Or use docker directly
docker build -t edgebase .
docker run -d -p 8787:8787 -v edgebase-data:/data --env-file .env.release --name edgebase edgebase
On first run, npx edgebase docker run automatically creates .env.release with secure random JWT_USER_SECRET and JWT_ADMIN_SECRET values. See the Environment Variables section below for details.
If your project defines frontend.directory in edgebase.config.ts, npx edgebase docker build also copies that prebuilt static bundle into the container image and serves it on the same origin as the API. Build the frontend before you run the Docker build so the bundle exists on disk.
For extra files referenced by the project Dockerfile, such as an entrypoint or
health-check script, put them in a project-level docker-context/ directory.
EdgeBase copies those files into its synthetic build context. The generated
Dockerfile, .dockerignore, and .edgebase/ bundle are reserved and cannot be
overridden from that directory.
If you provide a custom Dockerfile, its final stage must still execute the
generated protected entrypoint. The CLI rejects shell-form or shadowed final
commands, verifies the built image's effective JSON Entrypoint/Cmd, and
checks that the referenced bundle files exist inside the image. Bypassing
.edgebase/self-host/self-host-docker-entrypoint.mjs is not a supported
production configuration.
Docker Compose
The Compose file uses build: ., and the Dockerfile copies the portable app
bundle from .edgebase/targets/docker-app/. That directory only exists after you
prepare the Docker context, so generate it first or docker compose up will fail
with a COPY error. --context-only performs this preparation without building
an image or requiring a local Docker daemon.
# Prerequisite: generate the portable app bundle and Docker build context
npx edgebase docker build --context-only
# Start
docker compose up -d
# View logs
docker compose logs -f
# Stop
docker compose down
Environment Variables
Workers Secrets are mapped to environment variables in Docker:
# docker-compose.yml
services:
edgebase:
environment:
- JWT_USER_SECRET=your-secure-jwt-secret
- SERVICE_KEY=your-service-key
- JWT_ADMIN_SECRET=your-admin-jwt-secret
Or use an .env.release file (recommended — same file used by npx edgebase deploy):
# .env.release
JWT_USER_SECRET=your-secure-jwt-secret
SERVICE_KEY=your-service-key
JWT_ADMIN_SECRET=your-admin-jwt-secret
docker run --env-file .env.release -p 8787:8787 -v edgebase-data:/data edgebase
That SERVICE_KEY is the same credential consumed by all Admin SDKs.
The container forwards the complete process environment to Wrangler, so custom
application variables in the same env file are available to functions and
runtime config as well. The entrypoint removes any bundled .dev.vars file;
otherwise Wrangler would ignore the container process environment.
For local Docker development, use .env.development instead:
docker run --env-file .env.development -p 8787:8787 -v edgebase-data:/data edgebase
Data Persistence
All data is stored in the /data volume:
| Data | Path | Description |
|---|---|---|
| DO SQLite | /data/v3/do/ | Database/room state plus key-sharded atomic OAuth callback state |
D1 Auth (AUTH_DB) | /data/v3/d1/ | Auth control plane (users, sessions, OAuth, MFA, admin data) |
D1 Control (CONTROL_DB) | /data/v3/d1/ | Internal operational metadata (plugin versions, cleanup/backup metadata) |
| R2 Files | /data/v3/r2/ | Uploaded files |
| KV Data | /data/v3/kv/ | Ephemeral caches and best-effort legacy OAuth migration mirror |
AUTH_DB and CONTROL_DB are separate internal D1 databases that share the same persisted base directory. Keeping plugin/control-plane metadata in CONTROL_DB avoids mixing operational state into the auth hot path.
2. Packed Host Execution
Build a protected directory or portable artifact for the target host:
# Clone or initialize an EdgeBase project
npm create edgebase@latest my-project
cd my-project
# Directory artifact (requires Node.js on the target host)
npx edgebase pack --format dir --output ./dist/my-app
node ./dist/my-app/launcher.mjs
# Or a target-platform artifact with an embedded runtime
npx edgebase pack --format portable --output ./dist/my-app
The generated launcher verifies one immutable app generation, starts Wrangler only on loopback, authenticates the exact runtime generation and schedule digest, validates durable schedule state, completes the first supervisor pass, and opens the public gateway last. It owns the complete child process group and removes temporary runtime-secret files on normal startup failure or shutdown.
Do not replace the launcher with raw wrangler dev. Raw Wrangler, edgebase dev, and dev:raw omit production gateway admission, durable managed-schedule
supervision, and bounded process-group shutdown. They remain useful for local
development and dedicated tests only.
If frontend is configured, the local runtime can also serve that prebuilt bundle.
For mountPath, spaFallback, and route behavior, see Static Frontend Guide.
Process Management
# Install PM2
npm install -g pm2
# Start the generated launcher
pm2 start ./dist/my-app/launcher.mjs --interpreter node --name edgebase
# Configure auto-restart
pm2 startup
pm2 save
3. HTTPS Reverse Proxy
HTTPS is required for production. Use Caddy or Nginx as a reverse proxy.
EdgeBase uses the client IP address for rate limiting and brute-force
protection. Generated Docker and pack gateways discard client-supplied
forwarding headers and write their own values. They preserve an upstream
X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host set only when the
immediate peer matches EDGEBASE_TRUSTED_PROXY_CIDRS.
Set that variable to the exact Caddy/Nginx peer addresses or CIDRs. Do not use a broad private network when other workloads can connect from it. The gateway adds a fresh internal proof before forwarding to the Worker, and strips any client copy of that proof first. A matching public header alone therefore cannot establish proxy authority.
# Example only: use the actual immediate proxy peers for your topology.
EDGEBASE_TRUSTED_PROXY_CIDRS=127.0.0.1/32,::1/128
For direct TLS at the generated gateway, set LOCAL_PROTOCOL=https together
with both HTTPS_CERT_PATH and HTTPS_KEY_PATH. The production launcher does
not generate or silently fall back to a self-signed certificate.
Caddy (Recommended — Auto HTTPS)
# Install Caddy
sudo apt install -y caddy
Caddyfile configuration:
your-domain.com {
reverse_proxy localhost:8787 {
# EdgeBase preserves this set only when Caddy's peer address is listed
# in EDGEBASE_TRUSTED_PROXY_CIDRS.
header_up X-Forwarded-For {remote_host}
}
}
sudo systemctl reload caddy
Caddy automatically configures Let's Encrypt. No manual SSL certificate management is needed. Add Caddy's immediate peer address to
EDGEBASE_TRUSTED_PROXY_CIDRS; the generated gateway then preserves Caddy's overwritten forwarding set and proves that gateway hop to the Worker.
Nginx + Let's Encrypt
# Install Nginx + Certbot
sudo apt install -y nginx certbot python3-certbot-nginx
Nginx configuration (/etc/nginx/sites-available/edgebase):
server {
server_name your-domain.com;
location / {
proxy_pass http://localhost:8787;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# IMPORTANT: Use $remote_addr (not $proxy_add_x_forwarded_for) to prevent
# clients from injecting fake IPs. This overwrites any client-sent header.
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Enable site + SSL
sudo ln -s /etc/nginx/sites-available/edgebase /etc/nginx/sites-enabled/
sudo certbot --nginx -d your-domain.com
sudo systemctl reload nginx
The Upgrade header configuration is required for WebSocket support.
If you enable Service Key ipCidr constraints or rely on per-client rate
limiting behind a reverse proxy, exact EDGEBASE_TRUSTED_PROXY_CIDRS peer
configuration plus overwritten forwarding headers is required.
4. Backups
EdgeBase provides two backup methods:
| Method | Use Case | Speed |
|---|---|---|
| Volume/data copy | Restore within the same environment (Docker→Docker, pack→pack) | Fast |
| CLI Portable Backup | Cross-environment migration (Edge↔Docker↔pack) | Moderate |
4.1 Volume Backup (Same Environment)
The fastest method is to stop the launcher and copy its Docker volume or pack data directory as one coherent unit.
Docker Volume Backup
# Backup volume (tar archive)
docker run --rm -v edgebase-data:/data -v $(pwd):/backup \
alpine tar czf /backup/edgebase-backup-$(date +%Y%m%d).tar.gz /data
# Restore volume
docker run --rm -v edgebase-data:/data -v $(pwd):/backup \
alpine tar xzf /backup/edgebase-backup-20260213.tar.gz -C /
Pack Launcher Data Backup
# Launch with an explicit, easy-to-back-up data root
node ./dist/my-app/launcher.mjs --data-dir ./edgebase-data
# After stopping the launcher, back up that complete directory
tar czf edgebase-backup-$(date +%Y%m%d).tar.gz edgebase-data/
# Restore
tar xzf edgebase-backup-20260213.tar.gz
Filesystem copies only work within the same runtime/storage environment. For cross-environment migration (for example Docker→Edge or pack→Docker), use the CLI portable backup below.
4.2 CLI Portable Backup (Cross-Environment)
# DB only (default, fast)
npx edgebase backup create --url <URL> --service-key <KEY>
# DB + secrets (for environment migration — preserves existing JWTs)
npx edgebase backup create --include-secrets
# DB + R2 files (large, slow)
npx edgebase backup create --include-storage
# Full backup (complete migration)
npx edgebase backup create --include-secrets --include-storage
# Backup from Edge environment (enumerates DOs via CF API)
npx edgebase backup create --account-id <CF_ACCOUNT_ID> --api-token <CF_API_TOKEN>
Restore:
# Restore (Wipe & Restore — replaces all existing data)
npx edgebase backup restore --from backup.json --url <target-URL> --service-key <KEY>
# Restore to Edge target
npx edgebase backup restore --from backup.json --account-id <ID> --api-token <TOKEN>
When using --include-secrets, the backup file contains sensitive information. File permissions are automatically set to 600.
4.3 Automated Backup (Cron)
# Daily backup at 3 AM (Docker Volume)
echo "0 3 * * * docker run --rm -v edgebase-data:/data -v /backups:/backup alpine tar czf /backup/edgebase-\$(date +\\%Y\\%m\\%d).tar.gz /data" | crontab -
5. Monitoring
Health Check
curl http://localhost:8787/__edgebase/health
# → {"schemaVersion":1,"outcome":"ok","product":"proxy-ready","scheduler":{...}}
# Check the EdgeBase application runtime separately.
curl http://localhost:8787/api/health
# → {"status":"ok","version":"0.4.9"}
The endpoint returns 200 for structurally ready ready, running, or
degraded scheduler states, and 503 with outcome: "blocked" before
admission, after a structural failure, or during shutdown. A degraded item
remains visible in the scheduler payload without hiding the healthy product
surface. Use a real application route as a separate functional check.
Docker Logs
# Real-time logs
docker logs -f edgebase
# Last 100 lines
docker logs --tail 100 edgebase
Admin Dashboard
The Admin Dashboard is built into self-hosted deployments:
http://your-domain.com/admin
Production-style self-hosted deployments do not expose a public first-admin form. Bootstrap the first admin from your project directory instead:
npx edgebase admin bootstrap --url http://localhost:8787 --service-key <service-key>
npx edgebase docker run guides you through this automatically for first-time setups. If you lose an admin password later, recover it with npx edgebase admin reset-password using the same root Service Key. Admin recovery is CLI-based rather than email-based.
6. Troubleshooting
| Problem | Solution |
|---|---|
| Port conflict | Use --port to specify a different port |
| Data loss | Verify volume mount: -v edgebase-data:/data |
| WebSocket disconnects | Check reverse proxy Upgrade header configuration |
| Container not auto-restarting | Verify --restart unless-stopped flag |
| Out of memory | Set memory limit with --memory 512m |