Deployment

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.

1

Clone the repository

git clone https://github.com/octopusreview/octopus.git
cd octopus
2

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

3

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:

docker-compose.selfhost.yml
# 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:
4

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 -d

The 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 -d
5

Run 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 deploy
6

Open 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 -d

Then 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-text

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

.env
# 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_KEYrequired
Used for embeddings
ANTHROPIC_API_KEY
Claude for reviews
GITHUB_APP_IDrequired
123456
GITHUB_APP_PRIVATE_KEYrequired
-----BEGIN RSA...
GITHUB_WEBHOOK_SECRETrequired
whsec_...
GITHUB_CLIENT_ID
Iv1.abc123
GITHUB_CLIENT_SECRET
secret

Pre-filled defaults

DATABASE_URLrequired
postgresql://octopus:octopus@localhost:43332/octopus
QDRANT_URLrequired
http://localhost:43333
BETTER_AUTH_SECRETrequired
Auto-generated (64-char hex)
BETTER_AUTH_URLrequired
http://localhost:43300

Optional

GOOGLE_API_KEY
Gemini models
GROK_API_KEY
xAI Grok models
OPENROUTER_API_KEY
OpenRouter — many model vendors via one key
OLLAMA_SERVER_URL
Self-hosted Ollama (ollama: model ids); optional OLLAMA_USERNAME / OLLAMA_PASSWORD for a proxied host
ACP_BASE_URL
ACPX gateway (acp: model ids); set together with ACP_API_KEY
OPENCODE_BASE_URL
OpenCode gateway (opencode: model ids); set together with OPENCODE_API_KEY
QDRANT_API_KEY
If Qdrant auth is enabled
COHERE_API_KEY
For reranking search results
STRIPE_SECRET_KEY
For billing

Database 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 deploy

GitHub App Setup

To receive webhook events, you need to create a GitHub App:

  1. Go to GitHub Settings → Developer settings → GitHub Apps
  2. Create a new GitHub App with a webhook URL pointing to https://your-domain/api/github/webhook
  3. Enable permissions: Pull requests (read/write), Contents (read), Checks (read/write)
  4. Subscribe to events: Pull request, Pull request review
  5. 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.

1

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 -d
2

Verify 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 version
3

Roll 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 -d

This 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 pool per leg and a monitor that probes /api/health (it returns 200 only when the leg can reach the database, else 503). 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.