How to Configure Kaneo: Complete Environment Variable Guide for Self-Hosted Deployment
Kaneo uses a single .env file at the repository root to configure all API, web, and WebSocket services through environment variables consumed by the monorepo's services.
Kaneo is an open-source project management platform built as a monorepo. Understanding how to configure Kaneo properly requires knowing which environment variables control each service—authentication, database connections, CORS, and third-party OAuth providers—and how the codebase consumes these values at runtime.
This guide covers every required and optional variable, their implementation in source files, and practical examples for local development and production deployments.
Core Configuration Architecture
Configuration in Kaneo follows a centralized pattern. The .env file at the repository root feeds all services through process.env access in the API and build-time injection in the web client.
Key configuration files include:
.env.sample— template defining all available variables- [
apps/api/src/mcp/oauth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/mcp/oauth.ts) — SSO provider credential consumption - [
apps/api/src/ws/redis-broadcast-adapter.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/redis-broadcast-adapter.ts) — Redis WebSocket scaling - [
apps/web/src/lib/utils/api-url.ts](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/lib/utils/api-url.ts) — frontend API resolution
Required Environment Variables
These variables must always be set for Kaneo to start successfully.
| Variable | Purpose | Example Value |
|---|---|---|
AUTH_SECRET |
JWT signing secret (minimum 32 characters for Better Auth) | your-super-secret-32-char-key-here |
DATABASE_URL |
PostgreSQL connection string for Drizzle ORM | postgresql://kaneo:password@localhost:5432/kaneo |
KANEO_API_URL |
Absolute URL serving the API (used by web client and redirects) | http://localhost:1337 |
KANEO_CLIENT_URL |
Absolute URL serving the web frontend (CORS and redirect target) | http://localhost:5173 |
Authentication Secret Implementation
The AUTH_SECRET is consumed by Better Auth middleware throughout the API. The value must be cryptographically strong in production.
// apps/api/src/mcp/oauth.ts
const auth = betterAuth({
secret: process.env.AUTH_SECRET!,
// ... additional configuration
});
Database Connection Setup
Drizzle ORM initializes its PostgreSQL connection using DATABASE_URL directly.
// apps/api/src/database/client.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const db = drizzle(pool);
Optional Configuration Variables
These variables enable additional features or override defaults.
| Variable | Purpose | When Required |
|---|---|---|
CORS_ORIGINS |
Comma-separated allowed origins; empty string allows all (development only) | Production with strict CORS |
VITE_API_URL |
Build-time override for API endpoint in web client | Custom Vite dev server setup |
REDIS_URL |
Redis connection for multi-instance WebSocket broadcasting | Horizontal API scaling |
CORS Origin Configuration
The CORS_ORIGINS variable controls cross-origin resource sharing. Leave unset in development for permissive defaults; set explicitly in production.
// Internal middleware usage pattern
const corsOrigins = process.env.CORS_ORIGINS?.split(",") ?? "*";
Redis-Backed WebSocket Scaling
When REDIS_URL is present, Kaneo switches from in-memory to Redis pub/sub for WebSocket message broadcasting across multiple API instances.
// apps/api/src/ws/index.ts
import { createBroadcastAdapter } from "./broadcast-adapter";
export const broadcast = process.env.REDIS_URL
? createBroadcastAdapter("redis", process.env.REDIS_URL)
: createBroadcastAdapter("in-memory");
The Redis adapter implementation lives in [redis-broadcast-adapter.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/redis-broadcast-adapter.ts).
SSO Provider Configuration
Kaneo supports multiple OAuth providers for authentication. Each requires its own credential pair.
| Variable Pattern | Provider | File Reference |
|---|---|---|
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
GitHub OAuth | [oauth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/mcp/oauth.ts) |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
Google OAuth | [oauth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/mcp/oauth.ts) |
DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET |
Discord OAuth | [oauth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/mcp/oauth.ts) |
Provider configuration follows a consistent pattern in the OAuth module:
// apps/api/src/mcp/oauth.ts
providers: [
process.env.GITHUB_CLIENT_ID && {
name: "github",
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
// Additional providers follow same structure
].filter(Boolean),
Frontend API Resolution
The web client resolves its API endpoint through a utility that checks multiple sources in priority order.
// apps/web/src/lib/utils/api-url.ts
import { env } from "@/lib/env";
export const API_URL = env.VITE_API_URL ?? process.env.KANEO_API_URL!;
This feeds into the TanStack Query client initialization:
// apps/web/src/query-client/index.ts
import { API_URL } from "@/lib/utils/api-url";
import { QueryClient } from "@tanstack/react-query";
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }) => {
const res = await fetch(`${API_URL}${queryKey[0]}`);
return res.json();
},
},
},
});
Complete .env Example
Based on the .env.sample template, here is a production-ready configuration:
# Core URLs
KANEO_API_URL=https://api.kaneo.example.com
KANEO_CLIENT_URL=https://app.kaneo.example.com
# Security
AUTH_SECRET=your-production-secret-min-32-chars-long
# Database
DATABASE_URL=postgresql://kaneo:securepassword@db.example.com:5432/kaneo?sslmode=require
# CORS (production)
CORS_ORIGINS=https://app.kaneo.example.com
# Optional: Redis for scaling
REDIS_URL=redis://redis.example.com:6379
# Optional: SSO providers
GITHUB_CLIENT_ID=gh_client_xxx
GITHUB_CLIENT_SECRET=gh_secret_xxx
GOOGLE_CLIENT_ID=google_client_xxx
GOOGLE_CLIENT_SECRET=google_secret_xxx
Configuration Deployment Methods
Local Development
Copy .env.sample to .env, fill values, then run:
pnpm install
pnpm dev
Docker Compose
Environment variables pass through docker-compose.yml or an external .env file referenced with env_file:
services:
api:
env_file: .env
environment:
- NODE_ENV=production
Kubernetes
Use Secrets for sensitive values and ConfigMaps for non-sensitive configuration, mounting both as environment variables in pod specifications.
Summary
- Kaneo configuration is centralized in a single
.envfile at the repository root - Four variables are always required:
AUTH_SECRET,DATABASE_URL,KANEO_API_URL,KANEO_CLIENT_URL - Redis via
REDIS_URLenables horizontal scaling of WebSocket broadcasts - SSO providers use consistent
*_CLIENT_IDand*_CLIENT_SECRETpatterns inoauth.ts - The web client resolves API endpoints through
api-url.tswith fallback toKANEO_API_URL
Frequently Asked Questions
What happens if I don't set CORS_ORIGINS?
Leaving CORS_ORIGINS empty configures permissive CORS suitable for development. In production, this exposes your API to cross-origin requests from any domain. Set explicit origins matching your deployed web client URL.
How long should my AUTH_SECRET be?
Kaneo requires a minimum of 32 characters for AUTH_SECRET to satisfy Better Auth's security requirements. Generate cryptographically random secrets using openssl rand -hex 32 or similar tooling—never commit secrets to version control.
Can I run Kaneo without Redis?
Yes. Redis is strictly optional. Without REDIS_URL, Kaneo uses an in-memory broadcast adapter that works perfectly for single-instance deployments. Add Redis only when running multiple API replicas behind a load balancer.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →