Skip to content

Self-hosting

If your requirement is that no comment data touches a vendor, self-hosting is usually a better answer than the github or external backends. You keep the full feature set and give up nothing.

You need the Go server, a Postgres database, and somewhere to serve the widget bundle.

Run the server

Terminal window
docker run -p 8080:8080 \
-e DATABASE_URL="postgres://…" \
-e GUTTERNOTE_ENV=production \
ghcr.io/gutternote-dev/server:latest

The image is distroless and runs as non-root. Configuration is entirely environment-driven:

VariableDefaultDescription
DATABASE_URLRequired. Postgres connection string.
GUTTERNOTE_ADDR:8080Listen address.
GUTTERNOTE_ENVdevelopmentproduction switches logs to JSON.
GUTTERNOTE_LOG_LEVELinfodebug, info, warn, error.
GUTTERNOTE_TRUSTED_PROXIESfalseRead the client IP from X-Forwarded-For. Enable only behind a proxy you control.
GUTTERNOTE_GITHUB_TOKENOnly if you use the github backend.

Migrate

Terminal window
make -C apps/server migrate DATABASE_URL="postgres://…"

Migrations are goose SQL files in apps/server/internal/migrate/sql/, embedded in the binary and applied in filename order — the image that ships the code ships its schema.

Serve the widget

The widget is a single static file. Serve it from any origin your previews can reach, with a long immutable cache on versioned paths:

Cache-Control: public, max-age=31536000, immutable

Then point the script tag at your own API:

<script
src="https://cdn.acme.com/gutternote/v1/widget.js"
data-gutternote-key="pk_live_..."
data-gutternote-api="https://gutternote-api.acme.com"
defer
></script>

What you’re responsible for

Backups, upgrades, and TLS. The server is stateless — all state is in Postgres — so scale it horizontally and treat the database as the only thing to back up.