Self-hosting¶
comindly runs as a server for a team from one container image: members sign in by email or with
codes, admins invite people and manage the tokens it uses, and the index lives in your
storage. Agents connect to it over HTTP. Docker, Kubernetes, ECS, Cloud Run or a VM
all work the same way, with the image ghcr.io/sayef/comindly (amd64 and arm64, a tag per release).
Settings¶
The server reads everything from its environment. Each secret can instead be a file named by the
same variable with _FILE (Docker and Kubernetes secrets, Vault, AWS Secrets Manager or Google
Secret Manager mounts), so rotating one is a change in your secret store and a restart.
| Variable | Required | Purpose |
|---|---|---|
COMINDLY_PUBLIC_URL |
yes | The address members open, e.g. https://comindly.example.com. Setting it turns server mode on. |
COMINDLY_ADMIN_EMAILS |
yes | Comma-separated admins. Always admins; they cannot be removed in the app. |
COMINDLY_ADMIN_TOKEN |
recommended | Your own key, at least 32 random characters: API access as the first admin, and sign-in when email is unavailable. |
COMINDLY_SECRET_KEY |
for stored tokens | Encrypts tokens and keys saved in the app. Without it, they can only come from the environment. |
COMINDLY_SECRET_KEY_PREVIOUS |
when rotating | The old key: on start, stored secrets are re-encrypted with the new one. |
COMINDLY_SMTP_URL |
for email sign-in | Mail server, e.g. smtps://user:password@smtp.example.com. Without it, members sign in with codes. |
COMINDLY_SIGN_IN |
optional | email or code; by default email when COMINDLY_SMTP_URL is set, else code. |
COMINDLY_SMTP_FROM |
with SMTP | Sender, e.g. comindly <comindly@example.com>. |
COMINDLY_INDEX_DIR |
yes | Where the index lives (see Storage). |
GH_TOKEN, GITLAB_TOKEN |
optional | Repository access, if you keep it with the deployment rather than in the app. GH_TOKEN is used for github.com, GITLAB_TOKEN for gitlab.com. |
COMINDLY_GITLAB_HOSTS |
for self-managed GitLab | Comma-separated hosts that also get GITLAB_TOKEN, e.g. gitlab.example.com. |
OPENAI_API_KEY |
optional | LLM key, likewise; COMINDLY_LLM_BASE_URL for any OpenAI-compatible endpoint. |
COMINDLY_TRUST_PROXY |
behind a proxy | true when a proxy you run sets X-Forwarded-For, so sign-in limits apply per client. |
Everything else (members, sessions, personal tokens, stored secrets) is in a SQLite file under
/data. Keep that volume, and back it up with the index.
Signing in¶
A server signs everyone in one way: by email, or by codes. The deployment decides.
By email (with COMINDLY_SMTP_URL): invitations are emailed links for one address that work once
within 7 days; to sign in, members ask for a link that works once, for 15 minutes. Opening a link asks
for a click, so mail scanners cannot use it up.
By code: an admin invites an address and hands over the invitation code it gets (once, for that address, within 7 days). With it, the person signs in for the first time and receives their own sign-in code, shown once, which they use with their email address from then on. They can make a new one under Settings → Your access; an admin can reset it for someone who lost theirs. Each new code signs the person out everywhere else. Codes are generated, never chosen; five wrong ones pause that address for 15 minutes from the same client only, so nobody can lock someone else out.
In both: admins named in COMINDLY_ADMIN_EMAILS can always sign in with the admin token too;
sessions last 30 days; removing a member ends their sessions, codes and tokens.
Agents¶
Agents reach the server's tools at https://<your server>/mcp (MCP over HTTP) with a personal
access token from Settings → Your access, which also shows the command or config for Claude Code,
Cursor, Codex and VS Code. For Claude Code:
claude mcp add --transport http --scope user comindly https://comindly.example.com/mcp \
--header "Authorization: Bearer <token>"
Nothing is installed on the developer's machine, and no storage credentials leave the server. A personal access token is for agents and scripts: it reads and writes the index and the ledger, but cannot manage the account, its tokens, members or the server's secrets; that needs signing in. Records an agent writes say which member it works for; after each re-index, agents get the new index without reconnecting.
Admins manage repositories, indexing, members and the server's tokens; members use everything else. Ledger records written in the app carry the member's address.
Tokens and keys¶
Settings → Integrations shows each token the server uses and where it comes from, never its value.
One the deployment provides wins and is read-only there; otherwise an admin pastes one, and it is
stored encrypted with COMINDLY_SECRET_KEY. Read-only tokens are enough: a GitLab group access token
with read_repository and read_api, or a fine-grained GitHub token with Contents and Metadata read.
Deploying¶
The image listens on port 7878 and keeps everything it writes under /data: give it a volume there,
its settings and secrets, and HTTPS in front.
Docker¶
docker run -d --name comindly -p 127.0.0.1:7878:7878 -v comindly:/data \
-e COMINDLY_PUBLIC_URL=https://comindly.example.com \
-e COMINDLY_ADMIN_EMAILS=you@example.com \
-e COMINDLY_INDEX_DIR=s3://bucket/comindly \
-e COMINDLY_ADMIN_TOKEN_FILE=/run/secrets/admin_token \
-e COMINDLY_SECRET_KEY_FILE=/run/secrets/secret_key \
-v /etc/comindly/secrets:/run/secrets:ro \
ghcr.io/sayef/comindly:latest
Pin a version tag (ghcr.io/sayef/comindly:0.2.0) for anything you rely on. Generate the two
secrets once, for example with openssl rand -base64 32, and keep them in your secret store.
Kubernetes¶
apiVersion: apps/v1
kind: Deployment
metadata: { name: comindly }
spec:
replicas: 1
strategy: { type: Recreate } # one writer per data volume
selector: { matchLabels: { app: comindly } }
template:
metadata: { labels: { app: comindly } }
spec:
serviceAccountName: comindly # its cloud role reads the bucket
containers:
- name: comindly
image: ghcr.io/sayef/comindly:0.2.0
ports: [{ containerPort: 7878 }]
env:
- { name: COMINDLY_PUBLIC_URL, value: https://comindly.example.com }
- { name: COMINDLY_ADMIN_EMAILS, value: you@example.com }
- { name: COMINDLY_INDEX_DIR, value: s3://bucket/comindly }
- { name: COMINDLY_TRUST_PROXY, value: "true" }
- { name: COMINDLY_ADMIN_TOKEN_FILE, value: /run/secrets/admin_token }
- { name: COMINDLY_SECRET_KEY_FILE, value: /run/secrets/secret_key }
volumeMounts:
- { name: data, mountPath: /data }
- { name: secrets, mountPath: /run/secrets, readOnly: true }
readinessProbe: { httpGet: { path: /api/auth/me, port: 7878 } }
volumes:
- name: data
persistentVolumeClaim: { claimName: comindly-data }
- name: secrets
secret: { secretName: comindly }
---
apiVersion: v1
kind: Service
metadata: { name: comindly }
spec:
selector: { app: comindly }
ports: [{ port: 80, targetPort: 7878 }]
Route your ingress to the service with TLS. On ECS, Cloud Run or Azure Container Apps the same
applies: the image, a volume at /data, the variables above, secrets from the platform's store, and
the task's or workload's role for the bucket.
Behind a proxy¶
Terminate TLS at your proxy or load balancer and forward to port 7878, keeping the original Host
header (agents' requests to /mcp are checked against it). Set COMINDLY_PUBLIC_URL to the
https:// address people open (sign-in cookies are then secure and host-only, and links in emails
point there) and COMINDLY_TRUST_PROXY=true so sign-in limits apply per client, using the address
your proxy appends to X-Forwarded-For.
Backups and upgrades¶
Back up the /data volume (members, sessions, tokens and stored secrets live in server.db) together
with the index location. To upgrade, start the new image tag on the same volume and settings; the
database upgrades itself.
Trying it locally¶
scripts/docker-local.sh # builds the Linux binary here, packages comindly:local, makes .secrets/
docker compose up
Open http://localhost:7878, sign in as admin@example.com and read the link at
http://localhost:8025 (Mailpit). COMINDLY_SIGN_IN=code docker compose up -d switches to codes;
the admin token for the first sign-in is in .secrets/admin_token. The index goes to a local S3-compatible store (SeaweedFS).
docker compose down -v removes everything.