FeedLog is a useful find because it sits between two worlds we have been comparing lately.
It can run as a normal Docker app on a VPS or homelab, but it can also run on Cloudflare Workers or Vercel.
That makes it a good example of a modern Nuxt application where the app code is portable, while each deployment target swaps in its own platform primitives for database connectivity, file storage, queues, and scheduled work.
What is FeedLog?
FeedLog is an open-source feedback and product communication tool.
Collect product feedback, run a public roadmap, and publish changelogs — from a tool you own and control.
It covers the loop many small software teams eventually need:
- collect feature requests;
- let users vote and comment;
- merge similar ideas;
- organize feedback into a roadmap with Kanban-style columns;
- publish changelog entries when things ship;
- expose a widget/inbox inside your own product;
- optionally use AI for duplicate detection and changelog drafting.
The project is MIT licensed ❤️.
The Important Self-Hosting Detail
Yes, FeedLog can be self-hosted on Cloudflare Workers, Vercel, or Docker.
But it is not a tiny SQLite app.
All routes need PostgreSQL 17+ with pgvector. That is the shared foundation for normal application data and vector-powered similar-idea detection.
The deployment target changes how the rest is wired:
| Target | Database path | Blob storage | Background work |
|---|---|---|---|
| Docker | DATABASE_URL to Postgres/pgvector |
S3-compatible storage or local /app/.data fallback |
Nitro scheduled tasks in the app |
| Cloudflare Workers | POSTGRES Hyperdrive binding |
R2 BLOB binding |
Cloudflare Queues and cron triggers |
| Vercel | DATABASE_URL to Postgres/pgvector |
Vercel Blob | Vercel Queues and Vercel cron |
So this is not the same as the pure Cloudflare Workers, D1, KV, and R2 stack we covered earlier.
FeedLog on Cloudflare still needs external Postgres. Cloudflare Hyperdrive makes that connection more practical from Workers, but it does not replace the database.
Tech Overview
FeedLog is built on a modern Vue/Nuxt stack:
| Layer | Choice |
|---|---|
| Framework | Nuxt 4, Vue 3, Nitro, TypeScript |
| Database | PostgreSQL 17+ with pgvector |
| ORM | Drizzle ORM |
| Auth | better-auth with email/password, GitHub OAuth, Google OAuth |
| UI | Tailwind CSS v4, shadcn-vue, Lucide icons |
| State | Pinia |
| AI | OpenAI-compatible API |
| Uploads | R2, Vercel Blob, S3-compatible storage, or local filesystem fallback |
| Container image | ghcr.io/linkcraftstudio/feedlog |
The AI features are optional. Without OPENAI_API_KEY, FeedLog still works as a feedback, roadmap, changelog, and inbox tool. You just lose similar-idea detection, widget agent features, and AI changelog drafting.
Transactional email is also optional. Without a provider such as Resend, development email is logged instead of sent.
What the UI Gives You
After running the local Docker stack, the product shape is clear: FeedLog is not only a feedback form.
The public-facing side gives visitors:
- a feedback board with posts, voting counts, comments, statuses, and board filters;
- a roadmap view grouped into Kanban-style columns such as planned, in progress, and done;
- a changelog area for published release notes;
- a widget/inbox surface for embedding conversations inside another product.
The dashboard side is where the team manages the system:
- create and reorder boards;
- triage feedback;
- update statuses;
- merge duplicate ideas;
- manage roadmap items;
- draft and publish changelogs;
- configure widget behavior;
- review inbox conversations.
So the practical mental model is:
public portal = read, submit, vote, comment
dashboard = organize, moderate, publish, configure
Self-Hosting FeedLog with Docker
For homelab use, Docker is the cleanest path because the compose stack can include Postgres with pgvector.
I added a Home-Lab compose here:
The compose pins the app to ghcr.io/linkcraftstudio/feedlog:v0.5.0, uses pgvector/pgvector:pg17, mounts persistent volumes, and exposes the app on 3030 by default.
FeedLog Docker Compose
services:
db:
image: pgvector/pgvector:pg17
container_name: feedlog-db
restart: unless-stopped
environment:
POSTGRES_DB: feedlog
POSTGRES_USER: feedlog
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
volumes:
- feedlog-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U feedlog -d feedlog"]
interval: 5s
timeout: 5s
retries: 12
networks:
- feedlog
app:
image: ${FEEDLOG_IMAGE:-ghcr.io/linkcraftstudio/feedlog:v0.5.0}
container_name: feedlog
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://feedlog:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@db:5432/feedlog
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:?Set BETTER_AUTH_SECRET in .env}
- SYSTEM_ADMIN_EMAILS=${SYSTEM_ADMIN_EMAILS:?Set SYSTEM_ADMIN_EMAILS in .env}
- AUTH_EMAIL_ENABLED=${AUTH_EMAIL_ENABLED:-true}
- UPLOAD_PREFIX=${UPLOAD_PREFIX:-uploads}
- BETTER_AUTH_URL
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET
- GITHUB_CLIENT_ID
- GITHUB_CLIENT_SECRET
- AUTH_EMAIL_VERIFY
- OPENAI_API_KEY
- OPENAI_BASE_URL
- OPENAI_TEXT_MODEL
- EMAIL_PROVIDER
- RESEND_API_KEY
- EMAIL_FROM
- S3_ACCESS_KEY_ID
- S3_SECRET_ACCESS_KEY
- S3_BUCKET
- S3_ENDPOINT
- S3_REGION
volumes:
- feedlog-app-data:/app/.data
ports:
- "${FEEDLOG_PORT:-3030}:3000"
healthcheck:
test:
[
"CMD-SHELL",
"node -e \"fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""
]
interval: 20s
timeout: 5s
retries: 6
start_period: 30s
networks:
- feedlog
volumes:
feedlog-db-data:
feedlog-app-data:
networks:
feedlog:
Create .env from the sample in Home-Lab:
cp .env.sample .env
openssl rand -hex 32
Minimum values:
POSTGRES_PASSWORD=replace-with-a-long-random-postgres-password
BETTER_AUTH_SECRET=replace-with-openssl-rand-hex-32
SYSTEM_ADMIN_EMAILS=[email protected]
AUTH_EMAIL_ENABLED=true
FEEDLOG_PORT=3030
Then start it:
docker compose up -d
Open:
http://localhost:3030
Sign up using an email listed in SYSTEM_ADMIN_EMAILS; that first matching user becomes admin.
With the sample values above, the bootstrap admin email is:
There is no default admin password. You choose the password yourself on the first signup screen, using that email address.
In my local run, the UI came up fully from the Docker stack. The health endpoint passed, the root page returned 200 OK, and the app exposed the feedback, roadmap, changelog, dashboard, and auth flows through the browser. Screenshots can be added later without changing the deployment notes.
Field Note: Docker Trial
I validated the Home-Lab compose with:
POSTGRES_PASSWORD=replace-me \
BETTER_AUTH_SECRET=replace-me-32-characters-long-secret \
SYSTEM_ADMIN_EMAILS=[email protected] \
docker compose config
Then I started a temporary test instance on port 3806:
POSTGRES_PASSWORD=feedlog-test-postgres \
BETTER_AUTH_SECRET=feedlog-test-secret-012345678901234567890123456789 \
SYSTEM_ADMIN_EMAILS=[email protected] \
FEEDLOG_PORT=3806 \
docker compose -p feedlogtrial up -d
The health endpoint passed:
curl -fsS http://127.0.0.1:3806/health
Returned:
{"status":"ok","timestamp":"2026-10-06T13:05:56.476Z"}
The root page also returned HTTP/1.1 200 OK.
One practical issue came up: optional auth variables should not be passed as empty strings. When I did that initially, the container failed with:
[auth] No sign-in method configured. Enable OAuth ... or set AUTH_EMAIL_ENABLED=true.
The Home-Lab compose now sets AUTH_EMAIL_ENABLED=true for the simple Docker path and leaves optional OAuth/AI/email/S3 variables as pass-through values.
Cloudflare Workers Deployment
FeedLog on Cloudflare Workers uses:
- Workers for the app runtime;
- Hyperdrive as the
POSTGRESbinding to external Postgres; - R2 as the
BLOBbinding for uploaded files; - Cloudflare Queues for inbox work;
- cron triggers for daily recovery;
- Worker secrets for auth, admin bootstrap, OAuth, AI, and email secrets.
Manual setup looks like:
pnpm install
wrangler hyperdrive create feedlog --connection-string="postgresql://USER:PASSWORD@HOST:5432/DATABASE"
wrangler r2 bucket create feedlog
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put SYSTEM_ADMIN_EMAILS
pnpm build:cf
pnpm deploy:cf
On first request, the deployed Worker serves /setup, which runs migrations against the Hyperdrive-bound database. The vector extension must already exist in Postgres.
This is Cloudflare-native deployment, but not Cloudflare-only storage. You still need Postgres somewhere reachable from Cloudflare.
Vercel Deployment
Vercel is the managed Node path.
The repo has vercel.json with:
{
"buildCommand": "pnpm build:vercel",
"crons": [
{
"path": "/api/internal/scheduler/schedule",
"schedule": "0 3 * * *"
}
]
}
pnpm build:vercel runs migrations before the Nuxt build. That means a migration failure stops the new deploy instead of shipping a broken app.
For Vercel, the easiest database pairing is Neon or Supabase with pgvector enabled. Vercel Blob is optional but needed if you want uploads.
FAQ
Can FeedLog really run on Cloudflare Workers, Vercel, and Docker?
Yes. The repo has wrangler.toml, vercel.json, a Dockerfile, and a bundled compose file.
The catch is that the platform changes the supporting services. Cloudflare uses Hyperdrive, R2, Queues, and cron triggers. Vercel uses DATABASE_URL, Vercel Blob, Vercel Queues, and cron. Docker uses Postgres/pgvector plus optional S3-compatible storage or local filesystem uploads.
Is FeedLog fully serverless on Cloudflare?
The app runtime is serverless on Workers, but the database is not D1.
FeedLog needs PostgreSQL 17+ with pgvector, and Cloudflare Workers connects to it through Hyperdrive. So it is serverless at the app/runtime layer, but it still depends on an external Postgres database.
In short: FeedLog is Cloudflare-deployable, not Cloudflare-contained. It can use Cloudflare Workers, Hyperdrive, R2, Queues, and cron triggers, but it cannot run solely on Cloudflare primitives like Workers + D1 + KV + R2.
Can FeedLog run solely on Cloudflare infrastructure?
No, not in the pure Cloudflare-stack sense.
FeedLog’s Cloudflare deployment still needs an external PostgreSQL 17+ database with pgvector. Hyperdrive improves and pools the connection from Workers, but Hyperdrive is not the database.
So the Cloudflare deployment looks like:
Cloudflare Workers + Hyperdrive + R2 + Queues + Cron
|
v
external Postgres with pgvector
That Postgres can be Neon, Supabase, AWS RDS, a VPS-hosted Postgres, or another reachable Postgres provider. If you want the most self-contained setup, the Docker compose path is simpler because it includes pgvector/pgvector:pg17.
What is required for the Docker setup?
The minimum is:
POSTGRES_PASSWORD=...
BETTER_AUTH_SECRET=...
SYSTEM_ADMIN_EMAILS=...
The Home-Lab compose includes pgvector/pgvector:pg17, so you do not need to bring your own database for a first local install.
Which parts are public, and which require an account?
Published portal pages are public read surfaces. Visitors can browse feedback posts, the roadmap Kanban view, and published changelog entries without being staff.
Write actions need identity:
| Action | Access model |
|---|---|
| Browse feedback | Public read |
| Browse roadmap | Public read |
| Read published changelog | Public read |
| Create feedback | Signed-in user, or guest if guest posting is enabled |
| Vote | Signed-in user, or guest if guest voting is enabled |
| Comment/react | Signed-in user, or guest if that guest action is enabled |
| Manage boards | Staff role with moderation permission |
| Triage/merge/moderate feedback | Staff role with moderation permission |
| Draft/publish changelogs | Staff role with moderation permission |
| Organization/member settings | Owner-level organization permissions |
The guest-write switches default to off in the source, so a fresh install is conservative unless you enable guest posting, guest voting, or guest comments from settings.
How is authentication handled?
FeedLog uses better-auth.
The Docker setup in this post enables email/password auth with:
AUTH_EMAIL_ENABLED=true
SYSTEM_ADMIN_EMAILS=[email protected]
The first signup matching SYSTEM_ADMIN_EMAILS is added as owner of the default organization. That owner can access the dashboard and manage the instance.
In this guide’s sample config, that means signing up as [email protected]. The password is not pre-generated or stored in the compose file; you set it during signup.
Optional OAuth providers are available through:
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
GITHUB_CLIENT_ID
GITHUB_CLIENT_SECRET
If neither OAuth provider is configured, email/password login is the simple fallback. BETTER_AUTH_SECRET signs/encrypts auth state; rotating it invalidates existing sessions. If you configure Resend, FeedLog can send password reset, verification, invitation, and notification email; otherwise email is logged to the console for development.
How does FeedLog auth compare with the other Cloudflare apps covered here?
FeedLog has a more traditional SaaS-style application auth model than many of the Cloudflare-native tools in this series.
It uses better-auth inside the app, backed by Postgres tables for users, sessions, accounts, organization membership, invitations, and roles. That gives it email/password login, optional Google/GitHub OAuth, sessions, organization membership, owner/manager/contributor-style permissions, and guest identities for optional public write actions.
That is different from several other Cloudflare projects we covered:
| Project | Auth style | Practical meaning |
|---|---|---|
| Sink | Instance token / bearer-token style | Simple dashboard/API protection around a URL shortener, not a full user/org system. |
| Agentic Inbox | Cloudflare Access JWT validation | Cloudflare Access is the main trust boundary; good for protected demos/internal apps, but not per-mailbox app roles. |
| QuickInbox | App users, sessions, API keys, OAuth MCP | More app-like, with mailbox/user state in D1 and programmatic API access. |
| SaaSMail | App accounts, API keys, OAuth records | Built for SaaS/customer messaging flows, with campaign/API-oriented access. |
| Tempik | Anonymous session token | Disposable temp-mail access; not a team/admin SaaS auth model. |
| Ripgit | Separate GitHub OAuth auth Worker | The auth worker fronts the Git remote and forwards trusted actor headers through a Service Binding. |
| FeedLog | better-auth app auth |
Full app-level accounts, sessions, org roles, guest controls, OAuth, and admin bootstrap. |
So FeedLog is closer to a conventional multi-user product backend than a single-token Cloudflare utility. The tradeoff is that it needs Postgres and more auth state, but you get proper users, roles, sessions, invitations, and admin boundaries.
Does FeedLog require AI keys?
No. OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_TEXT_MODEL are optional.
Without them, normal feedback boards, roadmap, changelog, auth, voting, and comments still work. AI-assisted changelog drafting, widget agent features, and similar-idea detection need an OpenAI-compatible provider.
Does FeedLog require Resend or email infrastructure?
No for a basic trial, yes for production-grade account flows and notifications.
Without RESEND_API_KEY, FeedLog falls back to a console email provider. That is fine while testing, but password resets, verification, and user notifications need a real transactional email provider.
Should I deploy FeedLog with Docker, Cloudflare, or Vercel?
Use Docker when you want the most self-contained homelab setup and easy backups around Postgres volumes.
Use Cloudflare Workers if you already use Cloudflare and are comfortable running external Postgres plus Hyperdrive/R2/Queues.
Use Vercel if you want the simplest managed Nuxt deployment and can pair it with Neon, Supabase, and Vercel Blob.
How does FeedLog compare to heavier project management tools?
FeedLog is not trying to be Jira, Plane, Linear, or a full internal project-management suite.
It is more focused: public feedback intake, voting, roadmap communication, changelogs, and product-facing conversations. That makes it a better fit when you want a public feedback loop, not a complete company work tracker.
Comments