# How to Deploy Kaneo to Production: A Complete Docker-Based Guide

> Deploy Kaneo to production using Docker and compose.yml. This guide covers building the multi-stage Docker image for Hono API, React frontend, and Nginx for a production-ready container.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-10

---

**Deploy Kaneo to production by building the multi-stage Docker image from `Dockerfile.kaneo` and orchestrating services with [`compose.yml`](https://github.com/usekaneo/kaneo/blob/main/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)](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 |

```bash

# 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`](https://github.com/usekaneo/kaneo/blob/main/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 dependencies
- **`web-builder`** — Builds the React SPA using Vite with optimized assets
- **`runtime`** — 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:

```bash
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)](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 1337
- **`web`** — Serves the React SPA through Nginx on exposed port 5173

### Starting the Stack

```bash

# 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`](https://github.com/usekaneo/kaneo/blob/main/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

```bash

# 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/`](https://github.com/usekaneo/kaneo/tree/main/charts/kaneo)

Example Traefik configuration:

```yaml

# 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/`](https://github.com/usekaneo/kaneo/tree/main/charts/kaneo). This packages the same container image with Kubernetes-native resources including Deployments, Services, Ingress rules, and ConfigMaps for environment management.

```bash
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.kaneo` image and running `docker compose -f compose.yml up -d`
- **Configure required variables** in `.env` including `KANEO_CLIENT_URL`, `KANEO_API_URL`, `AUTH_SECRET`, and `DATABASE_URL`
- **Verify deployment health** using the built-in container health check and `/api/health` endpoint
- **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`](https://github.com/usekaneo/kaneo/blob/main/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:

```bash
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/`](https://github.com/usekaneo/kaneo/tree/main/apps/api/src) — Hono routes, controllers, and database schema
- Frontend web app: [`apps/web/src/`](https://github.com/usekaneo/kaneo/tree/main/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.