Skip to Content
GuidesDeployment

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.

Eneo System Architecture showing Client Layer, Reverse Proxy (Traefik), Frontend (SvelteKit), Backend API (FastAPI), Database (PostgreSQL with pgvector), Redis Cache/Queue, Worker (ARQ), and AI Providers

ServiceTechnologyPortRole
FrontendSvelteKit3000Web interface, SSR
BackendFastAPI (Python)8000API server, authentication
WorkerARQ-Background processing, document indexing
DatabasePostgreSQL 16 + pgvector5432Data storage, vector search
RedisRedis 76379Cache, task queue
db-initPython-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

RequirementMinimumRecommended
CPU2 cores4+ cores
RAM4GB8GB+
Disk20GB50GB+
NetworkPorts 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_ORIGIN in 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.template

Create environment files

cp env_backend.template env_backend.env cp env_frontend.template env_frontend.env cp env_db.template env_db.env

Configure 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 value
  • URL_SIGNING_KEY - paste the generated value
  • PUBLIC_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 value
  • ENEO_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.com with your actual domain (4 locations)
  • Replace your-email@domain.com with 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 -f

Verify and login

Navigate to https://your-domain.com

Default credentials: user@example.com / Password1!

Change this password immediately after first login.


Environment Configuration

Backend Environment Variables

Required:

VariableDescriptionExample
JWT_SECRETToken signing key (32+ chars). Generate: openssl rand -hex 32a1b2c3d4...
URL_SIGNING_KEYURL signing key. Generate: openssl rand -hex 32e5f6g7h8...
PUBLIC_ORIGINExternal URL for callbackshttps://eneo.example.com
DATABASE_URLPostgreSQL connection stringpostgresql://eneo:password@db:5432/eneo
REDIS_URLRedis connection stringredis://redis:6379/0

Default User Setup:

VariableDefaultDescription
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_MANAGEMENTfalseEnable 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):

VariableDefaultDescription
FEDERATION_PER_TENANT_ENABLEDfalseEnable 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:

VariableDefaultDescription
UPLOAD_MAX_FILE_SIZE10485760Max file size in bytes (10MB)
MAX_IN_QUESTION1Max files per chat message
TRANSCRIPTION_MAX_FILE_SIZE10485760Max audio file size (10MB)

Can’t upload multiple files? Increase MAX_IN_QUESTION in backend env.

Crawler Settings:

VariableDefaultDescription
CRAWL_FEEDER_ENABLEDtrueEnable optimized crawling (keep true)
CRAWL_MAX_LENGTH36000Max crawl duration in seconds (~10 hours)
CLOSESPIDER_ITEMCOUNT20000Max pages per crawl

Background Worker:

VariableDefaultDescription
WORKER_MAX_JOBS15Max concurrent background jobs (should be ≤60% of DB pool size)
TENANT_WORKER_CONCURRENCY_LIMIT4Max 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:

VariableDefaultDescription
TENANT_CREDENTIALS_ENABLEDfalseEnable 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:

PathTargetDescription
/*Frontend (port 3000)All requests except API
/api/*Backend (port 8000)API endpoints
/docsBackend (port 8000)OpenAPI documentation
/openapi.jsonBackend (port 8000)OpenAPI spec
/versionBackend (port 8000)Version endpoint

Headers:

  • X-Forwarded-Proto - original protocol (https)
  • X-Forwarded-For - client IP address
  • Host - 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.

KeyEnvironment VariablePurpose
Super API KeyENEO_SUPER_API_KEYSysadmin operations: tenants, users, credentials, crawler settings
Super Duper API KeyENEO_SUPER_DUPER_API_KEYModule management: assigning modules to tenants
User API KeyGenerated per userAdmin 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.

LiteLLM Architecture showing credential flow from Sysadmin API to encrypted storage to AI providers

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.

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-key

Modules API Access

To manage module assignments to tenants, set a separate higher-privileged API key:

ENEO_SUPER_DUPER_API_KEY=your-other-secure-api-key

Troubleshooting

Common Issues

SymptomLikely CauseFix
Can’t login with default credentialsDEFAULT_* vars commented or db-init didn’t runUncomment vars in env_backend.env, check docker logs eneo_db_init
Can’t see Users page in AdminUSING_ACCESS_MANAGEMENT=falseSet to true, restart backend
Frontend shows wrong API URLPUBLIC_ENEO_BACKEND_URL incorrectSet to external URL (not localhost in prod)
Can’t upload multiple filesMAX_IN_QUESTION=1Increase value in backend env
Large file uploads failUPLOAD_MAX_FILE_SIZE too lowIncrease value (default 10MB = 10485760 bytes)
“Failed to decrypt credential”ENCRYPTION_KEY missing or changedSet correct key or re-enter credentials
Credentials API returns 404/errorTENANT_CREDENTIALS_ENABLED=falseSet to true, requires ENCRYPTION_KEY
OIDC redirect failsPUBLIC_ORIGIN mismatchMust match in backend, frontend, AND IdP config
Crawls not processingCRAWL_FEEDER_ENABLED=falseSet to true
Crawls/uploads not workingWorker not running or no embedding modelCheck worker is running, enable embedding model in Admin → Models
Can’t create AppsNo transcription model enabledAdmin → Models → Transcription tab, enable Whisper
JWT token invalidJWT_SECRET mismatchMust be identical in backend AND frontend
Worker jobs stuck or slowWorker overloadedReduce WORKER_MAX_JOBS or increase resources
ECONNREFUSED 127.0.0.1:8000Frontend using localhost instead of Docker serviceSet ENEO_BACKEND_SERVER_URL=http://backend:8000 (use service name, not localhost)
HTTP to HTTPS redirect brokenMissing Traefik labelEnsure traefik.enable=true label on traefik service
502 Bad GatewayDomain not replaced in docker-compose.ymlReplace your-domain.com in all 4 locations

Debug Mode

Enable detailed logging for troubleshooting:

# In env_backend.env LOGLEVEL=DEBUG

Then restart and view logs:

docker compose restart backend docker compose logs -f backend

Service 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_init

Container Logs

# All services docker compose logs -f # Specific service docker compose logs -f backend # Last 100 lines docker compose logs --tail=100 backend worker

Maintenance

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.3

Find available versions:

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 eneo

Volume 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 /data

Updating 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.gz

Update version tags (if pinned)

Edit docker-compose.yml and update version tags (e.g., v1.2.3v1.2.4).

Pull and deploy

# Pull new images docker compose pull # Deploy updated version docker compose up -d # Verify services are running docker compose ps

Clean up old images

docker image prune -a

Rollback 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 -d

If 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=2

Migration: 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 variableNew variable
INTRIC_SUPER_API_KEYENEO_SUPER_API_KEY
INTRIC_SUPER_DUPER_API_KEYENEO_SUPER_DUPER_API_KEY
INTRIC_BACKEND_URLENEO_BACKEND_URL
INTRIC_BACKEND_SERVER_URLENEO_BACKEND_SERVER_URL
PUBLIC_INTRIC_BACKEND_URLPUBLIC_ENEO_BACKEND_URL

Removed (no replacement needed):

  • INTRIC_MARKETPLACE_API_KEY — was unused
  • INTRIC_MARKETPLACE_URL — was unused

To migrate, update your env_backend.env and env_frontend.env files and restart all services.


Last updated on