Self-Hosting
Run Kody on your own infrastructure with full control over your data. Choose between Docker Compose (recommended), bare Node.js with systemd, or any other deployment method.
Requirements
- Node.js 18+ (22 LTS recommended) — only needed for bare metal deployment
- pnpm — install with
corepack enable pnpm - Docker and Docker Compose — for containerized deployment (recommended)
- An OpenAI-compatible AI endpoint — Ollama, vLLM, llama.cpp, OpenAI, or any compatible provider
- ~100 MB disk space for the application and SQLite database
Docker Compose (Recommended)
The easiest way to deploy Kody. The repository includes two Docker Compose files:
docker-compose.yml— development setup with Ollama, server, and webdocker-compose.prod.yml— production setup with health checks, restart policies, and env file support
Development
Starts an Ollama instance alongside the Kody server and web frontend:
git clone https://github.com/chafficui/kody.git
cd kody
docker compose up -dThis starts three services: Ollama on port 11434, the Kody server on port 3456, and the web frontend on port 3000. The SQLite database is persisted in a Docker volume.
Production
Create a .env file in the project root:
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=a-strong-password
NODE_ENV=production
PORT=3456
LOG_LEVEL=infoThen start with the production compose file:
docker compose -f docker-compose.prod.yml up -dThe production compose file includes automatic restarts (restart: always), health checks (hitting /health every 30s), and persists the SQLite database in a Docker volume at /data/kody.db.
Docker Images
Kody uses multi-stage Docker builds for minimal image sizes:
| Image | Dockerfile | Base | Description |
|---|---|---|---|
| kody-server | Dockerfile.server | node:22-alpine | API server + widget bundle. Builds shared, widget, and server packages. Production stage installs only production dependencies. Runs as non-root node user. |
| kody-web | Dockerfile.web | node:22-alpine | Next.js standalone output for the marketing/docs/admin site. Runs as non-root node user. |
Environment Variables
All environment variables are validated with Zod on server startup. Invalid values cause the server to exit with a descriptive error.
| Variable | Default | Description |
|---|---|---|
| PORT | 3456 | Port the server listens on. Range: 1-65535. Automatically coerced from string. |
| NODE_ENV | development | Must be development, production, or test. Set to production for deployments. |
| DATABASE_PATH | ./kody.db | Path to the SQLite database file. In Docker, this defaults to /data/kody.db (inside the volume). |
| ADMIN_EMAIL | — | Email for the initial admin account. Must be a valid email address. Required for first start to bootstrap the admin user. |
| ADMIN_PASSWORD | — | Password for the initial admin account. Minimum 8 characters. Hashed with argon2 before storage. Required for first start. |
| CORS_ALLOW_ALL_DEV | false | Skip origin checks in development. Automatically coerced from string. Never enable in production. |
| LOG_LEVEL | info | Logging verbosity: debug, info, warn, or error. |
Bare Metal Deployment
If you prefer not to use Docker, you can run Kody directly with Node.js:
git clone https://github.com/chafficui/kody.git
cd kody
pnpm install
pnpm build
export ADMIN_EMAIL="admin@example.com"
export ADMIN_PASSWORD="a-strong-password"
export NODE_ENV="production"
export PORT=3456
cd packages/server
node dist/index.jsThe server creates the SQLite database and runs migrations automatically on first start. The widget bundle is served from /widget.js and the admin dashboard from /admin — everything you need is in the single server process, no separate frontend required.
Reverse Proxy (nginx)
In production you should put Kody behind a reverse proxy that handles TLS termination. Here is an example nginx configuration:
server {
listen 443 ssl http2;
server_name kody.example.com;
ssl_certificate /etc/ssl/certs/kody.example.com.pem;
ssl_certificate_key /etc/ssl/private/kody.example.com.key;
location / {
proxy_pass http://127.0.0.1:3456;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support for streaming responses
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
}
}
server {
listen 80;
server_name kody.example.com;
return 301 https://$host$request_uri;
}proxy_buffering off) so that SSE streaming chat responses are delivered to the client in real time. Without this, nginx will buffer the entire response before sending it, making the chat appear to hang until the AI finishes generating.Database
Kody uses SQLite via better-sqlite3 — no external database server required. The database file is created at the path specified by the DATABASE_PATH environment variable (defaults to ./kody.db in bare metal, or /data/kody.db in Docker).
The database stores site configurations, admin users (with argon2-hashed passwords), session tokens, conversation history, and cached knowledge content. Migrations in packages/server/src/db/migrations/ are applied automatically when the server starts.
Backups
Since SQLite stores everything in a single file, backups are straightforward:
# Option 1: sqlite3 backup (safe while server is running)
sqlite3 kody.db ".backup kody-backup.db"
# Option 2: file copy (stop the server first)
cp kody.db kody-backup-$(date +%Y%m%d).db
# Option 3: Docker volume backup
docker compose exec server sqlite3 /data/kody.db ".backup /data/kody-backup.db"
docker cp $(docker compose ps -q server):/data/kody-backup.db ./kody-backup.dbsqlite3 .backup command for live backups — it creates a consistent snapshot even while the server is handling requests. File copies while the server is running may produce a corrupted backup.Process Management (systemd)
For bare metal deployments without Docker, use systemd to keep Kody running and automatically restart on crashes:
[Unit]
Description=Kody AI Chat Assistant
After=network.target
[Service]
Type=simple
User=kody
WorkingDirectory=/opt/kody/packages/server
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3456
Environment=DATABASE_PATH=/opt/kody/data/kody.db
Environment=ADMIN_EMAIL=admin@example.com
Environment=ADMIN_PASSWORD=change-me-on-first-run
Environment=LOG_LEVEL=info
[Install]
WantedBy=multi-user.target# Install the service
sudo cp kody.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable kody
sudo systemctl start kody
# View logs
sudo journalctl -u kody -fkody system user with no login shell and minimal permissions. The server only needs read access to its own directory and read/write access to the database path.Health Check
The server exposes a /health endpoint that returns a 200 status when the server is healthy. Use this for load balancer health checks, Docker health checks, and monitoring:
curl http://localhost:3456/healthThe production Docker Compose file includes a health check that hits this endpoint every 30 seconds with a 5-second timeout and 3 retries.
Updating
To update to a new version of Kody:
Docker
cd kody
git pull
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -dBare metal
cd /opt/kody
git pull
pnpm install
pnpm build
sudo systemctl restart kodyDatabase migrations are applied automatically on server start — no manual migration step is needed. Always back up the database before updating, just in case.