How to Deploy Kaneo to Production: A Complete Docker-Based Guide
Deploy Kaneo to production by building the multi-stage Docker image from Dockerfile.kaneo and orchestrating services with compose.yml, which packages a Hono API, a React frontend, and Nginx into a single production-ready container.
Kaneo is an open-source project management platform distributed as a Docker-based monorepo. The production deployment follows a containerized approach that bundles the backend API (Hono + PostgreSQL) and frontend web app (React + Vite) behind an Nginx reverse proxy. This guide walks through each step required to deploy Kaneo to production based on the official source code in usekaneo/kaneo.
Prerequisites and Environment Configuration
Before building the container image, you must configure the runtime environment. Create a .env file at the repository root with the mandatory variables listed in [ENVIRONMENT_SETUP.md](https://github.com/usekaneo/kaneo/blob/main/ENVIRONMENT_SETUP.md).
Required Environment Variables
| Variable | Purpose |
|---|---|
KANEO_CLIENT_URL |
Public URL where users access the web UI (e.g., https://kaneo.example.com) |
KANEO_API_URL |
Public URL for API requests (e.g., https://api.kaneo.example.com) |
AUTH_SECRET |
JWT signing secret—must be at least 32 bytes |
DATABASE_URL |
PostgreSQL connection string for the API |
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD |
Credentials for the PostgreSQL instance |
CORS_ORIGINS |
Optional comma-separated list of allowed origins for cross-origin requests |
# Create a production-ready .env file
cat > .env <<'EOF'
KANEO_CLIENT_URL=https://kaneo.example.com
KANEO_API_URL=https://api.kaneo.example.com
AUTH_SECRET=$(openssl rand -hex 32)
DATABASE_URL=postgresql://kaneo_user:secure_password@db:5432/kaneo
POSTGRES_DB=kaneo
POSTGRES_USER=kaneo_user
POSTGRES_PASSWORD=secure_password
CORS_ORIGINS=https://kaneo.example.com
EOF
The AUTH_SECRET is critical for production security. Generate it with cryptographically secure methods like OpenSSL as shown above.
Building the Production Docker Image
Kaneo's production build uses a multi-stage Dockerfile defined in Dockerfile.kaneo. This approach minimizes the final image size by separating build and runtime dependencies.
Build Stages Explained
api-builder— Compiles the Hono API and installs Node.js production dependenciesweb-builder— Builds the React SPA using Vite with optimized assetsruntime— Assembles a minimal Alpine Linux base, copies compiled binaries and static files, installs only production Node.js modules, and configures Nginx to serve the web UI and proxy API requests
Build the image with an explicit tag for version tracking:
docker build -f Dockerfile.kaneo -t kaneo:latest -t kaneo:$(git rev-parse --short HEAD) .
The build process typically completes in 2-5 minutes depending on network conditions and CPU resources.
Deploying with Docker Compose
The [compose.yml](https://github.com/usekaneo/kaneo/blob/main/compose.yml) file defines the production service topology. It orchestrates two primary containers:
api— Runs the Hono backend on internal port 1337web— Serves the React SPA through Nginx on exposed port 5173
Starting the Stack
# Start all services in detached mode
docker compose -f compose.yml up -d
# View real-time logs
docker compose -f compose.yml logs -f
# Scale the API horizontally (if supported by your DATABASE_URL configuration)
docker compose -f compose.yml up -d --scale api=2
The compose file automatically handles container networking, allowing the web service to proxy requests to api:1337 internally. For PostgreSQL, either use an external database by pointing DATABASE_URL to your managed instance, or extend compose.yml to include a db service with persistent volume mounts.
Health Verification and Monitoring
The Dockerfile.kaneo includes a built-in health check that polls the API's health endpoint before marking the container as healthy.
Verification Commands
# Check container health status
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Health}}"
# Inspect detailed health state
docker inspect --format='{{.State.Health.Status}}' <web-container-id>
# Test the API health endpoint directly
curl -s http://localhost:5173/api/health | jq .
A successful deployment returns HTTP 200 with a JSON payload confirming database connectivity and service readiness. Monitor logs for startup errors, particularly database connection failures or JWT configuration issues.
Securing Production with TLS
The Docker Compose setup exposes plain HTTP on port 5173. For production deployments, terminate TLS at a reverse proxy or load balancer positioned in front of the Kaneo stack.
Recommended TLS Architectures
- Traefik — Automatic Let's Encrypt certificates with Docker labels
- Caddy — Simplified configuration with automatic HTTPS
- Cloud load balancer — AWS ALB, GCP Cloud Load Balancing, or Azure Front Door for managed certificate handling
- Kubernetes Ingress — If deploying via the Helm chart in
charts/kaneo/
Example Traefik configuration:
# labels added to compose.yml web service
labels:
- "traefik.enable=true"
- "traefik.http.routers.kaneo.rule=Host(`kaneo.example.com`)"
- "traefik.http.routers.kaneo.tls.certresolver=letsencrypt"
- "traefik.http.services.kaneo.loadbalancer.server.port=5173"
Alternative: Kubernetes Deployment
For organizations running container orchestration platforms, Kaneo provides a Helm chart in charts/kaneo/. This packages the same container image with Kubernetes-native resources including Deployments, Services, Ingress rules, and ConfigMaps for environment management.
helm upgrade --install kaneo ./charts/kaneo \
--namespace kaneo-prod \
--create-namespace \
--set ingress.host=kaneo.example.com \
--set database.url=postgresql://...
The Helm chart supports horizontal pod autoscaling, pod disruption budgets, and secret management through external tools like Sealed Secrets or External Secrets Operator.
Summary
- Deploy Kaneo to production by building the multi-stage
Dockerfile.kaneoimage and runningdocker compose -f compose.yml up -d - Configure required variables in
.envincludingKANEO_CLIENT_URL,KANEO_API_URL,AUTH_SECRET, andDATABASE_URL - Verify deployment health using the built-in container health check and
/api/healthendpoint - Terminate TLS externally using Traefik, Caddy, cloud load balancers, or Kubernetes Ingress rather than modifying the container
- Consider Helm charts for Kubernetes environments needing advanced orchestration features
Frequently Asked Questions
What ports does Kaneo expose in production?
The web container exposes port 5173 for HTTP traffic. The api container listens on port 1337 internally, which is not exposed directly—Nginx proxies API requests from the web container. Database connections use standard PostgreSQL port 5432, configured via DATABASE_URL.
Can I use an external PostgreSQL database instead of a container?
Yes. Set DATABASE_URL to a connection string pointing to your external database host (e.g., postgresql://user:pass@db.example.com:5432/kaneo). The compose.yml does not require a local db service if DATABASE_URL references an external host. Ensure network connectivity and firewall rules permit access from the Kaneo container.
How do I update Kaneo to a new version?
Pull the latest source code, rebuild the Docker image with a new tag, and restart the compose stack:
git pull origin main
docker build -f Dockerfile.kaneo -t kaneo:latest .
docker compose -f compose.yml up -d
For zero-downtime deployments in production environments, use the Helm chart with rolling update strategies or run multiple replicas behind a load balancer.
Where are the frontend and backend source files located?
- Backend API:
apps/api/src/— Hono routes, controllers, and database schema - Frontend web app:
apps/web/src/— React components and TanStack Query hooks
These paths are referenced during the multi-stage Docker build to compile both services into the production image.
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 →