Skip to content

C:\> Documentation

On-prem connector

For teams whose production database must never be reachable from the cloud: a slim container that runs inside your network, dials out to Codepanion, and executes the agent's read-only queries locally. No inbound ports, and your connection string never leaves your network.

Contents

How it works

The connector is outbound-only. It opens a long-poll control channel to Codepanion and waits. When the agent needs your database, the query travels down that channel, the connector runs it locally, with the same read-only enforcement Codepanion applies everywhere (a read-only connection plus a transaction that is always rolled back, a 5-second timeout, and a 1,000-row cap), and posts the result back. Your firewall stays closed.

Good to know

The database connection string is configured on your side, as an environment variable on the container. Codepanion never sees it, never stores it, and cannot connect to your database itself. Every query is executed by the connector, inside your network, under read-only enforcement that runs on your side of the channel.

Create a connector API key

The connector authenticates with an API key scoped connector. It is created through the same endpoint the Settings card uses, with "scope": "connector" added. The card itself mints ingest-scoped keys, so this one is created by calling the endpoint directly. It can't upload code, and an ingest-scoped key can't open the connector channel. The value is shown once. To rotate it, create a new API key before revoking the old one.

create a connector API key
POST /settings/ingest-tokens
{ "label": "datacentre-east", "scope": "connector" }

Run the connector

One container, three environment variables. Run it anywhere inside your network that can reach the database, on Docker, Kubernetes, or a VM with Docker installed:

Getting the image

The image is delivered on request during early access rather than pulled anonymously. Ask us and we send registry credentials for your organisation; run docker login ghcr.io with them on the host before the first pull, and the commands on this page work as written. A public, signed image is the plan once the update channel below ships.

docker run
docker run -d --restart unless-stopped \
  --name codepanion-connector \
  -e CODEPANION_URL="https://api.codepanion.app/api" \
  -e CONNECTOR_TOKEN="<your connector-scoped key>" \
  -e CONNECTOR_DB_CONNECTION_STRING="Server=db.internal;Database=prod;User Id=codepanion_reader;Password=...;" \
  ghcr.io/codepanion-app/connector:latest

Use a dedicated read-only database user, exactly as you would for a direct connection. Least privilege applies on your side too. SQL Server and PostgreSQL are both supported; the connector detects the provider from the connection string. The image is hardened: a chiseled, distroless-style runtime with no shell and no package manager, running as a non-root user.

Point the agent at it

Mark a database environment as connector-mode and the agent routes that environment's queries through the channel instead of a stored connection string:

mark an environment as connector-mode
POST /settings/databases/prod
{ "mode": "connector" }

Marking the environment is an API call today; the form on the Settings card creates and edits direct connections. The list under it shows the result: a connector environment carries a Connector badge in place of a provider and a last test, because nothing on our side holds a connection string for it or can test one.

One database connection per environment
Settings → Database Connections. The production row is reached through the connector: no provider, no connection test, and no credential stored on our side. The other rows are direct connections and a catalog.
One database connection per environment
Screenshot of One database connection per environment. Settings → Database Connections. The production row is reached through the connector: no provider, no connection test, and no credential stored on our side. The other rows are direct connections and a catalog.
Settings → Database Connections. The production row is reached through the connector: no provider, no connection test, and no credential stored on our side. The other rows are direct connections and a catalog.

One connector environment per tenant. The channel addresses a connector by tenant, and a container serves the single database it is pointed at, so marking a second environment as connector-mode returns a 409 instead of quietly answering staging queries from production. Tell us if you need two and we will build the routing around what you actually run.

If the connector isn't running when the agent asks, the query fails fast with a clear "no connector connected" answer. Nothing hangs, and the agent tells the support engineer what's wrong.

Catalog resolver (multi-tenant databases)

If your real database is looked up per tenant from a catalog (a registry table that maps a tenant key to its database) rather than living at one fixed connection string, the connector can resolve it for you, entirely inside your network. Point the connector at your local catalog database, and a small resolver snippet turns a tenant key into the actual connection string. The connector then runs the agent's query against the resolved database and posts back only the rows. The resolved per-tenant connection string never crosses the channel. We hold nothing.

add the local catalog
docker run -d --restart unless-stopped \
  --name codepanion-connector \
  -e CODEPANION_URL="https://api.codepanion.app/api" \
  -e CONNECTOR_TOKEN="<your connector-scoped key>" \
  -e CONNECTOR_CATALOG_CONNECTION_STRING="Server=catalog.internal;Database=registry;User Id=codepanion_reader;Password=...;" \
  ghcr.io/codepanion-app/connector:latest

Mark the environment as both connector-mode and catalog, supplying the resolver snippet and the lookup key:

configure a catalog connector environment
POST /settings/databases/prod
{
  "mode": "connector",
  "kind": "catalog",
  "resolverJs": "return connection.query({ table: 'tenants', where: [{ column: 'key', op: '=', value: resolveKey }], limit: 1 })[0].cs;",
  "resolveKey": "acme"
}

What runs, and how it's contained

The resolver runs on your connector, in a hardened sandbox: a JavaScript interpreter with no access to the host, the filesystem, or .NET, offering only a read-only connection.query() against your catalog, under strict time, memory, and statement limits. It is the one place the connector executes a snippet that came from Codepanion. If you would rather the connector never run a snippet delivered over the channel, pin your own resolver locally with CONNECTOR_RESOLVER_JS (inline) or CONNECTOR_RESOLVER_JS_FILE (a file path). A pinned resolver always wins, and the connector logs which source is active at startup.

Logs through the connector

The channel carries more than SQL. An environment's log source can be marked connector-mode too, and the agent's search_logs and get_log_context calls then run inside your network against a log store we never reach. Marking it is Settings work, set out under on-prem logs. What changes on this side is the container.

A log store the connector reaches with its own key needs two more variables, and they must be set together or the connector refuses to start:

add a node-local log store
docker run -d --restart unless-stopped \
  --name codepanion-connector \
  -e CODEPANION_URL="https://api.codepanion.app/api" \
  -e CONNECTOR_TOKEN="<your connector-scoped key>" \
  -e CONNECTOR_SEQ_URL="https://seq.internal" \
  -e CONNECTOR_SEQ_APIKEY="<a Seq API key that never leaves your network>" \
  ghcr.io/codepanion-app/connector:latest

The two ambient identities need no variable at all. With Azure Managed Identity or an AWS IAM role the connector queries Log Analytics or CloudWatch Logs as the host it runs on, so the grant is made once, on your infrastructure, to a workload you control. We store nothing to revoke.

On those two the workspace ID or log group is named in Settings and travels with each call, because it is ordinary non-secret configuration rather than a credential. The connector reads only what its host identity is already permitted to read, so scope that identity to exactly the workspaces and log groups you want reachable. A Seq endpoint is different: its URL and key come from the container's own environment and are never sent to us.

A connector can serve logs and nothing else: leave CONNECTOR_DB_CONNECTION_STRING unset and a query or schema call is answered with a clear "not configured" result rather than an obscure failure. On an ambient identity that makes a logs-only node two variables in total, CODEPANION_URL and CONNECTOR_TOKEN.

Network requirements

Outbound only. No inbound rules, no port forwarding, no VPN. If your egress is allow-listed, permit HTTPS (443) to these three, plus your own database and log store:

egress allow-list
api.codepanion.app                    # the control channel
ghcr.io                               # pulling connector images
pkg-containers.githubusercontent.com  # GHCR's blob storage (image layers)

The two registry hosts cover both your initial docker pull and the automatic updates once they ship.

An ambient-identity log source needs its cloud's own endpoints as well, since the connector calls them directly as the host: login.microsoftonline.com and api.loganalytics.io for Azure Monitor, or logs.<region>.amazonaws.com for CloudWatch Logs. A node-local Seq needs no egress rule at all.

What we can and can't reach

Through the connector, the agent can do two things: run read-only queries expressed in Codepanion's structured query language, and read your database schema (tables, columns, keys, indexes). That's it. It cannot write: the connector wraps every query in a read-only connection and a transaction that is always rolled back. It cannot run arbitrary SQL, because queries arrive as a structured definition that is compiled and parameterised locally, against your actual schema. And it cannot reach anything else on your network: the connector connects only to the database(s) you configured, and nothing in the protocol can change that from our side.

Logs are searching only, and a Seq store is pinned the same way a database is: its URL and key are read from the container's own environment, so nothing in the protocol can aim the connector at a different one. The two ambient identities work differently, and the section above says how. The workspace ID or log group is named in Settings and travels with the call, bounded by whatever the host identity you granted is allowed to read.

The exception on the database side is the optional catalog resolver. If you enable it, the connector runs a resolver snippet and then dials whichever connection string that snippet returns, so the snippet decides which database is reached rather than a fixed environment variable. The snippet runs in a hardened, locked-down sandbox (no host, filesystem, or .NET access; read-only catalog queries only; strict resource limits), and the resolved string never leaves your network. To fix the reachable set on your side as well as the code, pin your own resolver with CONNECTOR_RESOLVER_JS. See that section.

Updates

The connector stamps its version on every request it makes, so we can see who is running what. Today, updating is a normal docker pull + restart (or your orchestrator's rolling update).

Coming: automatic updates

The connector is designed to keep itself current so you don't have to: roughly every 24 hours it will check ghcr.io for a newer image, verify the publisher's cosign signature, pin the exact image digest it verified, and restart onto it, so the update channel can't be hijacked between check and run. Until that ships, releases are infrequent by design (the connector is deliberately tiny) and we email connector customers when one is out.

Need help getting set up?

We do the first setup on a call with you: the CI step, the connection string, and a first investigation against your real code.