Self-Hosting

Your infrastructure, your reviews.

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.

View on GitHub →

What you'll need

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.

Docker

With Compose plugin (included in Docker Desktop and most server installs).

PostgreSQL 17

Bundled in the docker-compose file, or bring your own.

Download and configure

Grab the example Docker Compose file and a starter .env, then generate a secret key for signing cookies.

Terminal
# Download config files
$ curl -o docker-compose.yml \
https://raw.githubusercontent.com/tomasz-tomczyk/crit-web/main/contrib/docker-compose.example.yml
$ curl -o .env \
https://raw.githubusercontent.com/tomasz-tomczyk/crit-web/main/.env.example
# Generate a secret key
$ openssl rand -base64 64

Set SECRET_KEY_BASE and PHX_HOST in your .env file.

Start the services

One command brings the stack up. Database migrations run automatically on boot, so you don't have to wire anything else in.

Terminal
$ docker compose up -d
Container crit-db Started
Container crit-web Started
$ # Ready at https://crit.example.com

Environment variables

Every knob lives in .env — grouped below by what they control.

Database

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.

required*
DB_HOST

Database host — alternative to DATABASE_URL

required*
DB_USER

Database user

required*
DB_PASSWORD

Database password

required*
DB_NAME

Database name

required*
DB_PORT 5432

Database port

optional
DB_SSL false

Set to true to enable SSL. Without DB_SSL_CA_CERT, connects encrypted without certificate verification (recommended for AWS RDS)

optional
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.

optional
POOL_SIZE 10

Database connection pool size

optional
Application
SECRET_KEY_BASE

64+ byte secret for signing cookies. Generate with openssl rand -base64 64

required
PHX_HOST

Hostname where crit-web is served, e.g. crit.example.com

required
PHX_SERVER

Set to true to start the web server

required
SELFHOSTED

Set to true to enable self-hosted mode (dashboard, no marketing pages)

required
ADMIN_PASSWORD

Password for the admin dashboard. If unset and no OAuth configured, dashboard is open to anyone.

optional
OAuth

Use GITHUB_* or OAUTH_* — not both. When either is set, OAuth login is required to access the dashboard and view reviews.

GITHUB_CLIENT_ID

GitHub OAuth App client ID. Set alongside GITHUB_CLIENT_SECRET to enable GitHub login. When set, OAuth is required to access the dashboard and view reviews.

optional
GITHUB_CLIENT_SECRET

GitHub OAuth App client secret

optional
OAUTH_CLIENT_ID

Generic OIDC/OAuth2 client ID. Use with OAUTH_CLIENT_SECRET and OAUTH_BASE_URL for providers like Google, GitLab, or Okta. Mutually exclusive with GITHUB_CLIENT_ID.

optional
OAUTH_CLIENT_SECRET

Generic OAuth2 client secret

optional
OAUTH_BASE_URL

OIDC discovery base URL, e.g. https://accounts.google.com

optional
Network
PORT 4000

HTTP port the server listens on

optional
PHX_SCHEME https

URL scheme used in generated links

optional
FORCE_SSL false

Set to true to force HTTPS redirects. Not needed when running behind a reverse proxy.

optional
Error monitoring

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.

optional
SENTRY_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.

optional
SENTRY_ENV prod

Environment tag attached to events (e.g. staging, prod).

optional
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.

optional

Using your own Postgres

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.

Terminal
$ docker run -d \
--name crit-web \
--restart unless-stopped \
-e DATABASE_URL="postgres://user:pass@your-host:5432/crit" \
-e SECRET_KEY_BASE="your-generated-secret" \
-e PHX_HOST="crit.example.com" \
-e SELFHOSTED=true \
-e PHX_SERVER=true \
-e PORT=4000 \
-p 4000:4000 \
ghcr.io/tomasz-tomczyk/crit-web:latest \
sh -c "/app/bin/migrate && /app/bin/server"

Staying up to date

Updates are a pull and a restart. Migrations run on boot, so you don't have to coordinate anything.

Terminal
$ docker compose pull
$ docker compose up -d
Updated to latest

Available image tags.

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.

Keep going.

Now that the server is up, point the CLI at it or wire up your editor.