Codeveira Installation Guide — Docker Compose Setup

Deploy Codeveira on your own server in under 10 minutes using Docker Compose. Supports GitLab, GitHub, Gitea, Forgejo, Bitbucket Cloud, Bitbucket Server, Azure DevOps, Gerrit and SVN — you can connect multiple platforms at the same time.

Prerequisites

Before you start, make sure the following are installed on your server:

Codeveira runs entirely on your infrastructure. No data is sent to any external service except when you explicitly configure an Anthropic API key for the AI reviewer.
1

Get the source code

Codeveira is currently in early access. To receive the source code, contact us at hello@codeveira.com — we'll send you a download link and set you up.

The public repository will be available soon. We'll notify you when it's released.

Once you have the archive, extract it and enter the directory:

tar -xzf codeveira.tar.gz
cd codeveira
2

Configure environment variables

Copy the example configuration file and fill in your values:

cp .env.example .env
nano .env   # or any editor you prefer

These variables are required regardless of which Git platform you use:

# Required — generate with: openssl rand -hex 64
SECRET_KEY_BASE=your_long_random_string_here

# Public URL of your Codeveira instance (used in email links)
APP_HOST=https://codeveira.yourdomain.com

# PostgreSQL password
DB_PASSWORD=choose_a_strong_db_password
Never commit your .env file to version control. It contains secrets.

Then add the variables for the platforms you want to use. You only need to configure the ones you'll actually connect:

GitLab

# Base URL of your GitLab instance
GITLAB_URL=https://gitlab.example.com

# Personal access token with api scope (used as default for new repos)
GITLAB_TOKEN=glpat-xxxxxxxxxxxx

# Webhook verification token
WEBHOOK_SECRET=your_gitlab_webhook_secret

# OAuth — enables "Sign in with GitLab" (optional)
GITLAB_APP_ID=your_app_id
GITLAB_APP_SECRET=your_app_secret

GitHub

# Personal access token — used as default token for GitHub repos
GITHUB_TOKEN=github_pat_xxxxxxxxxxxx

# Webhook HMAC-SHA256 secret (recommended)
GITHUB_WEBHOOK_SECRET=your_github_webhook_secret

# OAuth — enables "Sign in with GitHub" (optional)
GITHUB_APP_ID=your_oauth_app_client_id
GITHUB_APP_SECRET=your_oauth_app_client_secret

Gitea / Forgejo

# Default base URL for Gitea/Forgejo instances
# (can also be set per-repository)
GITEA_URL=https://gitea.example.com

# Webhook HMAC-SHA256 secret — shared by Gitea and Forgejo
GITEA_WEBHOOK_SECRET=your_gitea_webhook_secret

Bitbucket Cloud

# Webhook HMAC-SHA256 secret (recommended)
BITBUCKET_WEBHOOK_SECRET=your_bitbucket_webhook_secret

Also change the default admin credentials before going live:

ADMIN_EMAIL=admin@yourcompany.com
ADMIN_PASSWORD=choose_a_strong_password
3

Start the application

Build and start all services with Docker Compose:

docker compose up -d

This will start four containers: app (Rails), db (PostgreSQL 18), redis and sidekiq (background jobs). The first run also runs database migrations automatically.

Check that all containers are running:

docker compose ps

To watch the application logs in real time:

docker compose logs -f app
After any change to application files, rebuild the image: docker compose build app && docker compose up -d app
4

First login

Open your browser and navigate to http://your-server:3000 (or the domain you configured).

Log in with the admin credentials you set in .env:

  • Email: value of ADMIN_EMAIL
  • Password: value of ADMIN_PASSWORD

You will land on the dashboard. The repository list will be empty until you add your first repository in the next step.

5

Add a repository

Go to Repositories → New Repository. Select the source platform using the buttons at the top of the form — the fields change depending on what you select.

  • Name — display name for the repository
  • GitLab Project ID — found in GitLab → Settings → General
  • GitLab Path — e.g. mygroup/myrepo
  • GitLab URL — optional if GITLAB_URL is already set in .env
  • Access Token — personal access token with api scope
  • Name — display name for the repository
  • Repo pathowner/repo, e.g. octocat/Hello-World
  • Access Token — personal access token (fine-grained or classic) with Contents: Read permission for private repos
For private repositories, use a fine-grained PAT with Contents: Read permission. Classic PATs need the repo scope.
  • Name — display name for the repository
  • Gitea URL — base URL of your Gitea instance, e.g. https://gitea.example.com
  • Owner/Repo — e.g. myorg/myrepo
  • Access Token — personal access token from Gitea → Settings → Applications
  • Name — display name for the repository
  • Forgejo URL — base URL of your Forgejo instance, e.g. https://forgejo.example.com
  • Owner/Repo — e.g. myorg/myrepo
  • Access Token — personal access token from Forgejo → Settings → Applications
Forgejo uses the same API as Gitea. The same GITEA_WEBHOOK_SECRET is shared by both.
  • Name — display name for the repository
  • Workspace/Repo — e.g. myworkspace/myrepo
  • App Password — format: username:app_password. Create one in Bitbucket → Personal settings → App passwords (needs Repositories: Read)
  • Name — display name for the repository
  • Instance URL — required, e.g. https://bitbucket.example.com
  • PathPROJECTKEY/repo-slug, e.g. PROJ/my-repo (project key is uppercase)
  • Token — HTTP access token, or username:password for Basic auth
See the Bitbucket Server guide for token creation steps.
  • Name — display name for the repository
  • Instance URL — leave blank for Azure DevOps cloud; set for on-premises Azure DevOps Server
  • Pathorganization/project/repository, e.g. myorg/MyProject/MyRepo
  • Token — Personal Access Token (PAT) with Code: Read permission
See the Azure DevOps guide for PAT creation and Service Hook setup.
  • Name — display name for the repository
  • Instance URL — required, e.g. https://gerrit.example.com
  • Project Name — Gerrit project name, e.g. my-project or org/my-project
  • Token — format: username:http_password from Gerrit → Settings → HTTP Credentials
See the Gerrit guide for the webhooks plugin configuration.
  • Name — display name for the repository
  • Instance URL — repository root URL, required, e.g. https://svn.example.com/repos/myproject or svn://svn.example.com/myproject
  • Path Identifier — a unique string matched against the hooks/post-commit script's path field; also doubles as the reviewable root, defaults to trunk
  • Credentials — optional username:password; leave blank for anonymous read
See the SVN guide for the hooks/post-commit script. SVN has no PR/branch model, so Pull Request Mode isn't offered for SVN repositories.

Once saved, click the repository on the dashboard to browse its recent commits and create code reviews.

6

Configure webhooks (optional but recommended)

Webhooks let Codeveira automatically create a code review on every push, without polling. Each platform has its own endpoint and verification method.

You can find the exact webhook URL and secret for each repository on its Repository → Show page in Codeveira.

GitLab

  1. Go to your GitLab project → Settings → Webhooks
  2. URL: https://codeveira.yourdomain.com/webhooks/gitlab
  3. Secret token: value of WEBHOOK_SECRET
  4. Trigger: Push events

GitHub

  1. Go to your GitHub repo → Settings → Webhooks → Add webhook
  2. Payload URL: https://codeveira.yourdomain.com/webhooks/github
  3. Content type: application/json
  4. Secret: value of GITHUB_WEBHOOK_SECRET
  5. Trigger: Just the push event

Gitea

  1. Go to your Gitea repo → Settings → Webhooks → Add webhook → Gitea
  2. Target URL: https://codeveira.yourdomain.com/webhooks/gitea
  3. Secret: value of GITEA_WEBHOOK_SECRET
  4. Trigger: Push events

Forgejo

  1. Go to your Forgejo repo → Settings → Webhooks → Add webhook → Forgejo
  2. Target URL: https://codeveira.yourdomain.com/webhooks/forgejo
  3. Secret: value of GITEA_WEBHOOK_SECRET (shared with Gitea)
  4. Trigger: Push events

Bitbucket Cloud

  1. Go to your Bitbucket repo → Repository settings → Webhooks → Add webhook
  2. URL: https://codeveira.yourdomain.com/webhooks/bitbucket
  3. Secret: value of BITBUCKET_WEBHOOK_SECRET
  4. Trigger: Repository push (repo:push)

Bitbucket Server / Data Center

  1. Go to your Bitbucket Server repo → Repository settings → Webhooks → Create webhook
  2. URL: https://codeveira.yourdomain.com/webhooks/bitbucket_server
  3. Secret: value of BITBUCKET_SERVER_WEBHOOK_SECRET
  4. Events: Push (repo:refs_changed)

Azure DevOps

  1. Go to Azure DevOps → Project Settings → Service hooks → + Create subscription
  2. Service: Web Hooks → Next
  3. Event: Code pushed, select your repository → Next
  4. URL: https://codeveira.yourdomain.com/webhooks/azure
  5. Basic auth: any username, password = value of AZURE_WEBHOOK_SECRET → Finish

Gerrit

  1. Install the webhooks plugin on your Gerrit instance
  2. Add to $GERRIT_SITE/etc/webhooks.config:
[remote "codeveira"]
  url = https://codeveira.yourdomain.com/webhooks/gerrit
  event = ref-updated
  password = $GERRIT_WEBHOOK_SECRET

SVN

SVN has no native webhook mechanism, so an executable hooks/post-commit script on the SVN server notifies Codeveira after every commit:

#!/bin/sh
REPOS="$1"
REV="$2"
PATH_ID="myrepo"   # must match the repository's Path Identifier in Codeveira
curl -s -X POST "https://codeveira.yourdomain.com/webhooks/svn" \
  -u "x:$SVN_WEBHOOK_SECRET" \
  -H "Content-Type: application/json" \
  -d "{\"path\":\"$PATH_ID\",\"revision\":\"$REV\"}"

See the SVN guide for the full setup and troubleshooting.

Generate strong webhook secrets with: openssl rand -hex 20

GitLab, GitHub, Gitea, Forgejo, and Bitbucket (Cloud and Server) verify webhook requests via HMAC-SHA256 signature, using ActiveSupport::SecurityUtils.secure_compare to prevent timing attacks. Azure DevOps, Gerrit, and SVN don't support HMAC signing on their webhook mechanisms, so their requests are authenticated via HTTP Basic Auth against AZURE_WEBHOOK_SECRET/GERRIT_WEBHOOK_SECRET/SVN_WEBHOOK_SECRET instead. Every one of these secrets is required — leaving the corresponding env var blank rejects all of that platform's webhook requests with 401 Unauthorized rather than accepting them unauthenticated.

Domain & HTTPS

Codeveira ships with a built-in nginx reverse-proxy container — nothing to install or configure on the host. It's already in docker-compose.yml, publishes ports 80/443, and boots with a zero-config self-signed certificate, so the app is reachable over HTTPS immediately after docker compose up -d. It also proxies the LSP server's raw TCP port (7777) for IDE integration.

To go live on a real domain, sign in as an admin and open Settings → Domain & HTTPS:

Certificates issued via Let's Encrypt renew automatically twice a day — no cron job to set up yourself. Coordination between the app containers (which write the domain's config/certificate) and the nginx container (which serves them) goes entirely through shared Docker volumes plus a polling reload loop inside the nginx container — no Docker socket access, no cross-container RPC.

Prefer your own reverse proxy in front of Codeveira instead (e.g. an existing host-level nginx/Caddy/Traefik shared across other services)? That still works — don't publish the bundled nginx container's ports, and point your own proxy at app:3000 (and the LSP container's raw TCP port for IDE integration). A reference host-nginx config, including the required LSP stream {} block, is included in the source at nginx.conf.example.

The X-Forwarded-For header (already set by the bundled nginx container, or required in your own config if running one) is what lets the Audit Log record real client IPs instead of the proxy address. Rails reads this header automatically via ActionDispatch::RemoteIp middleware — no application changes needed.

Environment variable reference

Full list of variables available in .env:

Variable Required Description
Core
SECRET_KEY_BASErequiredRails secret key. Generate with openssl rand -hex 64.
DB_PASSWORDrequiredPostgreSQL password for the codereview user.
APP_HOSTrequiredPublic URL of your Codeveira instance. Used in email links and webhook setup hints.
ADMIN_EMAILoptionalInitial admin account email. Default: admin@example.com.
ADMIN_PASSWORDoptionalInitial admin account password. Default: changeme123. Change before going live.
TESTSERVICEoptionalSet to true to auto-seed demo data on first boot (5 users, 3 repos, 7 reviews).
GitLab
GITLAB_URLoptionalBase URL of your GitLab instance, e.g. https://gitlab.example.com. Used as default for GitLab repos.
GITLAB_TOKENoptionalGitLab personal access token with api scope. Default token for fetching commits and diffs.
WEBHOOK_SECREToptionalSecret token for verifying GitLab webhook requests (X-Gitlab-Token header).
GITLAB_APP_IDoptionalGitLab OAuth application ID. Enables "Sign in with GitLab".
GITLAB_APP_SECREToptionalGitLab OAuth application secret.
GitHub
GITHUB_TOKENoptionalGitHub personal access token. Used as the default token for GitHub repos and auto-creation on push.
GITHUB_WEBHOOK_SECREToptionalGitHub webhook secret for HMAC-SHA256 signature verification (X-Hub-Signature-256). Recommended.
GITHUB_APP_IDoptionalGitHub OAuth App client ID. Enables "Sign in with GitHub".
GITHUB_APP_SECREToptionalGitHub OAuth App client secret.
Gitea & Forgejo
GITEA_URLoptionalDefault base URL for Gitea/Forgejo instances. Can be overridden per repository.
GITEA_WEBHOOK_SECREToptionalWebhook HMAC-SHA256 secret shared by both Gitea (X-Gitea-Signature) and Forgejo webhooks. Recommended.
Bitbucket Cloud
BITBUCKET_WEBHOOK_SECREToptionalWebhook HMAC-SHA256 secret for Bitbucket Cloud push events. Recommended.
Bitbucket Server / Data Center
BITBUCKET_SERVER_WEBHOOK_SECREToptionalWebhook HMAC-SHA256 secret for Bitbucket Server push events (X-Hub-Signature). Recommended.
Azure DevOps
AZURE_WEBHOOK_SECRETrequired for webhooksHTTP Basic Auth password checked against the Service Hook subscription (Azure DevOps has no HMAC signing option). Blank rejects all Azure webhook requests with 401.
Gerrit
GERRIT_WEBHOOK_SECRETrequired for webhooksHTTP Basic Auth password checked against the webhooks plugin's remote config (Gerrit has no HMAC signing option). Blank rejects all Gerrit webhook requests with 401.
SVN (Subversion)
SVN_WEBHOOK_SECRETrequired for webhooksHTTP Basic Auth password checked against the hooks/post-commit script (SVN has no HMAC signing option). Blank rejects all SVN webhook requests with 401.
Email (SMTP)
SMTP_HOSToptionalSMTP server hostname for outgoing email.
SMTP_PORToptionalSMTP port. Default: 587.
SMTP_USERoptionalSMTP authentication username.
SMTP_PASSWORDoptionalSMTP authentication password.
SMTP_FROMoptionalSender address for notification emails.
LDAP / Active Directory
LDAP_HOSToptionalLDAP server hostname. Enables LDAP authentication when set.
LDAP_PORToptionalLDAP port. Default: 389.
LDAP_BASEoptionalLDAP search base DN, e.g. dc=example,dc=com.
LDAP_BIND_DNoptionalDN of the bind user, e.g. cn=admin,dc=example,dc=com.
LDAP_PASSWORDoptionalPassword for the LDAP bind user.
LDAP_DOMAINoptionalDomain suffix used to construct email addresses from LDAP login names.
Symbol Indexer (Find Usages / Go to Declaration)
SYMBOL_INDEXER_TOKENoptionalShared secret required in the X-Indexer-Token header between app/sidekiq and the indexer container. Recommended.
INDEXER_MEM_LIMIToptionalMemory limit for the indexer container. Default: 768m.
INDEXER_CPUSoptionalCPU limit for the indexer container. Default: 1.0.
Redis Sentinel (optional)
REDIS_SENTINELSoptionalhost:port,host:port,...; port defaults to 26379 if omitted. Setting this switches Sidekiq/ActionCable from plain REDIS_URL to Sentinel-aware connections.
REDIS_MASTER_NAMEoptionalSentinel master group name to monitor. Default: mymaster.
REDIS_PASSWORDoptionalAuth for the actual Redis master/replica, if configured.
REDIS_SENTINEL_PASSWORDoptionalAuth for the Sentinel processes themselves, if configured.

Scaling & High Availability

The shipped docker-compose.yml runs one replica of each service — enough for a single team on a single host. app, sidekiq, lsp, and indexer are stateless and already safe to scale horizontally:

Not HA out of the box: db (PostgreSQL) and redis are single-node in this compose file, with no replication or automatic failover. For Postgres, point DATABASE_URL at an externally managed HA database (RDS Multi-AZ, Cloud SQL HA, or a self-managed Patroni cluster) instead of the bundled db service. Redis has a built-in HA option — see below.

Redis Sentinel (optional)

The default redis service is a single non-HA instance. To run Redis with automatic master failover instead, merge the included Sentinel topology on top of the base compose file:

docker compose -f docker-compose.yml -f docker-compose.sentinel.yml up -d

This adds a replica and three Sentinel instances (quorum 2 of 3) and points app/sidekiq at them via REDIS_SENTINELS/REDIS_MASTER_NAME instead of a fixed host. Both Sidekiq's and ActionCable's Redis clients are Sentinel-aware — they auto-discover the current master and reconnect after a failover without needing a restart. The base docker-compose.yml is unmodified either way; switching back is just dropping the second -f flag.

Variable Default Description
REDIS_SENTINELSunset (plain Redis)host:port,host:port,... — port defaults to 26379 if omitted. Setting this enables Sentinel mode; REDIS_URL is ignored once it's set.
REDIS_MASTER_NAMEmymasterThe Sentinel master group name to monitor.
REDIS_PASSWORDblankAuth for the actual Redis master/replica, if configured.
REDIS_SENTINEL_PASSWORDblankAuth for the Sentinel processes themselves, if configured (separate from the above).
Tearing the Sentinel stack back down: if a failover has actually happened while Sentinel was running, it reconfigures the original redis container as a replica of whichever node got promoted. Before reverting to plain docker-compose.yml, promote it back to a standalone master first — docker exec <redis container> redis-cli replicaof no one — otherwise it's left stuck read-only and Sidekiq will crash-loop.

Monitoring (optional)

A ready-made Prometheus + Grafana + Loki/Promtail stack, merged the same way as Redis Sentinel above:

docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d

One-time setup: create a service account (Settings → Users → New Service Account), copy its API token into monitoring/prometheus_token (see monitoring/prometheus_token.example — Prometheus reads the scrape credential from this file, never from a compose/env var), and set GRAFANA_ADMIN_PASSWORD in .env.

Grafana comes up at :3001 with two dashboards already provisioned — no manual datasource or import step:

Prometheus itself is exposed at :9090 for ad-hoc queries. Loki and Promtail have no exposed ports — Grafana talks to them over the internal Docker network only.

Redis data & backup

There's no scheduled backup job for Redis, unlike the Postgres Backup & Restore feature — by design. Everything durable (reviews, comments, users, the symbol index, audit log) lives in Postgres; Redis only holds Sidekiq's job queues and ActionCable's pub/sub, which are transient in-flight state, not the source of truth.

It's already persisted: the redis_data volume survives container restarts, and Redis's default RDB snapshot policy is active out of the box. AOF is off by default, so a hard crash can lose up to the last snapshot window — in practice a handful of in-flight jobs, never committed application data.

For a point-in-time snapshot anyway (e.g. before a risky upgrade):

docker compose exec redis sh -c "tar czf - -C /data ." > redis-backup-$(date +%Y%m%d).tar.gz
Next: GitLab Integration → ← All docs