Self-hosting overview
How a self-hosted Feeblo deployment fits together — the two images, the external services, and the environment that wires them up.
Feeblo is shipped as two container images plus three external services. This page explains how the pieces connect so you know exactly what you are responsible for providing when you self-host. Detailed setup lives in the dedicated Docker, environment, database, media, email, and Cloudflare pages.
This page is based on docker-compose.yml, docker/docker-compose.dev.yml, .env.example, apps/server/src/index.ts, and the root README.md in .reference/.
What you run
A production Feeblo deployment has two of its own services and three dependencies:
server— the Effect HTTP API (ghcr.io/g3root/feeblo-server). Serves the HTTP/REST surface, the RPC router the dashboard calls, better-auth session handlers, and the OpenAPI/Scalar UI at/docs. Default container port8080.web— the Astro dashboard (ghcr.io/g3root/feeblo-web). The control panel your users log into. Default container port4321.- PostgreSQL — the only database. Drizzle migrations define the schema.
- SMTP — an email server (or API relay) used for transactional email.
- S3-compatible object storage — for media uploads (e.g. MinIO, Cloudflare R2, AWS S3).
The official compose file only ships server and web. PostgreSQL, SMTP, and media storage are external to it — you point Feeblo at them with environment variables. This is the most common thing to miss when self-hosting, so it's covered in detail below.
The official Docker Compose
docker-compose.yml in .reference/ defines the two Feeblo services:
services:
server:
container_name: feeblo-server
image: ghcr.io/g3root/feeblo-server:${IMAGE_TAG:-nightly}
restart: always
environment:
NODE_ENV: production
PORT: ${SERVER_PORT:-8080}
SERVER_PORT: ${SERVER_PORT:-8080}
APP_URL: ${APP_URL}
API_URL: ${API_URL}
APP_ROOT_DOMAIN: ${APP_ROOT_DOMAIN}
APP_RELEASE: ${IMAGE_TAG:-nightly}
ports:
- "${SERVER_PORT:-8080}:8080"
web:
container_name: feeblo-web
image: ghcr.io/g3root/feeblo-web:${IMAGE_TAG:-nightly}
restart: always
environment:
NODE_ENV: production
HOST: 0.0.0.0
PORT: ${WEB_PORT:-4321}
API_URL: ${API_URL}
APP_URL: ${APP_URL}
APP_ROOT_DOMAIN: ${APP_ROOT_DOMAIN}
APP_RELEASE: ${IMAGE_TAG:-nightly}
ports:
- "${WEB_PORT:-4321}:4321"
depends_on:
- serverNote a few specifics straight from the file:
- Image tag defaults to
nightlyvia${IMAGE_TAG:-nightly}. Pin a specific tag in production. - The server container listens on
8080internally (PORT/SERVER_PORT);SERVER_PORTalso controls the host port mapping. The web container listens on4321internally. webdepends onserver, so compose starts the API first.
Environment the compose does NOT include
The compose forwards APP_URL, API_URL, APP_ROOT_DOMAIN, and APP_RELEASE into both services. It does not forward the database, auth, SMTP, or media variables. Those are read by the server at startup, so you must add them to the server service (and any others that need them):
DATABASE_URL— required, read as a redacted config inpackages/db/src/config.ts.AUTH_ENCRYPTION_KEY— required, read inpackages/auth/src/config.tsandpackages/domain/src/auth/config.ts.SMTP_*— transactional email settings (SMTP_TRANSPORT,SMTP_HOST,SMTP_PORT,SMTP_FROM_NAME,SMTP_FROM_ADDRESS, …).MEDIA_UPLOAD_*— object storage settings (MEDIA_UPLOAD_REGION,MEDIA_UPLOAD_ENDPOINT,MEDIA_UPLOAD_ACCESS_KEY_ID,MEDIA_UPLOAD_SECRET_ACCESS_KEY,MEDIA_PUBLIC_BUCKET_NAME).NODE_ENV,PORT/SERVER_PORT— already in the compose.
Forward the missing env vars
The combined docker compose file as shipped intentionally only contains server and web. If
you run it as-is without adding DATABASE_URL, AUTH_ENCRYPTION_KEY, SMTP_*, and
MEDIA_UPLOAD_* to the server service (and a postgres/smtp/minio service if you don't have
external ones), the server will fail to start. Either extend this compose or provide the values
some other way.
How the origin checks work
The server validates Cross-Origin requests with an allowlist computed in apps/server/src/index.ts (isAllowedOrigin). A request Origin is allowed when its hostname matches:
- the
API_URLhostname, or - the
APP_URLhostname, or - any subdomain of
APP_ROOT_DOMAIN(e.g.feedback.acme.comifAPP_ROOT_DOMAIN=acme.com).
In development, requests to localhost / 127.0.0.1 / *.localhost origins are allowed when NODE_ENV=development. CORS sends Access-Control-Allow-Credentials and a maxAge of one day.
APP_ROOT_DOMAIN is a hostname, not a URL
isAllowedOrigin strips a single :port from APP_ROOT_DOMAIN before comparing. Set it to the
bare host (acme.com), not https://acme.com. The dev .env.example ships
APP_ROOT_DOMAIN=localhost:3001, which works because the port is stripped and the dev-host branch
handles localhost.
Helpful server endpoints
When the server is running you can sanity-check the deployment:
GET /health— returns the textOK(defined inapps/server/src/index.ts).GET /docs— the Scalar/OpenAPI UI for theApi(wired viaHttpApiScalar.layer(Api, { path: "/docs" })).GET /api/auth/*— better-auth handlers mounted through the server.
Pointers before you start
- Environment — start with
.reference/.env.example; every variable Feeblo understands is listed there (see the environment page for a walkthrough). - Database — PostgreSQL only; Drizzle manages the schema (see the database page).
- Media — any S3-compatible store;
MEDIA_UPLOAD_REGIONshould beautowhen using Cloudflare R2 (see the media page). - Email — a single nodemailer SMTP transport built from the
SMTP_*vars (Mailpit for dev) (see the email page). - Local development — for running from source instead of the published images, see the development setup page; the dev compose (
docker/docker-compose.dev.yml) brings up the same Postgres / Mailpit / MinIO stack with sensible defaults.
Docker setup
Bring up the full stack with the published ghcr.io images and compose.
Environment variables
Every Feeblo variable grouped and explained.
Database
PostgreSQL setup and auto-running migrations.
Media storage
Point Feeblo at MinIO, R2, or any S3-compatible store.
SMTP transport for transactional email.
Development setup
Run Feeblo from source for local development and testing.