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.

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 POSTGRES binding to external Postgres;
  • R2 as the BLOB binding 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