Cloudflare Workers gives FeedLog a serverless edge deployment — zero idle cost, global distribution, native integration with R2 for blob storage and Hyperdrive for accelerated Postgres connections.
- A Cloudflare account (free tier works for early usage)
- A Postgres 17+ with the
vectorextension reachable over the public internet. Recommended providers: Neon, Supabase, AWS RDS, any host you can connect to via TLS. - Node.js 22+ and pnpm 10+ on your local machine for manual setup
- wrangler CLI for manual setup:
pnpm dlx wrangler --version(installed on demand)
The button forks this repo into your GitHub account, then prompts you to configure the bindings and secrets FeedLog needs before it can boot:
POSTGRES(Hyperdrive) — click Create and paste your Postgres connection string. Hyperdrive pools and accelerates the connection from Cloudflare's edge. Your Postgres must have thevectorextension enabled (Neon and Supabase support it out of the box).BLOB(R2 bucket) — click Create and pick a bucket name (e.g.feedlog) to store uploaded images. You can also skip this step; the app still runs, but file uploads will fail until you add the binding later from the Cloudflare dashboard.BETTER_AUTH_SECRET— 32+ random chars. Generate withopenssl rand -hex 32.SYSTEM_ADMIN_EMAILS— your email, so the first user you sign up with becomes admin. Comma-separate multiple admin bootstrap emails. Must be set before the first sign-up; otherwise the install has no admin user.
Cloudflare renders BETTER_AUTH_SECRET and SYSTEM_ADMIN_EMAILS from
.dev.vars.example as masked secret fields, uploads them as encrypted
Worker Secrets, and does not write them back to the forked repository.
After the first build, visit the *.workers.dev URL Cloudflare prints.
On first request, FeedLog detects the empty database and serves a /setup
page that runs migrations in the background. The page redirects back to
the app as soon as the schema is initialized — no local pnpm migrate
step required. Sign up with an email listed in SYSTEM_ADMIN_EMAILS and
you land as admin.
The runtime
/setuppage requires thevectorextension to already exist in your Postgres database. Neon and Supabase enable it via their UI; for other hosts runCREATE EXTENSION IF NOT EXISTS vector;once before the first request.
git clone https://github.com/linkcraftstudio/feedlog.git
cd feedlog
pnpm installHyperdrive pools and caches connections to your Postgres over Cloudflare's network, which is essential for Workers (which can't hold long-lived TCP connections).
wrangler hyperdrive create feedlog \
--connection-string="postgresql://USER:PASSWORD@HOST:5432/DATABASE"Copy the returned id value and paste it into wrangler.toml:
[[hyperdrive]]
binding = "POSTGRES"
id = "PASTE-THE-ID-HERE" # <-- replacewrangler r2 bucket create feedlogwrangler.toml already declares the binding as bucket_name = "feedlog".
If you use a different name, update the file accordingly.
Migrations run automatically on first request via the /setup page, but
they assume the vector extension already exists. Enable it once against
your Postgres:
CREATE EXTENSION IF NOT EXISTS vector;Neon and Supabase expose a UI toggle for this — no SQL needed.
[vars]
BETTER_AUTH_URL = "https://feedlog.your-subdomain.workers.dev"
# Optional:
# GOOGLE_CLIENT_ID = "your-oauth-client-id.apps.googleusercontent.com"Never put secrets in wrangler.toml — use Cloudflare's encrypted secret
store:
wrangler secret put BETTER_AUTH_SECRET
# Paste a 32+ char random string. Generate one with:
# openssl rand -hex 32
wrangler secret put SYSTEM_ADMIN_EMAILS
# Your email (comma-separated list if multiple). The first user who signs
# up with one of these emails is promoted to admin automatically.
# Optional — enable Google OAuth
wrangler secret put GOOGLE_CLIENT_SECRET
# Optional — enable AI features
wrangler secret put OPENAI_API_KEY
# Optional — transactional email
wrangler secret put RESEND_API_KEYpnpm build:cf # Nuxt build with NITRO_PRESET=cloudflare-module
pnpm deploy:cf # wrangler deploy --cwd .outputYour app is now live at the URL wrangler prints (e.g.
https://feedlog.your-subdomain.workers.dev).
In the Cloudflare dashboard → Workers & Pages → your worker → Custom Domains.
Point a route like feedback.yourdomain.com at the worker. Update
BETTER_AUTH_URL in wrangler.toml to match, and redeploy.
Values that end up in the running Worker come from three places:
[vars]inwrangler.toml— public config (base URL, OAuth client IDs). Committed.wrangler secret put— sensitive values (auth secret, OAuth client secrets, API keys). Never committed.- Bindings — the Hyperdrive
POSTGRESbinding replacesDATABASE_URL, and the R2BLOBbinding replaces S3-compatible blob storage.
So for Cloudflare deployments you don't set DATABASE_URL or any
S3_* variables — the bindings do that work. All other env vars from
.env.example apply as usual.
For the complete list of environment variables FeedLog reads, see the configuration reference.
git pull # pull the latest FeedLog changes
pnpm install # refresh dependencies
pnpm build:cf && pnpm deploy:cfIf the release includes new migrations, sign in as admin on the deployed
site and visit /setup — the page runs the pending upgrade and redirects
back. Anonymous upgrades are refused (that would let any visitor trigger
a schema change).
Almost always a Hyperdrive connection failure. Check:
- The Postgres reachable from Cloudflare's network (public IP + TLS)
- The connection string in
wrangler hyperdrive createincluded the correct database name and credentials - The
vectorextension is installed
Use wrangler tail to see the real error:
wrangler tailThe registered callback URL in Google / GitHub OAuth must match
${BETTER_AUTH_URL}/api/auth/callback/<provider> exactly, including the
scheme and any custom domain.
Hit /api/_migrate/status directly and check the state field:
unreachable— the Hyperdrive binding isn't resolving. Verify thePOSTGRESbinding id inwrangler.tomlmatches a real Hyperdrive config (wrangler hyperdrive list), and that its connection string works from outside Cloudflare.bootstrapafter clicking "Try again" — most commonly a missingvectorextension. RunCREATE EXTENSION IF NOT EXISTS vector;and retry.pendingbut the page refuses to advance — the upgrade path requires a signed-in admin. Sign in through/api/auth/...first, then refresh/setup.
The /setup page completes DB initialization but does not create a user.
To get admin access on a fresh install:
- Go to the app home, sign up via email + password using an address listed
in
SYSTEM_ADMIN_EMAILS. - better-auth's admin plugin promotes that user to
role = 'admin'on first sign-up. - Configure GitHub or Google OAuth later from the dashboard if desired (OAuth providers need your public URL as a callback; easier to wire once the Worker has a stable URL).
- Docker deploy — single-container self-hosting
- Vercel deploy — managed Node.js deploy