For self-hosters

For teams that have to self-host

Self-hosting buys control over where the code and the data live. It also transfers the pager. The real question is rarely cloud or self-hosted; it is which of three things you are actually required to keep inside your network.

This page is about what you will run into, not about why self-hosting is good. If the answer turns out to be "none of the three", a managed service is cheaper and you should take it.

You inherit the parts nobody demos

A self-hosted stack arrives with databases you now own: separate schemas for identity, each retrieval module, and the agent's checkpoint store. Backups, migrations, connection limits and the upgrade order between services become your problem the moment the first user signs in.

That is not an argument against self-hosting. It is an argument for counting the operational surface before you commit, because those pieces do not appear in a demo and they are the ones that page you at night.

Write the upgrade order down before the first deploy. Every module talks to Postgres, and a migration that runs in the wrong order is the failure mode that takes the longest to diagnose, because the symptom appears in a service that did not change.

Company-Brain/.env.example:5-15

One Postgres instance, five databases. You are the operator of all five.

# PostgreSQL admin connection used by scripts/init-db.ts.
POSTGRES_ADMIN_URL=postgres://<local-user>@localhost:5432/postgres

# Platform identity database used by apps/api and packages/identity.
IDENTITY_DATABASE_URL=postgres://<local-user>@localhost:5432/platform_identity_db

# Module databases.
NANO_BRAIN_DATABASE_URL=postgres://<local-user>@localhost:5432/nano_brain_db
TRADITIONAL_RAG_DATABASE_URL=postgres://<local-user>@localhost:5432/traditional_rag_db
GRAPH_RAG_DATABASE_URL=postgres://<local-user>@localhost:5432/graph_rag_db

Self-hosted runtime is not local inference

This is the fact that decides most budgets, and it is easy to miss because the word "self-hosted" sounds like "offline". Running the application inside your network does not mean the model runs there too.

In CompanyBrain the application, the databases and the retrieval modules are local, while embeddings and the agent model are called over an OpenAI-compatible endpoint. The default in the example environment is a hosted provider with an API key. Only the tokenizer is vendored locally, and the comment says why: to avoid a runtime download.

So the honest question is not "can we self-host it" but "may the text leave the network". If the answer is no, you also need a local embedding model and a local chat model, and the hardware budget changes by an order of magnitude.

The same split shows up in the agent path, where the model endpoint is configured separately from the retrieval pieces. Two external dependencies, two keys to rotate, and two providers to watch when latency moves.

Company-Brain/.env.example:34-39

The application is local; the embeddings are not. This is the line that decides your data-residency answer.

# Shared embedding configuration for all RAG modules.
# OpenRouter uses an OpenAI-compatible API.
EMBEDDING_PROVIDER=openai-compatible
EMBEDDING_BASE_URL=https://openrouter.ai/api/v1
EMBEDDING_API_KEY=<your-openrouter-api-key>
EMBEDDING_MODEL=qwen/qwen3-embedding-8b
Company-Brain/.env.example:47-50

The agent model takes the same route, so inference is a second external dependency, not a side effect of the first.

# Agent model configuration for LangChain agents.
# Keep AGENT_API_KEY local only; do not commit real secrets.
AGENT_PROVIDER=openai-compatible
AGENT_BASE_URL=https://openrouter.ai/api/v1

Three decisions, in this order

First, data egress: which fields may leave the network, and does a hosted embedding call violate that rule. Decide this before comparing vendors, because it eliminates most of them.

Second, integration depth: how many of your systems must talk to the stack, and over what. Every additional module is another process to supervise, another credential to rotate and another health check to write.

Third, on-call: who answers when a module dies at 2am. If the answer is a person who has never run Postgres, the honest plan is a managed database and a smaller self-hosted surface, not a bigger one.

A useful exercise is to write the three answers on one page and show them to whoever signs for the budget. If the page needs a fourth decision to be honest, the scope is bigger than the team thinks.

What a self-hosted boundary looks like in code

CompanyBrain is built around one boundary worth copying: the platform never reads a module's tables. It calls each module's HTTP API with an internal token, and the agent reaches each module through an MCP server.

That boundary has a price you will feel on day one. A stdio MCP connection is process-scoped, so one long-lived process cannot carry several users' tokens. The gateway therefore starts a separate MCP child process per agent session and injects that user's token through the environment. It is correct, and it means your process count grows with concurrency.

Company-Brain/apps/agent-gateway/src/sessions.ts:16-32

One MCP child process per agent session, with the user's token passed in through the environment.

export function buildPerUserMcpLaunchPlan(input: AgentSessionInput): McpSessionLaunchPlan {
  const descriptor = getModuleMcpDescriptor(input.activeModule);

  // stdio MCP is a process-level connection, so one long-lived process cannot
  // carry several users' tokens. Each agent session starts its own MCP child
  // process and injects the current user's token through the module's env.
  return {
    moduleId: input.activeModule,
    command: descriptor.command,
    env: {
      [descriptor.envTokenName]: input.bearerToken,
      FF_USER_ID: input.user.userId,
      FF_USERNAME: input.user.username,
      FF_IS_ADMIN: String(input.user.isAdmin),
    },
  };

What the bill actually contains

Three lines dominate: the machines that hold the databases and the module processes, the people who keep them alive, and the model calls that still leave the network.

The first two scale with your surface area, not with your traffic, which is why a smaller self-hosted footprint is usually the better first step: one database, one module, one process to supervise. Add the second module when the first one has survived a month.

The third line is the one teams forget to model. Embedding every document on ingest and every query at read time is a recurring cost that looks like a hosting bill and behaves like a utility.

When self-hosting is the wrong call

If you are one person without operations experience, self-hosting this stack turns into a second job: five databases, several Python services, a Node gateway, an MCP process per session, and a model endpoint you still depend on.

If your data is allowed to leave the network, the control you gain is mostly over deployment timing, and a managed service usually wins on cost. The case for self-hosting is strongest when data residency is a hard requirement and someone on the team actually enjoys running databases.