Self-Hosting
Run Crit Web on your own server with Docker. Share reviews within your team without sending data to a third party.
tomasz-tomczyk/crit-web
Source code, Dockerfile, example configs, and issue tracker.
Docker with the Compose plugin and a Postgres 17 database. Compose ships with Docker Desktop and most server installs, and the example config bundles its own Postgres if you don't already have one.
With Compose plugin (included in Docker Desktop and most server installs).
Bundled in the docker-compose file, or bring your own.
Grab the example Docker Compose file and a starter .env,
then generate a secret key for signing cookies.
Set SECRET_KEY_BASE and PHX_HOST in your .env file.
One command brings the stack up. Database migrations run automatically on boot, so you don't have to wire anything else in.
Every knob lives in .env — grouped below by what they control.
Set DATABASE_URL
or
all four of DB_HOST, DB_USER, DB_PASSWORD, DB_NAME.
DATABASE_URL
PostgreSQL connection URL, e.g. ecto://user:pass@host/db. Use this
or
the individual
DB_*
vars.
DB_HOST
Database host — alternative to
DATABASE_URL
DB_USER
Database user
required*DB_PASSWORD
Database password
required*DB_NAME
Database name
required*DB_PORT
5432
Database port
optionalDB_SSL
false
Set to true
to enable SSL. Without DB_SSL_CA_CERT, connects encrypted without certificate verification (recommended for AWS RDS)
DB_SSL_CA_CERT
Path to a CA certificate file. When set alongside DB_SSL=true, enables full
verify_peer
verification. In Docker, mount the file as a volume and pass the container path.
POOL_SIZE
10
Database connection pool size
optionalSECRET_KEY_BASE
64+ byte secret for signing cookies. Generate with openssl rand -base64 64
PHX_HOST
Hostname where crit-web is served, e.g.
crit.example.com
PHX_SERVER
Set to true
to start the web server
SELFHOSTED
Set to true
to enable self-hosted mode (dashboard, no marketing pages)
Pick one of three ways for your team to sign in:
GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET.
OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and OAUTH_BASE_URL for Google, GitLab, Keycloak, Okta, or any other OIDC provider.
LOCAL_REGISTRATION_ENABLED=true, then sign up at /users/register and flip it back to false once your team is in.
The two OAuth options are mutually exclusive. Basic accounts are off by default and can run on their own or alongside either OAuth provider — useful for service accounts or users without an SSO identity.
GITHUB_CLIENT_ID
GitHub OAuth App client ID. Set alongside GITHUB_CLIENT_SECRET to enable GitHub login.
GITHUB_CLIENT_SECRET
GitHub OAuth App client secret
optionalOAUTH_CLIENT_ID
Generic OIDC/OAuth2 client ID. Use with OAUTH_CLIENT_SECRET and OAUTH_BASE_URL. Mutually exclusive with GITHUB_CLIENT_ID.
OAUTH_CLIENT_SECRET
Generic OAuth2 client secret
optionalOAUTH_BASE_URL
OIDC discovery base URL, e.g. https://accounts.google.com
LOCAL_REGISTRATION_ENABLED
false
Set to true to open /users/register for email + password sign-ups. Flip back to false once your team is in.
ADMIN_EMAILS
Comma-separated list of admin email addresses. Users with these emails are auto-promoted to admin on every login and on app boot, gaining access to /admin/users and /admin/settings plus moderation powers (delete any review or comment). Removing an email and restarting demotes that user back to a regular user.
PORT
4000
HTTP port the server listens on
optionalPHX_SCHEME
https
URL scheme used in generated links
optionalFORCE_SSL
false
Set to true
to force HTTPS redirects. Not needed when running behind a reverse proxy.
All Sentry vars are optional. Leave them unset and no error data is sent anywhere — the browser SDK isn't even loaded.
SENTRY_DSN
Backend Sentry DSN. Captures Elixir/Phoenix exceptions. Request bodies, cookies, and comment text are scrubbed before events leave the server.
optionalSENTRY_FRONTEND_DSN
Browser SDK DSN. The
@sentry/browser
chunk is only fetched when this is set, so unset means zero third-party
requests from your users' browsers.
SENTRY_ENV
prod
Environment tag attached to events (e.g. staging, prod).
SENTRY_RELEASE
app version
Release tag for grouping events (e.g. a commit SHA or version string).
Falls back to the app version from mix.exs.
Already have a managed Postgres (RDS, Supabase, Neon, anything)? Skip the bundled database and run crit-web as a single container pointed at your existing instance.
Updates are a pull and a restart. Migrations run on boot, so you don't have to coordinate anything.
Images are published to the GitHub Container Registry. Pick a tag based on how aggressive you want updates to be.
latest
Latest stable release. Recommended for production.
main
Bleeding edge. Built from the main branch on every push.
1.2.3
Pin a specific version. Use for reproducible deployments.
If your Crit instance sits behind an SSO reverse proxy (Cloudflare Access,
Tailscale Funnel, oauth2-proxy, etc.), the local CLI can't authenticate
to /api/reviews
from the terminal — the proxy redirects to a login page. Set
proxy_auth: true
in your
~/.config/crit/crit.json
to route Share, Pull, Re-share, and Unpublish through a browser popup
that carries your existing proxy session cookie.
proxy_auth is a CLI-side setting, not a server env var.
The terminal commands crit share, crit fetch,
and crit unpublish
remain unavailable behind SSO — use the in-browser buttons. For terminal
or CI access, ask the proxy admin about
Authorization: Bearer
passthrough on /api/reviews*,
which works with the existing crit auth device flow.
If your proxy strips or rewrites WebSocket upgrades, set
CRIT_LIVEVIEW_TRANSPORT=longpoll
on the server to skip the WebSocket attempt and connect via long-polling
directly. Default is
websocket
with automatic fallback after 2.5s.
If your proxy already authenticates users (oauth2-proxy, Cloudflare Access, Google IAP, Pomerium, Authelia), you can have Crit pick up the signed-in user's email from a request header instead of running its own OAuth.
CRIT_TRUSTED_PROXY_USER_HEADER
Request header containing the authenticated user's email.
required
CRIT_TRUSTED_PROXY_CIDRS
Comma-separated CIDR ranges your proxy forwards from, e.g. 10.0.0.0/8,172.16.0.0/12.
Mandatory — Crit refuses to start if the header is set without CIDRs.
CRIT_LIVEVIEW_TRANSPORT
websocket
Set to longpoll
if your proxy strips or rewrites WebSocket upgrades.
X-Auth-Request-Email (oauth2-proxy),
Cf-Access-Authenticated-User-Email (Cloudflare Access),
X-Goog-Authenticated-User-Email (Google IAP),
X-Pomerium-Claim-Email (Pomerium),
Remote-Email (Authelia).
Your proxy must overwrite the header on every request, not append to incoming values.
This authenticates browser-driven flows only (review pages, dashboard, the in-browser Share popup).
The CLI's direct /api/reviews uploads still require a bearer token from crit auth — use proxy_auth: true on the CLI side if the proxy blocks direct API access.
Two common proxy quirks are handled automatically:
Cache-Control: no-transform
on every response, which tells conformant proxies to leave the body alone.
Now that the server is up, point the CLI at it or wire up your editor.