Codumentor logo Codumentor

Self-Hosting Walkthrough

This walkthrough takes you from nothing to a multi-user, workspace-isolated Codumentor instance — real logins, encrypted per-user secrets, sandboxed conversations, and a working plugin set — running behind a reverse proxy. It is the connective tissue between the reference pages; each step links to the detail it summarises.

The end state mirrors the project's own codumentor-selfdev-isolated.yaml: a base config plus an isolation overlay layered with extends.

Just want to try it on your laptop? Use Setup → Getting Started instead — single user, no auth, no isolation. Come back here when you're deploying for a team.

What you'll build

Step 1 — Install

Install from source (recommended for a server) or grab the portable build. See Installation for both paths. From source:

git clone <your-codumentor-remote> /opt/codumentor && cd /opt/codumentor
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt

Step 2 — Install host prerequisites for the features you want

Plugins are where most deployments get their value, and several need software on the host that pip install does not provide. Decide which features you want, then install their prerequisites using the matrix in Plugins in Production → Prerequisites at a glance.

For the isolated, plugin-rich target in this guide:

# Workspace isolation / secure push / sandboxed external agents (Linux only):
#   bubblewrap with overlay support — see the isolation prerequisites page,
#   it usually means building bwrap 0.8.0+ from source.
./src/codumentor/plugins/workspace_isolation/setup/check.sh   # verify, then setup.sh if needed

# Common tool plugins:
sudo apt install ripgrep subversion          # ripgrep + svn plugins
pip install playwright && playwright install chromium   # mermaid_render + webretrieve (JS)

Full details and the AppArmor note for Ubuntu 24.04+ are in Workspace isolation prerequisites.

Step 3 — Provide secrets as environment variables

Never put secrets in the config file. Codumentor reads ${VAR} references at load time. At minimum, for this guide:

# Encryption + session signing — generate once, keep stable for the deployment's life.
export CODUMENTOR_JWT_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"

# Local login passwords:
export CODUMENTOR_ADMIN_PASSWORD='…'
export CODUMENTOR_DEV_PASSWORD='…'

# Model / service keys, as needed by your config:
export OPENAI_API_KEY='…'        # chat/embeddings and/or speech
export TAVILY_API_KEY='…'        # websearch, if enabled

In production these live in a systemd EnvironmentFile or a secrets manager — see Security → Secrets Management. CODUMENTOR_JWT_SECRET is special: without it, sessions reset on every restart and the per-user secret / model-profile / OAuth stores are written unencrypted — see jwt_secret.

Step 4 — Write the base config

Create /opt/codumentor/codumentor.yaml. Point models at your LLM endpoint (any OpenAI-compatible API; for a self-hosted model see LLM Server) and list your repos:

repos:
  - url: https://gitea.example.com/org/main-app.git
    options:
      platform: gitea          # enables correct permalink generation
  - ssh://git@gitea.example.com:2244/org/sandbox.git
repos_dir: "../repos"

models:
  agent: "your-chat-model"
  agent_base_url: "https://your-llm-endpoint/v1/"
  agent_api_key: "${OPENAI_API_KEY}"
  embedding: "your-embedding-model"
  embedding_base_url: "https://your-llm-endpoint/v1/"
  embedding_api_key: "${OPENAI_API_KEY}"

vector_db:
  path: "./data/vector_db"

api:
  host: "127.0.0.1"            # behind the reverse proxy; see Step 7
  port: 2638

See Configuration for every option.

Step 5 — Add real logins (local auth)

For a small team without Gitea/LDAP, the local provider defines users in config with env-injected passwords. Add to the base config:

auth:
  provider: local
  jwt_secret: "${CODUMENTOR_JWT_SECRET}"
  local:
    users:
      - username: admin
        password: "${CODUMENTOR_ADMIN_PASSWORD}"
        email: admin@example.com
        roles: ["admin", "user", "power_user"]
      - username: dev
        password: "${CODUMENTOR_DEV_PASSWORD}"
        roles: ["user"]

Prefer pbkdf2_sha256$ hashes if you'd rather commit the config — see Authentication → Local. For Gitea or LDAP/AD instead, see the Authentication guide.

Step 6 — Enable plugins

Add a plugins: list to the base config. Start with a useful core and grow from the full catalog — remember each plugin's host prerequisites from Step 2.

plugins:
  - { module: codumentor.plugins.user_secrets,    class: UserSecretsPlugin, args: { enabled: true } }
  - { module: codumentor.plugins.ripgrep,         class: RipgrepPlugin }
  - { module: codumentor.plugins.explore,         class: ExplorePlugin, args: { enabled: true } }
  - { module: codumentor.plugins.context_summarization, class: ContextSummarizationPlugin, priority: 10, args: { max_context_tokens: 170000 } }
  - { module: codumentor.plugins.configuration_assistant, class: ConfigurationAssistantPlugin, args: { enabled: true } }
  # …add websearch, database, redmine, the Office editors, read_aloud (+ a speech: block), etc.

user_secrets is what makes per-user credentials work (and relies on jwt_secret from Step 3). For the read-aloud / voice features, add a top-level speech: block.

Step 7 — Layer the isolation overlay with extends

Rather than editing the base, keep isolation in a second file that inherits it. extends loads the parent first; plugins_overrides deep-merges by name and appends the rest, so you don't restate the base plugin list. (This is exactly how codumentor-selfdev-isolated.yaml is built.) See Config inheritance with extends.

Create /opt/codumentor/codumentor-isolated.yaml:

extends: codumentor.yaml

plugins_overrides:
  - module: codumentor.plugins.workspace_isolation
    class: WorkspaceIsolationPlugin
    priority: 5
    args:
      enabled: true
      persistent_home: true
      subagent_isolation: shared
      snapshots: { upper_dir_ttl: 2592000, cleanup_interval: 3600, min_free_gb: 5 }
      sandbox: { unshare_net: true }
  - { module: codumentor.plugins.scm_push, class: ScmPushPlugin, priority: 25, dependencies: ["workspace_isolation", "user_secrets"], args: { enabled: true } }
  # add svn / branch_switching here too if you use them

This is the file you run in production. (You can run the plain base config without isolation any time — useful for comparison or on a host without bubblewrap.)

Step 8 — Ingest, run, and put a proxy in front

# Index the repos once (and on a schedule / via webhooks later):
.venv/bin/python -m codumentor -c codumentor-isolated.yaml ingest

# Run the API:
.venv/bin/python -m codumentor -c codumentor-isolated.yaml api --host 127.0.0.1 --port 2638

For the systemd unit, nginx config (the SSE proxy headers are mandatory), data directories, and backups, follow Deployment. The API must stay on 127.0.0.1 with TLS terminated at the proxy.

Step 9 — Verify

  1. Browse to your proxied URL and log in as admin with CODUMENTOR_ADMIN_PASSWORD.
  2. Start a conversation and confirm answers cite your indexed repos.
  3. Confirm isolation is active: a shell/edit tool runs inside the sandbox (changes land in the overlay, not your real checkout). If startup logged a bubblewrap/overlay error, revisit Workspace isolation prerequisites.
  4. Check the startup log has no auth.jwt_secret … not set ERROR — if it does, your env var didn't reach the process and secrets are unencrypted.
  5. Open Settings → User Secrets and add a credential a plugin needs (e.g. a Redmine API key); confirm it saves.

Step 10 — Harden

Run through the Security checklist before opening the instance to users: TLS, loopback bind, shell_always_allow: false, restricted data-dir permissions, log rotation, backups, and trusted plugins only.

See also