Self-Hosting
Deploy Octopus on your own infrastructure. Your code never leaves your servers.
Prerequisites
PostgreSQL 15+
Primary database for all application data.
Qdrant
Vector database for code embeddings and search.
Node.js 20+ or Bun
Runtime for the Next.js application.
OpenAI API key (default embeddings)
Used for code embeddings (text-embedding-3-large) — or run fully local via Ollama (see the all-local section).
You'll also need an AI provider key (Anthropic Claude or OpenAI) for the review engine.
Quick Start
The fastest way to run Octopus is the prebuilt Docker image — it needs no build step and brings up the app, PostgreSQL, and Qdrant together. Prefer to run against your own services instead? Switch to Without Docker.
Clone the repository
git clone https://github.com/octopusreview/octopus.git
cd octopusCreate your .env file
Use the environment generator below to create a .env file with a pre-generated auth secret, then save it to the project root. Fill in your API keys before continuing.
Review docker-compose.selfhost.yml
The repository's docker-compose.selfhost.yml pulls the prebuilt public image ghcr.io/octopusreview/octopus-selfhost (no local build) and runs the webservice, PostgreSQL, and Qdrant together, with the database and Qdrant URLs wired for Docker's internal network. Use it as-is:
# Self-host deployment compose — pulls the prebuilt public image instead of
# building from source (see docker-compose.yml for the build-from-source dev
# variant). The image bakes NEXT_PUBLIC_OCTOPUS_SELF_HOSTED=true, so
# email/password sign-in is enabled out of the box.
#
# docker compose -f docker-compose.selfhost.yml pull
# docker compose -f docker-compose.selfhost.yml up -d
#
# Pin a release by exporting OCTOPUS_VERSION (e.g. OCTOPUS_VERSION=1.0.27);
# defaults to :latest. Migrations are NOT in the runtime image — run them from
# a checkout of the matching tag (see docs/self-hosting).
#
# Change the published port by exporting OCTOPUS_PORT (defaults to 43300):
# OCTOPUS_PORT=8080 docker compose -f docker-compose.selfhost.yml up -d
services:
web:
image: ghcr.io/octopusreview/octopus-selfhost:${OCTOPUS_VERSION:-latest}
ports:
- "${OCTOPUS_PORT:-43300}:3000"
# env_file delivers the operator's secrets into the container (the
# environment: block only overrides the compose-internal service URLs).
env_file:
- .env
environment:
- DATABASE_URL=postgresql://octopus:octopus@postgres:5432/octopus
- QDRANT_URL=http://qdrant:6333
- ENABLE_REVIEW_WORKERS=true
depends_on:
postgres:
condition: service_healthy
qdrant:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17-alpine
ports:
- "43332:5432"
environment:
POSTGRES_USER: octopus
POSTGRES_PASSWORD: octopus
POSTGRES_DB: octopus
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U octopus"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
qdrant:
image: qdrant/qdrant:v1.17.0
ports:
- "43333:6333"
- "43334:6334"
volumes:
- qdrant_data:/qdrant/storage
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:6333/readyz"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
qdrant_data:Pull and run
export OCTOPUS_VERSION=latest # or a pinned release, e.g. 1.0.27
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -dThe public image is built with NEXT_PUBLIC_OCTOPUS_SELF_HOSTED=true already baked in, so email/password sign-in and the first-boot admin work out of the box — no build step and no build args needed. (To build from source instead, use docker-compose.yml with docker compose build --build-arg NEXT_PUBLIC_OCTOPUS_SELF_HOSTED=true.)
Octopus is then available at http://localhost:43300. To publish on a different port, set OCTOPUS_PORT (defaults to 43300):
OCTOPUS_PORT=8080 docker compose -f docker-compose.selfhost.yml up -dRun database migrations
Migrations run from the repo checkout — the runtime image doesn't ship the Prisma CLI or migration files.
cd packages/db
DATABASE_URL=postgresql://octopus:octopus@localhost:43332/octopus bunx prisma migrate deployOpen Octopus
Visit http://localhost:43300 to access your self-hosted Octopus instance. Create your first account and connect a GitHub repository to get started.
All-local with Ollama (optional)
To run Octopus with no cloud API keys — both the review LLM and code embeddings served on your own hardware — start the optional Ollama overlay alongside the base compose file. It adds an ollama service and points the app at it.
docker compose -f docker-compose.yml -f docker-compose.ollama.yml up -dThen pull at least one chat model and the embedding model — from the UI (Settings → Models → Local models) or the shell:
docker compose exec ollama ollama pull qwen2.5-coder:7b
docker compose exec ollama ollama pull nomic-embed-textTo also use Ollama for embeddings, set OCTOPUS_EMBED_PROVIDER=ollama, OCTOPUS_EMBED_MODEL=nomic-embed-text, and OCTOPUS_EMBED_DIM=768 in your .env before first indexing — switching providers afterward requires a re-index since different models produce non-comparable vectors. Ollama runs CPU-only by default; see the overlay file for enabling NVIDIA GPU acceleration.
Environment Variables
Generate a default .env file with pre-filled defaults for database, Qdrant, and auth. A unique BETTER_AUTH_SECRET is generated automatically. Fill in the remaining values (API keys, GitHub App, etc.) before starting.
# Database (overridden by docker-compose when using Docker)
DATABASE_URL=postgresql://octopus:octopus@localhost:43332/octopus
# Qdrant (overridden by docker-compose when using Docker)
QDRANT_URL=http://localhost:43333
QDRANT_API_KEY=
# Auth
BETTER_AUTH_SECRET=b5f97888e482653ad67dda04b08cd7e6628bd8eff3b606afe6fc87049ae7d910
BETTER_AUTH_URL=http://localhost:43300
# Data encryption key (32 bytes hex). Encrypts OAuth tokens and per-org AI
# provider keys at rest. Decoupled from BETTER_AUTH_SECRET so the auth secret
# can rotate without invalidating encrypted data.
OCTOPUS_DATA_KEY=7f2da2581314dcc5db68d1a1fe7403bd7158c53bff3db631f243f1ada39644da
# AI Providers
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
# GitHub App
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY=
GITHUB_WEBHOOK_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
NEXT_PUBLIC_GITHUB_APP_SLUG=
# Optional
COHERE_API_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=Required — fill these in
OPENAI_API_KEYrequiredANTHROPIC_API_KEYGITHUB_APP_IDrequiredGITHUB_APP_PRIVATE_KEYrequiredGITHUB_WEBHOOK_SECRETrequiredGITHUB_CLIENT_IDGITHUB_CLIENT_SECRETPre-filled defaults
DATABASE_URLrequiredQDRANT_URLrequiredBETTER_AUTH_SECRETrequiredBETTER_AUTH_URLrequiredOptional
GOOGLE_API_KEYGROK_API_KEYOPENROUTER_API_KEYOLLAMA_SERVER_URLACP_BASE_URLOPENCODE_BASE_URLQDRANT_API_KEYCOHERE_API_KEYSTRIPE_SECRET_KEYDatabase Setup
Run migrations to set up the database schema. Migrations run from the repo checkout — the runtime image doesn't ship the Prisma CLI or migration files.
cd packages/db
DATABASE_URL=postgresql://octopus:octopus@localhost:43332/octopus bunx prisma migrate deployGitHub App Setup
To receive webhook events, you need to create a GitHub App:
- Go to
GitHub Settings → Developer settings → GitHub Apps - Create a new GitHub App with a webhook URL pointing to
https://your-domain/api/github/webhook - Enable permissions:
Pull requests(read/write),Contents(read),Checks(read/write) - Subscribe to events:
Pull request,Pull request review - Generate a private key and add it to your environment
Production Tips
Use connection pooling
Use PgBouncer or Supabase pooler for PostgreSQL connections. Multiple app instances and pg-boss worker processes can otherwise exhaust Postgres connection limits.
Secure Qdrant
Enable API key authentication on Qdrant and restrict network access. Never expose Qdrant directly to the internet.
Set a spend limit
Configure per-organization spend limits in the admin panel to control AI costs.
Enable HTTPS
Use a reverse proxy (nginx, Caddy, Traefik) with TLS termination. Required for OAuth callbacks.
Upgrading & rolling back
Check out a specific release tag in production rather than tracking master — that is what turns a rollback into re-checking-out the previous tag for the matching compose file and migrations. The runtime image itself is pulled from GHCR.
Upgrade
Check out the new tag (for the compose file + migration files), pull the new image, apply migrations, then roll. Octopus migrations are additive (expand-only) and therefore backward-compatible — the previous image keeps working against the new schema, which is exactly what makes the rollback below safe.
git fetch --tags && git checkout vX.Y.Z
export OCTOPUS_VERSION=X.Y.Z
docker compose -f docker-compose.selfhost.yml pull
# migrate FIRST (expand-only, safe under the still-running old version) ...
cd packages/db && DATABASE_URL=postgresql://octopus:octopus@localhost:43332/octopus bunx prisma migrate deploy && cd ../..
# ... then roll to the new version
docker compose -f docker-compose.selfhost.yml up -dVerify before sending traffic
Confirm the app is healthy before pointing users at the new version:
curl -fsS http://localhost:43300/api/health # expect {"status":"ok"}
curl -fsS http://localhost:43300/api/version # confirm the new versionRoll back (if needed)
Point OCTOPUS_VERSION at the previous release and roll. Do not roll back the database. Because every migration is additive, the older image runs fine against the newer schema, so you keep all data and avoid a risky down-migration.
export OCTOPUS_VERSION=X.Y.Z # the previous release
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -dThis expand-only discipline is enforced in CI: the migrate-checkworkflow fails any change whose migration drops or rewrites a table/column (without an explicit override), so the "roll back the code, keep the database" path stays safe from one release to the next. The same property is what lets a hosted, blue-green / rolling deploy run both versions against a single shared database during cutover.
Zero-downtime cutover (Cloudflare LB)
For zero-downtime deploys, front two legs (e.g. your datacenter and a cloud VM) with a Cloudflare Load Balancer. Both legs are stateless app containers pulling the same image and pointing at one shared database (and Qdrant/Redis) — Octopus is safe to run as multiple app instances on one DB, since pg-boss coordinates workers and dedupes the scheduled jobs across them.
- Create a Cloudflare LB with one
poolper leg and amonitorthat probes/api/health(it returns200only when the leg can reach the database, else503). Cloudflare drops an unhealthy leg from rotation automatically. - Deploy the new tag to the idle leg, let its
/api/healthgo green, then point the LB's default pool at it. Keep the previous leg running — rollback is an instant flip back. - The included
deployworkflow automates this (deploy → health-gate → Cloudflare cutover → verify); set the documented Cloudflare token / LB & pool IDs as repo secrets and variables.
Note: this is zero-downtime deploys against one shared DB. True survive-a-whole-site-outage HA additionally needs that database (and Qdrant) replicated across both legs with failover — a separate, larger piece of work.