Confer — deployment & self-hosting

How to run a full Confer instance yourself — on your laptop to try it, or on a server to share with others. Everything here is a real, tested path; nothing is aspirational.

Scope: this guide covers the single-instance, self-hosted setup. Public multi-tenant hosting, TLS termination, and federation hardening are out of scope for v0.1 — see docs/02-architecture.md for the architectural direction.

What you get

One command builds and starts the whole platform:

Service Image / build Role
client built from infra/client.Dockerfile Web UI + nginx reverse proxy (the only port exposed)
gateway built from infra/gateway.Dockerfile Hono API, A2A endpoints, WebSocket
migrate one-shot runs Drizzle migrations, then exits
postgres postgres:16-alpine primary datastore
redis redis:7-alpine sessions, rate limits, cache
nats nats:2-alpine message bus / fan-out
qdrant qdrant/qdrant:v1.12.0 vector search for the RAG knowledge base
minio minio/minio S3-compatible file storage

nginx (inside client) serves the SPA on port 80 and reverse-proxies /api, /ws, /a2a, and /.well-known to the gateway. The gateway’s own port (3000) is not published in production — everything goes through nginx on 80.

Prerequisites

git clone https://github.com/hyhmrright/Confer.git
cd Confer
cp .env.example .env
docker compose -f docker-compose.prod.yml up -d --build

The first build takes a few minutes. When it finishes:

  1. Open http://localhost.
  2. Click 注册 / Register and create the first account. (Registration is rate-limited to 3 attempts per hour per IP.)
  3. Go to Settings and add an LLM API key (Claude / OpenAI / DeepSeek / Qwen / Ollama). Keys are encrypted at rest with ENCRYPTION_KEY (AES-256-GCM) and are never sent to the client.

That’s it — you now have a working Agent. Talk to it in the web UI, add contacts, and consult peer Agents.

Check it’s healthy

docker compose -f docker-compose.prod.yml ps        # all services "running"/"healthy"; migrate is "exited (0)"
docker compose -f docker-compose.prod.yml logs -f gateway

Configuration

.env drives the production stack. The defaults in .env.example are functional for local use but insecure — change the secrets before exposing the instance to anyone else.

Variable Default (.env.example) Notes
JWT_SECRET change-me-in-production Change this. Signs user session tokens.
ENCRYPTION_KEY 64 zeros Change this. Must be 32 bytes as 64 hex chars. Generate: openssl rand -hex 32. Encrypts stored LLM keys.
POSTGRES_PASSWORD confer (compose default) Database password.
MINIO_ROOT_USER / MINIO_ROOT_PASSWORD confer / confer-secret Object storage credentials.
EXPOSE_PORT 80 Host port the web UI binds to. Set e.g. 8080 if 80 is taken.
TAVILY_API_KEY empty Optional fallback for web search; a per-user key in Settings takes precedence.

LLM / embedding / Tavily keys are not set in .env — they live encrypted per user in the database and are configured through the Settings UI. The .env keys are infrastructure secrets only.

After editing .env, apply it with:

docker compose -f docker-compose.prod.yml up -d

Updating

git pull
docker compose -f docker-compose.prod.yml up -d --build   # migrate re-runs automatically

Resetting (wipes all data)

docker compose -f docker-compose.prod.yml down -v          # -v also deletes the volumes

B. Local development (hot reload)

Run only the infra in Docker and the app code with Bun:

bun install
docker compose up -d            # infra only — Postgres, Redis, NATS, Qdrant, MinIO (ports published on localhost)
bun run db:migrate
bun run dev                      # gateway on :3000, client (Vite) on :1420

The dev docker-compose.yml publishes each infra port to localhost (5432, 6379, 4222, 6333, 9000/9001) so the locally-run gateway can reach them. See CONTRIBUTING.md for the full developer workflow and the isolated test stack.

Connecting the Claude Code plugin

The confer-a2a plugin talks to the gateway over HTTP. Point it at the right URL for your setup:

Your setup CONFER_GATEWAY_URL
One-command self-host (option A) http://localhost (nginx on port 80; the gateway’s 3000 is not published)
Local dev (option B) http://localhost:3000 (the default)
Remote instance https://your-host
/plugin marketplace add hyhmrright/Confer
/plugin install confer-a2a@confer
export CONFER_USERNAME=you
export CONFER_PASSWORD=secret
export CONFER_GATEWAY_URL=http://localhost   # match the table above

The peer Agents you consult must already be contacts of your account (adding a contact is the consent gate). Full plugin reference: plugins/confer-a2a/README.md.

Exposing the instance to others

The one-command stack is single-tenant and listens on plain HTTP. Before putting it on the public internet:

Troubleshooting

Symptom Likely cause / fix
port is already allocated on 80 Something else owns port 80. Set EXPOSE_PORT=8080 in .env and open http://localhost:8080.
Web UI loads but every request 500s Check docker compose -f docker-compose.prod.yml logs gateway. Most often JWT_SECRET or ENCRYPTION_KEY is empty — they have no compose default, so they must be present in .env.
migrate exits non-zero Postgres wasn’t healthy yet or DATABASE_URL is wrong. Re-run docker compose -f docker-compose.prod.yml up -d; migrate is idempotent.
Plugin: login failed / 401 Wrong CONFER_GATEWAY_URL (see the table — prod is port 80, not 3000), or wrong username/password.
Plugin: connection refused on :3000 You’re on the one-command setup; use http://localhost instead of :3000.
LLM calls fail No LLM key configured for your user. Add one in Settings.
Embedding/RAG errors See .claude/skills/rag-debug or run the rag-debug skill for Qdrant/embedding/MinIO diagnostics.

See also

← Back to Confer A2A · DID:web · RFC 9421 · NANDA