Self-hosting Overview
What running Feeblo yourself involves: the four services, what they require, and how traffic flows between them.
Self-hosted Feeblo is four containers: the API server, the dashboard web app, PostgreSQL, and Redis. Both app containers ship as prebuilt images, so a working deployment needs a compose file, a handful of environment variables, and a machine with Docker. Your data stays in your database, under your backup schedule, behind your firewall.
What you run
| Service | Image | Job |
|---|---|---|
server |
ghcr.io/g3root/feeblo-server |
HTTP API: auth, dashboard RPC, widget endpoints, webhooks, email |
web |
ghcr.io/g3root/feeblo-web |
Astro app serving the dashboard and every public feature board |
pg |
pgvector/pgvector:pg17 |
PostgreSQL 17 with the pgvector extension |
redis |
redis:7-alpine |
Shared store for rate limiting across server replicas |
Images publish to GHCR automatically on pushes to main and on version tags. Pin an
exact tag in production; latest moves whenever main moves.
Requirements
Runtime. Docker Engine plus Docker Compose v2 covers everything in this section. If
you build from source instead of using the images, you need Node.js ^26.4.0 and pnpm
^11.0.3.
Database. Migrations run CREATE EXTENSION IF NOT EXISTS vector and add a
vector(1536) column to the post table, so your PostgreSQL instance must offer the
pgvector extension even if you never turn embeddings on. The compose stack satisfies this
with the pgvector/pgvector:pg17 image. Bring-your-own databases work as long as the
extension does.
Redis. One server instance rate-limits in memory and runs fine without Redis. Once
you run more than one replica, point REDIS_URL at a shared instance so limits count
across all of them.
Domain. One root domain carries everything. The dashboard lives on the app
subdomain, the API on its own host, and each public board gets a subdomain of its own
(feedback.yourcompany.com, acme.yourcompany.com). APP_ROOT_DOMAIN names that root.
Email. Sign-up requires clicking a verification link by default
(AUTH_EMAIL_VERIFICATION_REQUIRED defaults to true), so SMTP must deliver mail before
anyone can finish creating an account. Set SMTP_HOST, SMTP_PORT, and
SMTP_FROM_ADDRESS before first boot.
Secrets. Compose refuses to start until AUTH_ENCRYPTION_KEY is set. This key
encrypts auth data and integration credentials at rest, and changing it later invalidates
existing sessions. Generate one with openssl rand -hex 32.
Ports
Compose publishes four ports, each adjustable through .env:
| Variable | Default | Serves |
|---|---|---|
SERVER_PORT |
8080 | API server |
WEB_PORT |
4321 | Dashboard and public boards |
POSTGRES_PORT |
5432 | PostgreSQL (drop the mapping on public hosts) |
REDIS_PORT |
6379 | Redis (same) |
The server container always listens on 8080 internally; the image pins the port so the
healthcheck and reverse proxies agree with the compose mapping. If host port 8080 is
already taken, edit the left side of the ports mapping in the compose file and leave
SERVER_PORT unset: compose feeds that variable into both sides, and setting it makes
the mapping point at a port nothing listens on.
How the pieces connect
browser ──▶ web :4321 dashboard on app.<root>, boards on <site>.<root>
browser ──▶ server :8080 sign-in callbacks, RPC calls from the dashboard,
widget requests, webhook payloads
web ──▶ server server-side requests follow API_URL
server ──▶ pg DATABASE_URL
server ──▶ redis REDIS_URL (rate-limit counters)
server ──▶ outbound SMTP for email, S3-compatible storage for media
Browsers reach web and server directly, which is why APP_URL and API_URL must be
real, reachable origins. The web container routes by hostname: any subdomain except app
renders the public board registered for that subdomain, and app (or no subdomain)
serves the dashboard. Because browsers hit both containers, cookies land correctly only
when both URLs share APP_ROOT_DOMAIN.
Startup order takes care of itself: server waits for pg and redis to pass their
healthchecks, and web waits for server. One docker compose up -d brings the stack
up in the right order.