Deployment Guide
Deploy Eneo in production using Docker Compose. This guide covers configuration, deployment, and troubleshooting.
This is an example configuration. You MUST customize all values before deployment.
Search for your-domain.com, changeme, and CHANGEME and replace with your actual values.
Copy-paste without modification will NOT work.
Architecture Overview
Eneo uses a microservices architecture with six core services.
| Service | Technology | Port | Role |
|---|---|---|---|
| Frontend | SvelteKit | 3000 | Web interface, SSR |
| Backend | FastAPI (Python) | 8000 | API server, authentication |
| Worker | ARQ | - | Background processing, document indexing |
| Database | PostgreSQL 16 + pgvector | 5432 | Data storage, vector search |
| Redis | Redis 7 | 6379 | Cache, task queue |
| db-init | Python | - | One-time database initialization |
Traefik is provided as an example reverse proxy, not a requirement. You can use nginx, HAProxy, Caddy, or any reverse proxy that supports the routing requirements below.
Prerequisites
Before deploying, ensure you have:
- Docker and Docker Compose v2+ installed
- Domain name with DNS pointing to your server
- SSL certificate (Let’s Encrypt via Traefik, or your own PKI/CA)
- At least one AI provider API key (see AI Provider Configuration)
System Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4+ cores |
| RAM | 4GB | 8GB+ |
| Disk | 20GB | 50GB+ |
| Network | Ports 80, 443 open | - |
Quick Start
Preflight Checklist - Before running docker compose up:
- Set domain in docker-compose.yml (4 locations)
- Set
POSTGRES_PASSWORD(not “changeme”) - Generate and set
JWT_SECRET(32+ chars) - Set
PUBLIC_ORIGINin both backend and frontend env files - Configure at least ONE AI provider key
- Run
docker network create proxy_tier - Production: Pin image versions instead of
latest(see Version Pinning)
Get deployment files
# Option A: Clone the repository
git clone https://github.com/eneo-ai/eneo.git
cd eneo/docs/deployment/
# Option B: Download files directly
mkdir eneo-deployment && cd eneo-deployment
curl -O https://raw.githubusercontent.com/eneo-ai/eneo/main/docs/deployment/docker-compose.yml
curl -O https://raw.githubusercontent.com/eneo-ai/eneo/main/docs/deployment/env_backend.template
curl -O https://raw.githubusercontent.com/eneo-ai/eneo/main/docs/deployment/env_frontend.template
curl -O https://raw.githubusercontent.com/eneo-ai/eneo/main/docs/deployment/env_db.templateCreate environment files
cp env_backend.template env_backend.env
cp env_frontend.template env_frontend.env
cp env_db.template env_db.envConfigure required values
Generate secrets and update environment files:
# Generate JWT_SECRET (MUST match in backend AND frontend)
JWT_SECRET=$(openssl rand -hex 32)
echo "JWT_SECRET=$JWT_SECRET"
# Generate URL_SIGNING_KEY
echo "URL_SIGNING_KEY=$(openssl rand -hex 32)"
# Generate database password
echo "POSTGRES_PASSWORD=$(openssl rand -base64 24)"Edit env_backend.env with your values:
JWT_SECRET- paste the generated valueURL_SIGNING_KEY- paste the generated valuePUBLIC_ORIGIN- your external URL (e.g.,https://eneo.example.com)- At least one AI provider key (e.g.,
OPENAI_API_KEY)
Edit env_frontend.env:
JWT_SECRET- MUST match backend valueENEO_BACKEND_URL- backend URL for SSR (e.g.,http://backend:8000)PUBLIC_ENEO_BACKEND_URL- external API URL (e.g.,https://eneo.example.com/api)PUBLIC_ORIGIN- your external URL (e.g.,https://eneo.example.com)ORIGIN- same as PUBLIC_ORIGIN
Edit docker-compose.yml:
- Replace
your-domain.comwith your actual domain (4 locations) - Replace
your-email@domain.comwith your email for Let’s Encrypt
Create Docker network and deploy
# Create external network for Traefik
docker network create proxy_tier
# Start all services
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -fVerify and login
Navigate to https://your-domain.com
Default credentials: user@example.com / Password1!
Change this password immediately after first login.
Environment Configuration
Backend
Backend Environment Variables
Required:
| Variable | Description | Example |
|---|---|---|
JWT_SECRET | Token signing key (32+ chars). Generate: openssl rand -hex 32 | a1b2c3d4... |
URL_SIGNING_KEY | URL signing key. Generate: openssl rand -hex 32 | e5f6g7h8... |
PUBLIC_ORIGIN | External URL for callbacks | https://eneo.example.com |
DATABASE_URL | PostgreSQL connection string | postgresql://eneo:password@db:5432/eneo |
REDIS_URL | Redis connection string | redis://redis:6379/0 |
Default User Setup:
| Variable | Default | Description |
|---|---|---|
DEFAULT_TENANT_NAME | - | Name for initial tenant |
DEFAULT_USER_EMAIL | - | Email for initial admin user |
DEFAULT_USER_PASSWORD | - | Password for initial admin user |
USING_ACCESS_MANAGEMENT | false | Enable Users page in Admin panel |
OIDC Authentication (Enterprise SSO):
Glossary: IdP = Identity Provider (Entra ID, MobilityGuard, Auth0). OIDC = OpenID Connect protocol.
Choose one mode:
- Single-Tenant via env vars (
FEDERATION_PER_TENANT_ENABLED=false): Configure OIDC in frontend env. Requires restart to change settings. Simplest for single-organization deployments. - Federation via API (
FEDERATION_PER_TENANT_ENABLED=true): Configure IdP via sysadmin API. Changes take effect immediately (no restart). Use for multi-tenant deployments OR single-tenant with dynamic configuration.
Multi-Tenant Federation (API-managed OIDC):
| Variable | Default | Description |
|---|---|---|
FEDERATION_PER_TENANT_ENABLED | false | Enable per-tenant IdP configuration via API |
ENCRYPTION_KEY | - | 44-char base64 Fernet key (required for credentials & federation) |
ENEO_SUPER_API_KEY | - | Super admin API access |
Generating ENCRYPTION_KEY:
ENCRYPTION_KEY is required when enabling TENANT_CREDENTIALS_ENABLED=true or FEDERATION_PER_TENANT_ENABLED=true.
# Development (with uv)
uv run python -m intric.cli.generate_encryption_key
# Production (Docker)
docker compose run --rm backend python -m intric.cli.generate_encryption_key
# Alternative (Python)
python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'Backup your ENCRYPTION_KEY securely. Without it, encrypted credentials and federation secrets cannot be decrypted. If lost, you must re-enter all tenant API keys and reconfigure all IdP settings.
Upload Limits:
| Variable | Default | Description |
|---|---|---|
UPLOAD_MAX_FILE_SIZE | 10485760 | Max file size in bytes (10MB) |
MAX_IN_QUESTION | 1 | Max files per chat message |
TRANSCRIPTION_MAX_FILE_SIZE | 10485760 | Max audio file size (10MB) |
Can’t upload multiple files? Increase MAX_IN_QUESTION in backend env.
Crawler Settings:
| Variable | Default | Description |
|---|---|---|
CRAWL_FEEDER_ENABLED | true | Enable optimized crawling (keep true) |
CRAWL_MAX_LENGTH | 36000 | Max crawl duration in seconds (~10 hours) |
CLOSESPIDER_ITEMCOUNT | 20000 | Max pages per crawl |
Background Worker:
| Variable | Default | Description |
|---|---|---|
WORKER_MAX_JOBS | 15 | Max concurrent background jobs (should be ≤60% of DB pool size) |
TENANT_WORKER_CONCURRENCY_LIMIT | 4 | Max concurrent jobs per tenant (prevents one tenant monopolizing workers) |
High CPU usage on worker? Reduce WORKER_MAX_JOBS to limit concurrent document processing.
Dynamic Credential Management:
| Variable | Default | Description |
|---|---|---|
TENANT_CREDENTIALS_ENABLED | false | Enable API-based credential management (Fernet encrypted) |
Recommended even for single-tenant deployments. When TENANT_CREDENTIALS_ENABLED=true, you can add, update, or rotate AI provider API keys via the Credentials API without restarting the backend. Credentials are encrypted with Fernet (AES-128-CBC + HMAC). Requires ENCRYPTION_KEY to be set.
Integrations:
Setting up SharePoint? SharePoint integration requires SHAREPOINT_WEBHOOK_CLIENT_STATE for webhook validation. See the SharePoint Integration Guide for complete setup instructions including Azure AD app registration.
Reverse Proxy Requirements
Any reverse proxy (Traefik, nginx, HAProxy, Caddy) must meet these requirements:
Routing:
| Path | Target | Description |
|---|---|---|
/* | Frontend (port 3000) | All requests except API |
/api/* | Backend (port 8000) | API endpoints |
/docs | Backend (port 8000) | OpenAPI documentation |
/openapi.json | Backend (port 8000) | OpenAPI spec |
/version | Backend (port 8000) | Version endpoint |
Headers:
X-Forwarded-Proto- original protocol (https)X-Forwarded-For- client IP addressHost- original host header
Requirements:
- WebSocket support (for real-time features)
- TLS termination with valid certificate
Using nginx Instead of Traefik
upstream frontend {
server frontend:3000;
}
upstream backend {
server backend:8000;
}
server {
listen 443 ssl http2;
server_name eneo.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# API routes to backend
location /api/ {
proxy_pass http://backend;
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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /docs {
proxy_pass http://backend;
proxy_set_header Host $host;
}
location /openapi.json {
proxy_pass http://backend;
}
location /version {
proxy_pass http://backend;
}
# Everything else to frontend
location / {
proxy_pass http://frontend;
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;
}
}API Key Hierarchy
Eneo uses a three-tier API key system for different administrative tasks.
| Key | Environment Variable | Purpose |
|---|---|---|
| Super API Key | ENEO_SUPER_API_KEY | Sysadmin operations: tenants, users, credentials, crawler settings |
| Super Duper API Key | ENEO_SUPER_DUPER_API_KEY | Module management: assigning modules to tenants |
| User API Key | Generated per user | Admin operations within a tenant via /api/v1/admin/ |
Service Account Pattern: We recommend creating a dedicated admin account (service account) for automated user provisioning via the admin API endpoints, rather than using personal accounts.
Credentials API
Manage AI provider credentials per tenant using the sysadmin API.
Prerequisite: Set TENANT_CREDENTIALS_ENABLED=true and ENCRYPTION_KEY in backend env before using these endpoints. See Per-Tenant Credentials in the Backend configuration.
Set Credentials
curl -X PUT "https://eneo.example.com/api/v1/sysadmin/tenants/{tenant_id}/credentials/{provider}" \
-H "X-API-Key: your-super-api-key" \
-H "Content-Type: application/json" \
-d '{"api_key": "sk-..."}'Supported providers: openai, anthropic, azure, google, mistral, berget, gdm, vllm
Credentials are encrypted with Fernet (AES-128-CBC + HMAC). Backup your ENCRYPTION_KEY - if lost, all credentials must be re-entered.
Crawler Settings API
Configure crawling behavior per tenant.
Set Settings
curl -X PUT "https://eneo.example.com/api/v1/sysadmin/tenants/{tenant_id}/crawler-settings" \
-H "X-API-Key: your-super-api-key" \
-H "Content-Type: application/json" \
-d '{"crawl_feeder_enabled": true, "closespider_itemcount": 20000}'Enabling Features
After deployment, some features require additional configuration in the Admin panel.
Crawler & Document Upload
To use the web crawler or upload documents for processing, the worker service must be running and at least one embedding model must be enabled in Admin → Models → Embeddings.
Apps (Voice/Audio Features)
Can’t create Apps? This feature requires a transcription model. Go to Admin → Models → Transcription tab and enable a model like Whisper.
Sysadmin API Access
To use system administration endpoints (tenants, credentials, crawler settings), set the API key in env_backend.env:
ENEO_SUPER_API_KEY=your-secure-api-keyModules API Access
To manage module assignments to tenants, set a separate higher-privileged API key:
ENEO_SUPER_DUPER_API_KEY=your-other-secure-api-keyTroubleshooting
Common Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
| Can’t login with default credentials | DEFAULT_* vars commented or db-init didn’t run | Uncomment vars in env_backend.env, check docker logs eneo_db_init |
| Can’t see Users page in Admin | USING_ACCESS_MANAGEMENT=false | Set to true, restart backend |
| Frontend shows wrong API URL | PUBLIC_ENEO_BACKEND_URL incorrect | Set to external URL (not localhost in prod) |
| Can’t upload multiple files | MAX_IN_QUESTION=1 | Increase value in backend env |
| Large file uploads fail | UPLOAD_MAX_FILE_SIZE too low | Increase value (default 10MB = 10485760 bytes) |
| “Failed to decrypt credential” | ENCRYPTION_KEY missing or changed | Set correct key or re-enter credentials |
| Credentials API returns 404/error | TENANT_CREDENTIALS_ENABLED=false | Set to true, requires ENCRYPTION_KEY |
| OIDC redirect fails | PUBLIC_ORIGIN mismatch | Must match in backend, frontend, AND IdP config |
| Crawls not processing | CRAWL_FEEDER_ENABLED=false | Set to true |
| Crawls/uploads not working | Worker not running or no embedding model | Check worker is running, enable embedding model in Admin → Models |
| Can’t create Apps | No transcription model enabled | Admin → Models → Transcription tab, enable Whisper |
| JWT token invalid | JWT_SECRET mismatch | Must be identical in backend AND frontend |
| Worker jobs stuck or slow | Worker overloaded | Reduce WORKER_MAX_JOBS or increase resources |
ECONNREFUSED 127.0.0.1:8000 | Frontend using localhost instead of Docker service | Set ENEO_BACKEND_SERVER_URL=http://backend:8000 (use service name, not localhost) |
| HTTP to HTTPS redirect broken | Missing Traefik label | Ensure traefik.enable=true label on traefik service |
| 502 Bad Gateway | Domain not replaced in docker-compose.yml | Replace your-domain.com in all 4 locations |
Debug Mode
Enable detailed logging for troubleshooting:
# In env_backend.env
LOGLEVEL=DEBUGThen restart and view logs:
docker compose restart backend
docker compose logs -f backendService Health Checks
# Check all services are running
docker compose ps
# Test backend API
curl https://your-domain.com/api/health
# Check database
docker compose exec db psql -U eneo -c "SELECT version()"
# Check Redis
docker compose exec redis redis-cli ping
# View db-init logs (for first-run issues)
docker logs eneo_db_initContainer Logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f backend
# Last 100 lines
docker compose logs --tail=100 backend workerMaintenance
Version Pinning (Recommended)
Production deployments should pin to specific version tags instead of using latest. This gives you control over when updates are applied and makes rollbacks straightforward.
The example docker-compose.yml uses :latest tags for simplicity, but for production you should pin versions:
# Default (not recommended for production)
frontend:
image: ghcr.io/eneo-ai/eneo-frontend:latest
# Production (pin to specific version)
frontend:
image: ghcr.io/eneo-ai/eneo-frontend:v1.2.3
backend:
image: ghcr.io/eneo-ai/eneo-backend:v1.2.3
worker:
image: ghcr.io/eneo-ai/eneo-backend:v1.2.3Find available versions:
- GitHub Releases: https://github.com/eneo-ai/eneo/releases
- Container Registry: https://github.com/orgs/eneo-ai/packages
Always run the latest released version to get bug fixes and security patches. Check the release notes before upgrading.
Backups
Always back up before updating. Take database and volume backups before any upgrade to enable rollback if issues occur.
Database backup with pg_dump (recommended):
# Create timestamped database backup
docker compose exec -T db pg_dump -U eneo eneo | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
# Restore from backup (if needed)
gunzip -c backup_20250108.sql.gz | docker compose exec -T db psql -U eneo eneoVolume backup (for complete recovery):
# Stop services first
docker compose down
# Backup PostgreSQL data volume
docker run --rm -v eneo_postgres_data:/data -v $(pwd):/backup ubuntu \
tar czf /backup/postgres_volume_$(date +%Y%m%d).tar.gz /data
# Backup Redis data volume (optional)
docker run --rm -v eneo_redis_data:/data -v $(pwd):/backup ubuntu \
tar czf /backup/redis_volume_$(date +%Y%m%d).tar.gz /dataUpdating Eneo
Back up your data
# Database backup (required)
docker compose exec -T db pg_dump -U eneo eneo | gzip > backup_$(date +%Y%m%d).sql.gzUpdate version tags (if pinned)
Edit docker-compose.yml and update version tags (e.g., v1.2.3 → v1.2.4).
Pull and deploy
# Pull new images
docker compose pull
# Deploy updated version
docker compose up -d
# Verify services are running
docker compose psClean up old images
docker image prune -aRollback procedure: If issues occur after an update, restore from your backup:
docker compose down
gunzip -c backup_YYYYMMDD.sql.gz | docker compose exec -T db psql -U eneo eneo
docker compose up -dIf using pinned versions, also revert the version tags in docker-compose.yml.
Scaling
# Scale backend instances (with load balancer)
docker compose up -d --scale backend=3 --scale worker=2Migration: INTRIC_ to ENEO_ Environment Variables
All INTRIC_-prefixed environment variables have been renamed to ENEO_. The old names are still accepted but deprecated and will be removed in v3.0.
No immediate action required. Existing installations using INTRIC_* variables will continue to work. A deprecation warning will appear in the logs on startup. Update at your convenience before v3.0.
| Old variable | New variable |
|---|---|
INTRIC_SUPER_API_KEY | ENEO_SUPER_API_KEY |
INTRIC_SUPER_DUPER_API_KEY | ENEO_SUPER_DUPER_API_KEY |
INTRIC_BACKEND_URL | ENEO_BACKEND_URL |
INTRIC_BACKEND_SERVER_URL | ENEO_BACKEND_SERVER_URL |
PUBLIC_INTRIC_BACKEND_URL | PUBLIC_ENEO_BACKEND_URL |
Removed (no replacement needed):
INTRIC_MARKETPLACE_API_KEY— was unusedINTRIC_MARKETPLACE_URL— was unused
To migrate, update your env_backend.env and env_frontend.env files and restart all services.
Related Documentation
- AI Provider Configuration - Configure AI models and providers
- Multi-Tenant OIDC Federation - Enterprise SSO setup
- Single-Tenant OIDC - Simple SSO setup
- SharePoint Integration - Connect SharePoint and OneDrive
- Authentication Architecture - Technical authentication details
- System Architecture - Complete architecture overview