Last Updated: 2026-05-06 Status: Active
How to run the blog system locally and deploy it to production.
1. Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Runtime for local dev |
| pnpm | 8+ | Package manager (Turborepo workspace) |
| Wrangler CLI | 4.x | Cloudflare Workers local dev + deploy |
| A Neon account | — | PostgreSQL database |
2. Local Development
The blog system spans three services. In local dev they run on these ports:
| Service | Port | Start command |
|---|---|---|
| seller-dashboard | 3000 | pnpm dev (from repo root, or turbo run dev) |
| gateway | 8788 | pnpm dev in apps/gateway |
| blog-service | 8791 | pnpm dev in apps/blog-service |
Step-by-Step Setup
1. Install dependencies
pnpm install2. Set up blog-service env
Create apps/blog-service/.env:
BLOG_DATABASE_URL=postgresql://user:password@hostname.neon.tech/blog?sslmode=require
CORE_DATABASE_URL=postgresql://user:password@hostname.neon.tech/main?sslmode=requireCreate apps/blog-service/.dev.vars:
GATEWAY_SECRET=your-local-gateway-secret
DEBUG=true
BLOG_DATABASE_URLandCORE_DATABASE_URLneed separate Neon databases (or branches).CORE_DATABASE_URLmust point to the same project asmanager-service'sDATABASE_URL.
3. Run database migrations
cd apps/blog-service
npm run migrateThis applies all migrations in drizzle/ to your BLOG_DATABASE_URL database.
4. Set up gateway env
Create apps/gateway/.dev.vars:
JWT_PUBLIC_KEY=<RSA public key PEM — must match manager-service's JWT_PRIVATE_KEY>
GATEWAY_SECRET=your-local-gateway-secret
DATABASE_URL=postgresql://user:password@hostname.neon.tech/main?sslmode=require
DEBUG=true
GATEWAY_SECRETmust be the same string in both gateway.dev.varsand blog-service.dev.vars.
5. Set up seller-dashboard env
Create apps/seller-dashboard/.env.local:
NEXT_PUBLIC_GATEWAY_URL=http://127.0.0.1:8788
NEXTAUTH_SECRET=any-stable-string
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret6. Start all services
Either start everything together from the repo root:
turbo run devOr start each service in a separate terminal:
# Terminal 1
cd apps/gateway && pnpm dev
# Terminal 2
cd apps/blog-service && pnpm dev
# Terminal 3
cd apps/seller-dashboard && pnpm dev7. Verify the setup
# blog-service health
curl http://localhost:8791/health
# → BLOG SERVICE OK
# blog-service env status (DEBUG mode only)
curl -H "x-gateway-key: your-local-gateway-secret" http://localhost:8791/debug
# → { service: "blog-service", status: "alive", env: { BLOG_DATABASE_URL_host: "..." } }
# Gateway → blog-service connectivity (DEBUG mode only)
curl -H "Authorization: Bearer <valid-jwt>" http://localhost:8788/blog/debug
# → { service: "blog-service", status: "alive" }3. Environment Variable Reference
blog-service
Set in .env (loaded by wrangler) and .dev.vars (local secrets, gitignored):
| Variable | Required | Where | Description |
|---|---|---|---|
BLOG_DATABASE_URL |
✅ | .env |
Neon connection string for the blog database |
CORE_DATABASE_URL |
✅ | .env |
Neon connection string for the core/manager database |
GATEWAY_SECRET |
✅ | .dev.vars |
Shared secret validated on every request from gateway |
DEBUG |
❌ | .dev.vars |
Set to "true" for verbose request logging |
Gateway
Set in .dev.vars (local) or Cloudflare Dashboard / wrangler secret put (production):
| Variable | Required | Description |
|---|---|---|
JWT_PUBLIC_KEY |
✅ | RSA public key (PEM) matching manager-service's signing key |
GATEWAY_SECRET |
✅ | Shared secret injected into every service request |
DATABASE_URL |
✅ | Core database for logging and API key validation |
DEBUG |
❌ | Set to "true" for verbose proxy logging |
Never commit
.dev.varsto git. It is gitignored by wrangler by default. For production secrets usewrangler secret put <NAME>.
seller-dashboard
Set in .env.local (gitignored):
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_GATEWAY_URL |
✅ | Gateway URL — http://127.0.0.1:8788 for local, production URL for prod |
NEXTAUTH_SECRET |
✅ | Signs NextAuth session cookies — must be stable across restarts |
NEXTAUTH_URL |
✅ | NextAuth callback base URL |
GOOGLE_CLIENT_ID |
✅ | OAuth — from Google Cloud Console |
GOOGLE_CLIENT_SECRET |
✅ | OAuth — from Google Cloud Console |
4. Production Deploy
blog-service
cd apps/blog-service
# Set production secrets (one-time per env)
wrangler secret put BLOG_DATABASE_URL
wrangler secret put CORE_DATABASE_URL
wrangler secret put GATEWAY_SECRET
# Deploy
npm run deploy
# → wrangler deploy --minifyThe Worker name is logicspike-blog-service (from wrangler.toml). The gateway's service binding BLOG_SERVICE references this exact name — they must match.
Gateway
cd apps/gateway
# Set production secrets
wrangler secret put JWT_PUBLIC_KEY
wrangler secret put GATEWAY_SECRET
wrangler secret put DATABASE_URL
# Deploy
pnpm deployProduction domain: api.vlozi.app (configured as custom_domain in wrangler.toml).
seller-dashboard
Deployed via Vercel. Set env vars in Vercel Dashboard:
NEXT_PUBLIC_GATEWAY_URL=https://api.vlozi.appNEXTAUTH_SECRET,NEXTAUTH_URL,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET
5. Service Binding
The gateway communicates with blog-service via a Cloudflare Service Binding — not HTTP. This means:
- Zero network latency (in-process call)
- No internet exposure for blog-service
- In local dev: wrangler starts both services and connects them via the binding
The binding is declared in apps/gateway/wrangler.toml:
[[services]]
binding = "BLOG_SERVICE"
service = "logicspike-blog-service"The service value must exactly match the name field in apps/blog-service/wrangler.toml.
6. Common Setup Issues
| Symptom | Cause | Fix |
|---|---|---|
Direct access forbidden. Use the gateway. |
blog-service received request without x-gateway-key or secret mismatch |
Verify GATEWAY_SECRET is identical in both .dev.vars files |
Configuration error: GATEWAY_SECRET missing |
blog-service started without .dev.vars |
Create .dev.vars in apps/blog-service/ |
BLOG_DATABASE_URL not configured |
Missing database env | Add BLOG_DATABASE_URL to apps/blog-service/.env |
| Gateway returns 503 / binding error | blog-service not running | Start blog-service first, then restart gateway |
| 401 on all admin requests | JWT_PUBLIC_KEY mismatch | Gateway's public key must match manager-service's private signing key |
| Env changes not taking effect | Next.js caches server-side env on startup | Fully stop and restart the dev server (hot reload doesn't re-read env) |
| Blog works in prod but not locally | NEXT_PUBLIC_GATEWAY_URL points to prod in .env.local |
Set it to http://127.0.0.1:8788 for local dev |