Self Host - Getting Started
This guide covers the standard self-hosted Kanera setup using Docker Compose.
Use it when you want to run Kanera on your own server with your own domains, database, uploads, backups, public API, and MCP endpoint.
Requirements
You need:
- A server with Docker and Docker Compose.
- A domain pointing at the server.
- HTTPS in front of Kanera, usually through Caddy, Traefik, nginx, Dokploy, or a cloud load balancer.
- SMTP credentials if you want Kanera to send email.
- A plan for backing up PostgreSQL data and uploaded files.
Deployment shape
The standard compose file runs these services:
| Service | Purpose |
|---|---|
postgres | PostgreSQL database with persistent data in kanera_pgdata. |
valkey | Required Valkey instance for realtime fanout, presence, and shared rate limits. |
migrate | One-shot database migration service that runs before app services start. |
api | Main Kanera API and realtime server. |
worker | Single background worker for schedulers, webhook delivery, notifications, cleanup, and realtime outbox fallback. |
public-api | Public integration API. |
mcp | MCP Streamable HTTP server backed by the public API. |
web | Built Kanera web app served by nginx. |
db-backup | Optional encrypted PostgreSQL backup scheduler. |
The browser should connect to the web service. Do not expose the main api service directly; the web nginx container proxies app API and Socket.IO traffic internally.
Expose public-api only if you want external integrations. Expose mcp only if you want remote MCP clients to connect over HTTP.
Recommended domains
Use separate domains for the app, public API, and MCP when you expose all three:
| Domain | Service | Use |
|---|---|---|
kanera.example.com | web | The Kanera web app. |
api.kanera.example.com | public-api | REST API, webhooks, and API reference. |
mcp.kanera.example.com | mcp | Remote MCP endpoint for AI clients. |
The public API and MCP domains are optional. You can start with only the web app and add the others later.
Create the environment file
On the server, copy the example environment file:
cp .env.example .env
Set the required production values:
WEB_ORIGIN=https://kanera.example.com
COOKIE_DOMAIN=kanera.example.com
COOKIE_SECURE=true
KANERA_ENVIRONMENT=production
JWT_SECRET=<openssl rand -hex 32>
MEDIA_SIGNING_SECRET=<openssl rand -hex 32>
SECRETS_ENCRYPTION_KEY=<openssl rand -hex 32>
Keep these secrets stable across redeploys. Changing JWT_SECRET signs users out. Changing MEDIA_SIGNING_SECRET invalidates existing signed media URLs. SECRETS_ENCRYPTION_KEY protects stored integration secrets and should be distinct from JWT_SECRET.
Self-hosted mode is the default:
KANERA_DEPLOYMENT_MODE=self_hosted
Do not set hosted Stripe billing variables unless you are running a Kanera SaaS-style hosted deployment.
Configure email
SMTP is optional, but recommended for invites, notifications, verification, and operational use.
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_PASSWORD=your-password
SMTP_FROM_NAME=Kanera
Leave email verification disabled until outbound mail is confirmed working:
EMAIL_VERIFICATION_ENABLED=false
After email is working, you can set EMAIL_VERIFICATION_ENABLED=true to require verification for signup, invite signup, and email changes.
Publish ports
The default compose file keeps services private. If you are running Docker directly on a server, add a compose.override.yml file:
services:
web:
ports:
- "8080:80"
public-api:
ports:
- "3001:3001"
mcp:
ports:
- "3002:3002"
Then point your HTTPS reverse proxy at:
| Public URL | Upstream |
|---|---|
https://kanera.example.com | http://127.0.0.1:8080 |
https://api.kanera.example.com | http://127.0.0.1:3001 |
https://mcp.kanera.example.com | http://127.0.0.1:3002 |
Only publish public-api and mcp when you need them.
Trust proxy settings
Set these when Kanera is behind a trusted reverse proxy such as nginx, Traefik, Dokploy, or an ingress that sends the real client IP:
API_TRUST_PROXY=true
PUBLIC_API_TRUST_PROXY=true
API_TRUST_PROXY helps app authentication rate limits use the real client IP. PUBLIC_API_TRUST_PROXY does the same for public API rate limits.
Leave them false only when the service is directly internet-facing or directly Cloudflare-facing.
Start Kanera
Build and start the deployment:
docker compose up -d --build
The migration service runs pending database migrations once before api, worker, and public-api start. The MCP service starts after the public API health check passes.
Check the deployment
Open your web domain in a browser:
https://kanera.example.com
Useful checks:
docker compose ps
docker compose logs -f api
docker compose logs -f worker
docker compose logs -f public-api
docker compose logs -f mcp
Health checks:
curl https://kanera.example.com/api/health
curl https://api.kanera.example.com/health
curl https://mcp.kanera.example.com/health
The public API and MCP checks only apply if those services are exposed.
Public API
The public API service is used for integrations, webhooks, and API docs.
When exposed, the API reference is available at:
| Path | Use |
|---|---|
/docs | Interactive Scalar reference. |
/swagger | Swagger UI reference. |
/openapi.json | OpenAPI document for SDK generation. |
For example:
https://api.kanera.example.com/docs
See API for API keys, first requests, webhooks, and signature verification.
MCP
The MCP service lets AI clients connect to Kanera over Streamable HTTP.
When exposed, the endpoint is:
https://mcp.kanera.example.com/mcp
Set the public MCP URL when exposing it:
MCP_SERVER_PUBLIC_URL=https://mcp.kanera.example.com/mcp
In Docker Compose, keep the internal MCP-to-public-API URL as:
KANERA_PUBLIC_API_URL=http://public-api:3001
MCP clients authenticate with Kanera workspace API keys using:
Authorization: Bearer kanera_live_...
See AI MCP for client setup and MCP capabilities.
Upload storage
By default, uploaded files are stored in the kanera_uploads Docker volume.
For S3-compatible storage, set:
S3_REGION=auto
S3_BUCKET=kanera
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_ENDPOINT=https://s3.example.com
S3_PUBLIC_URL_PREFIX=https://cdn.example.com/kanera
When the required S3 values are set, S3 takes precedence over local upload storage.
Backups
Back up both:
| Data | Location |
|---|---|
| PostgreSQL data | kanera_pgdata Docker volume. |
| Uploaded files | kanera_uploads Docker volume, unless S3 storage is configured. |
Manual PostgreSQL backup:
docker compose exec -T postgres pg_dump -U kanera kanera | gzip > kanera-$(date +%F).sql.gz
Kanera can also run encrypted full PostgreSQL backups to S3-compatible storage:
DB_BACKUPS_ENABLED=true
DB_BACKUP_ENCRYPTION_PASSPHRASE=<openssl rand -hex 32>
DB_BACKUP_TIMES_UTC=00:15,12:15,16:45
DB_BACKUP_RETENTION_DAYS=14
DB_BACKUP_S3_PREFIX=backups/postgres
The backup service compresses the dump, encrypts it with GPG symmetric AES-256, uploads it to S3, and prunes old backups after successful uploads. Store DB_BACKUP_ENCRYPTION_PASSPHRASE somewhere durable outside the deployment too; it is required to restore backups.
Updates
For a normal Docker deployment:
git pull
docker compose up -d --build api public-api mcp web
If a release includes database changes, the migration service runs before app services serve traffic.
For Dokploy, redeploy the application after pushing or pulling the latest code. Dokploy rebuilds from docker-compose.yml.
Dokploy notes
Dokploy is a good fit for Kanera because it can run the Docker Compose application and terminate HTTPS through Traefik.
In Dokploy:
- Create a project and application.
- Choose Docker Compose.
- Connect the Kanera repository.
- Set the compose file path to
docker-compose.yml. - Add the production environment variables.
- Deploy once.
- Add domains for
web,public-api, andmcpas needed.
Use these domain routes:
| Domain | Service | Container port |
|---|---|---|
kanera.example.com | web | 80 |
api.kanera.example.com | public-api | 3001 |
mcp.kanera.example.com | mcp | 3002 |
Do not create a public domain for the main api service.
Scaling notes
The default deployment uses multiple app API replicas and one worker.
API_REPLICAScontrols app API process count. Defaults to2.PUBLIC_API_REPLICAScontrols public API process count. Defaults to1.MCP_REPLICAScontrols MCP process count. Defaults to1.- Keep
workerat exactly1. - Valkey is required for realtime fanout, presence, and shared rate limits.
Raise replicas only when the server has enough CPU, memory, and database connection capacity. If you raise API_REPLICAS, review PG_POOL_MAX and PostgreSQL max_connections.
Common issues
| Problem | What to check |
|---|---|
| Users are signed out after login. | Confirm COOKIE_SECURE=true, COOKIE_DOMAIN, and WEB_ORIGIN match the public HTTPS domain. |
/api/health fails on the web domain. | Confirm the domain routes to the web service on port 80, not directly to api. |
| Public API does not respond. | Confirm the domain routes to public-api on port 3001. |
| MCP does not respond. | Confirm the domain routes to mcp on port 3002, MCP_SERVER_PUBLIC_URL points at the public /mcp URL, and KANERA_PUBLIC_API_URL is http://public-api:3001 inside compose. |
| Rate limits affect everyone at once. | Confirm API_TRUST_PROXY=true and PUBLIC_API_TRUST_PROXY=true when behind a trusted proxy. |
| Email verification blocks users. | Leave EMAIL_VERIFICATION_ENABLED=false until SMTP is working. |
| Uploads disappear after redeploy. | Confirm kanera_uploads is persistent or configure S3-compatible storage. |