Development Guide
Development Guide
How to set up, run, and iterate on Jan Server locally. This guide covers the full Docker stack, dev-full mode (hybrid debugging), and native service execution.
Table of Contents
- Development Guide
Prerequisites
Install these before running any commands:
- Docker Desktop 24+ with Docker Compose V2
- GNU Make (built in on macOS/Linux, install via Chocolatey/Brew on Windows)
- Go 1.21+ (only required for native/hybrid execution or when editing Go code)
Tip:
make setupusesjan-cli dev setupto verify Docker, copy.env.templateto.env, and createdocker/.envautomatically.
Quick Start
# 1. Clone and enter the repo
git clone https://github.com/janhq/jan-server.git
cd jan-server
# 2. Create .env, docker/.env, and Docker networks
make setup
# 3. Start the full stack (infra + APIs + MCP + optional vLLM)
make up-full
# 4. Verify everything is healthy
make health-check
# 5. Tail logs or iterate
make logs # all containers
docker compose ps # status- Stop containers:
make down - Remove volumes:
make down-clean - Restart a single service:
make restart-api,make restart-kong,make restart-keycloak
Access Points
| Service | URL | Notes |
|---|---|---|
| Kong Gateway | http://localhost:8000 | Single entry point for all APIs |
| LLM API | http://localhost:8080 | OpenAI-compatible API, /healthz for checks |
| Response API | http://localhost:8082 | Multi-step orchestration |
| Media API | http://localhost:8285 | File upload/management service |
| MCP Tools | http://localhost:8091 | Native MCP tool bridge |
| Memory Tools | http://localhost:8090 | Semantic memory service |
| Realtime API | http://localhost:8186 | WebRTC session management |
| Keycloak | http://localhost:8085 | Admin/Admin in development |
| PostgreSQL | localhost:5432 | Database user jan_user / password from .env |
| Grafana | http://localhost:3331 | Start with make monitor-up |
| Prometheus | http://localhost:9090 | Monitoring profile |
| Jaeger | http://localhost:16686 | Tracing profile |
Project Layout
jan-server/
+-- services/ # llm-api, media-api, response-api, mcp-tools, template-api
+-- tools/jan-cli/ # jan-cli sources (`./jan-cli.sh`, `.\jan-cli.ps1` wrappers)
+-- pkg/config/ # Single source of truth for config defaults and schema
+-- infra/docker/ # Compose fragments (infrastructure, services, dev-full, observability)
+-- docker compose.yml # Root compose file (includes infra/docker/*.yml via profiles)
+-- docker compose.dev-full.yml # Extra compose overrides for dev-full
+-- kong/ # Gateway configuration (kong.yml + kong-dev-full.yml)
+-- docs/ # Documentation (guides, architecture, configuration)
+-- Makefile # Canonical automation entry point
+-- .env.template # Copy to .env and edit per environmentDevelopment Workflows
Jan Server supports three development modes:
- Full Docker Stack - All services in containers (parity with CI/production)
- Dev-Full Mode - Hybrid approach with infrastructure in Docker and optional native service execution
- Native Execution - Run services directly on host without dev-full scaffolding
Full Docker Stack (default)
make up-full # start everything defined by COMPOSE_PROFILES in .env
make logs # follow logs for every service
make logs-api # only API services
make logs-mcp # MCP stack
make down # stop and remove containersUse this mode for integration testing and parity with CI. COMPOSE_PROFILES controls which Compose profiles (infra,api,mcp,full) load—edit .env if you want to disable GPU/vLLM locally.
Dev-Full Mode (Hybrid Debugging)
Dev-full mode is the officially supported hybrid workflow. It starts every dependency in Docker, configures Kong with host.docker.internal upstreams, and lets you replace any service with a native process via jan-cli dev run <service>.
Why Use Dev-Full Mode?
- Fast iteration without rebuilding Docker images
- Debug with breakpoints while Kong continues to enforce plugins and auth
- Keep PostgreSQL, Keycloak, Kong, and monitoring inside Docker for parity
- Instant rollback: stop your host process and Docker takes over again
Quick Start
make setup # once per machine
make dev-full # start infra + APIs + MCP with host routingAfter the stack is up you can replace a service:
./jan-cli.sh dev run llm-api # macOS/Linux
.\jan-cli.ps1 dev run llm-api # Windows PowerShelljan-cli dev run stops the matching container, loads environment variables from .env (override with --env), and runs go run ./cmd/server inside services/<name>.
How It Works
make dev-full:
- Loads
.envand copies it toinfra/docker/.envviaensure-docker-env - Runs
docker compose -f docker-compose.yml -f docker-compose.dev-full.yml --profile full up -d - Prints URLs for PostgreSQL, Keycloak, Kong, and every API/MCP service
- Keeps the
jan-network/jan-monitoringnetworks around for fast restarts
Kong's dual-target upstreams (from kong/kong-dev-full.yml):
upstreams:
- name: llm-api-upstream
targets:
- target: llm-api:8080
- target: host.docker.internal:8080
healthchecks:
active:
http_path: /healthzWhen you stop the Docker container, Kong automatically fails over to the host target. When your host process stops responding to /healthz, traffic returns to Docker.
Running Services Natively in Dev-Full Mode
| Service | Port | Command |
|---|---|---|
| LLM API | 8080 | jan-cli dev run llm-api |
| Media API | 8285 | jan-cli dev run media-api |
| Response API | 8082 | jan-cli dev run response-api |
| MCP Tools | 8091 | jan-cli dev run mcp-tools |
| Memory Tools | 8090 | jan-cli dev run memory-tools |
| Realtime API | 8186 | jan-cli dev run realtime-api |
Options:
- Use
--buildto compile before running (jan-cli dev run llm-api --build) - Pass
--env config/hybrid.envif you keep a dedicated env file for host processes - To hand control back to Docker, stop the host process and run
docker compose start <service>
Workflow
-
Start dev-full mode
make dev-full -
Replace a service with native execution
./jan-cli.sh dev run llm-api # macOS/Linux .\jan-cli.ps1 dev run llm-api # Windows PowerShell -
Iterate and debug
- Launch Delve:
dlv debug ./cmd/server --headless --listen=:2345 - Use
airfor hot reload insideservices/<name> - Observe requests through Kong at http://localhost:8000 exactly as clients would
- Launch Delve:
-
Hand control back to Docker
- Stop your host process (Ctrl+C)
- Restart the container if needed:
docker compose start llm-api
-
Stop dev-full when done:
make dev-full-stop # keep containers make dev-full-down # remove containers
Environment Variables for Hybrid Mode
| Variable | Purpose |
|---|---|
DB_POSTGRESQL_WRITE_DSN | PostgreSQL connection string. Use localhost when running on host |
KEYCLOAK_BASE_URL / ISSUER / JWKS_URL | Auth endpoints (use http://localhost:8085) |
HTTP_PORT | Local service port (8080, 8082, 8285, 8091, etc.) |
LOG_LEVEL / LOG_FORMAT | Logging controls |
MCP_* / SEARXNG_URL / VECTOR_STORE_URL | Tool integrations for mcp-tools |
OTEL_* | Telemetry export (set OTEL_ENABLED=true to emit traces) |
Need a dedicated hybrid env file? Create config/hybrid.env, copy values from .env, then run jan-cli dev run llm-api --env config/hybrid.env.
Monitoring in Dev-Full Mode
You can bring up observability while using dev-full:
make monitor-up # Prometheus + Grafana + Jaeger
make monitor-logs # follow collector/datasource logsThose containers watch the same jan-network, so traces and metrics include both Docker and host services (as long as you set OTEL_ENABLED=true).
Running Services Natively
You can run a service completely outside Docker by providing the same environment variables from .env:
# Example for llm-api
docker compose up -d api-db keycloak kong # ensure infra is running
cd services/llm-api
export DB_POSTGRESQL_WRITE_DSN="postgres://jan_user:${POSTGRES_PASSWORD}@localhost:5432/jan_llm_api?sslmode=disable"
export KEYCLOAK_BASE_URL="http://localhost:8085"
export JWKS_URL="http://localhost:8085/realms/jan/protocol/openid-connect/certs"
export ISSUER="http://localhost:8085/realms/jan"
export HTTP_PORT=8080
export LOG_LEVEL=debug
go run ./cmd/serverWindows users can run
.\jan-cli.ps1 dev run llm-api --env .envto load variables automatically.
Configuration
- Copy
.env.templateto.env(or runmake setup) and edit secrets likeHF_TOKEN,SERPER_API_KEY, andPOSTGRES_PASSWORD make setupalso writesdocker/.env, so Compose and jan-cli use the same valuespkg/config/defaults.yamlis the canonical configuration, generated from Go structs inpkg/config/types.go- Helpful jan-cli commands:
jan-cli config validate --file config/defaults.yaml
jan-cli config show --path services.llm-api
jan-cli config export --format env --output config/generated.envDatabase & Tooling
make db-migrate # Apply Go migrations for llm-api
make db-reset # Drop + recreate tables (uses docker compose)
make db-console # Opens psql inside the api-db container
# Direct Docker examples
docker compose logs api-db
psql "postgres://jan_user:${POSTGRES_PASSWORD}@localhost:5432/jan_llm_api?sslmode=disable"For backups and restores use make db-backup / make db-restore. The Makefile targets wrap docker compose so they work on Windows, macOS, and Linux.
Testing
- Full integration suite:
make test-all(runs every Postman collection listed in the Makefile) - Focused suites:
make test-auth,make test-conversations,make test-response,make test-media,make test-mcp-integration,make test-e2e - Unit tests: run them from each service directory (
go test ./...)
See Testing Guide for platform details, CI coverage, and troubleshooting tips.
IDE Integration
- VS Code launch configurations can depend on a task that runs
make dev-full - After the task finishes,
jan-cli dev run <service>is a good preLaunchCommand - Debuggers simply connect to the local port (Kong still listens on 8000)
- Hot reload tools (
air,reflex, etc.) live insideservices/<name>
Example .vscode/launch.json for debugging llm-api:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug LLM API",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/services/llm-api/cmd/server",
"envFile": "${workspaceFolder}/.env",
"preLaunchTask": "dev-full-start"
}
]
}Troubleshooting & Next Steps
Common Issues
| Symptom | Fix |
|---|---|
| Kong keeps hitting the Docker container | Ensure the host service listens on the same port and returns 200 on /healthz |
| Service cannot reach PostgreSQL | Update DB_POSTGRESQL_WRITE_DSN to use localhost instead of api-db when running on host |
| Environment variables missing | Pass --env .env (default) or a custom env file to jan-cli dev run |
| Port already in use | Stop the other listener or change HTTP_PORT before running the host service |
| Want to debug multiple services | Run jan-cli dev run ... in multiple terminals; each command stops its corresponding container first |
Health Checks and Monitoring
make health-check- verifies infrastructure, API, MCP, and optional servicesmake logsordocker compose logs <service>- inspect failures quicklymake restart-kong/make restart-keycloak- common fixes for gateway/auth issuesmake monitor-up- bring up Grafana/Prometheus/Jaeger if you need observability while debugging
Cleanup
make dev-full-stop # stop containers but keep them around
make dev-full-down # stop + remove containers
make down # standard docker workflow stop
make down-clean # remove volumes and networks for pristine stateNeed more help? Review Testing, Troubleshooting, Monitoring, and the configuration docs under docs/configuration/.