Self-Hosting Guide
DocIntel is a self-hosted product — your documents never leave your infrastructure. This guide covers deployment options.
Single command
docker run -d \
--name purple8-docintel \
-p 8200:8200 \
-e LLM__API_KEY=sk-... \
-e SERVER__API_KEY=change-me-min-32-chars \
-e LICENSE__KEY=your-license-key \
purple8/docintel:latestThe API is live at http://localhost:8200. Interactive docs at http://localhost:8200/docs.
Docker Compose — standalone
# docker-compose.yml
version: '3.9'
services:
docintel:
image: purple8/docintel:latest
ports:
- "8200:8200"
environment:
LLM__API_KEY: "${LLM__API_KEY}"
SERVER__API_KEY: "${SERVER__API_KEY}"
LICENSE__KEY: "${LICENSE__KEY}"
volumes:
- docintel-data:/data
restart: unless-stopped
volumes:
docintel-data:docker compose up -dDocker Compose — with Redis (multi-worker)
# docker-compose.production.yml
version: '3.9'
services:
docintel:
image: purple8/docintel:latest
ports:
- "8200:8200"
environment:
LLM__API_KEY: "${LLM__API_KEY}"
SERVER__API_KEY: "${SERVER__API_KEY}"
LICENSE__KEY: "${LICENSE__KEY}"
STORAGE__JOB_STORE: "redis"
STORAGE__REDIS_URL: "redis://redis:6379/0"
SERVER__WORKERS: "3"
depends_on:
- redis
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped
volumes:
redis-data:Kubernetes
Helm chart (coming soon)
A Helm chart for Kubernetes deployment is on the roadmap. In the meantime, use the Docker image with a Kubernetes Deployment:
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: docintel
labels:
app: docintel
spec:
replicas: 3
selector:
matchLabels:
app: docintel
template:
metadata:
labels:
app: docintel
spec:
containers:
- name: docintel
image: purple8/docintel:latest
ports:
- containerPort: 8200
env:
- name: LLM__API_KEY
valueFrom:
secretKeyRef:
name: docintel-secrets
key: llm-api-key
- name: SERVER__API_KEY
valueFrom:
secretKeyRef:
name: docintel-secrets
key: api-secret-key
- name: LICENSE__KEY
valueFrom:
secretKeyRef:
name: docintel-secrets
key: license-key
- name: STORAGE__JOB_STORE
value: redis
- name: STORAGE__REDIS_URL
value: redis://redis-svc:6379/0
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 8200
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8200
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: docintel-svc
spec:
selector:
app: docintel
ports:
- port: 8200
targetPort: 8200
type: ClusterIPAir-gapped deployment
For environments with no internet access (Enterprise plan):
Pre-pull images on a connected machine:
bashdocker pull purple8/docintel:latest docker save purple8/docintel:latest | gzip > docintel.tar.gzTransfer
docintel.tar.gzto the air-gapped environmentLoad and run:
bashdocker load < docintel.tar.gz docker compose up -dUse Ollama for LLM (no external API calls):
bashLLM__PROVIDER=ollama LLM__BASE_URL=http://ollama:11434 LLM__MODEL=llama3.1:70b
Output modes
JSON API (default)
Poll or stream results from the REST API:
# Submit
curl -X POST http://localhost:8200/process \
-H "Authorization: Bearer $API_KEY" \
-F "file=@invoice.pdf"
# Poll result
curl http://localhost:8200/jobs/{job_id} \
-H "Authorization: Bearer $API_KEY"Response:
{
"job_id": "abc-123",
"status": "completed",
"entities": [...],
"relationships": [...],
"metadata": {...}
}Webhook delivery (Pro+)
Configure webhook delivery via the Purple8 Graph connector. When Graph emission is enabled, you can set up downstream webhooks inside Purple8 Graph to forward extraction results to any endpoint.
Purple8 Graph emission
Emit extracted entities and relationships directly into Purple8 Graph for knowledge graph storage, Cypher queries, embeddings, and hybrid search.
Standalone DocIntel — if you omit GRAPH__BASE_URL, DocIntel returns JSON only. No Graph required.
DocIntel + Graph — set GRAPH__BASE_URL and DocIntel will POST /ingest/commit to Graph on every completed extraction.
Docker Compose (DocIntel + Graph)
version: "3.9"
services:
docintel:
image: purple8/docintel:latest
ports:
- "8200:8200"
environment:
LLM__API_KEY: "${LLM__API_KEY}"
SERVER__API_KEY: "${SERVER__API_KEY}"
LICENSE__KEY: "${LICENSE__KEY}"
GRAPH__BASE_URL: "http://graph:8100"
depends_on:
- graph
restart: unless-stopped
graph:
image: purple8/graph:latest
ports:
- "8100:8100"
environment:
PORT: "8100"
restart: unless-stoppedGraph-related environment variables
| Variable | Default | Description |
|---|---|---|
GRAPH__BASE_URL | http://localhost:8100 | Purple8 Graph API URL. Omit to disable emission. |
GRAPH__API_KEY | (empty) | Graph API authentication key |
GRAPH__TENANT_ID | (none) | Multi-tenant tenant ID |
GRAPH__AUTO_COMMIT | true | Auto-commit extracted data to Graph on completion |
GRAPH__TIMEOUT_S | 30 | Connection timeout (seconds) |
Verify end-to-end
# 1. Check both services
curl http://localhost:8200/health # DocIntel
curl http://localhost:8100/health # Graph
# 2. Process a document — results are emitted to Graph automatically
curl -X POST http://localhost:8200/process \
-H "Authorization: Bearer $SERVER__API_KEY" \
-F "file=@contract.pdf"
# 3. Query Graph for the extracted entities
curl http://localhost:8100/api/entities \
-H "Authorization: Bearer $GRAPH_API_KEY"Resource requirements
| Deployment | CPU | Memory | Storage | Documents/min |
|---|---|---|---|---|
| Minimal (1 worker) | 1 vCPU | 512 MB | 1 GB | ~5 |
| Recommended (3 workers) | 4 vCPU | 2 GB | 10 GB | ~15 |
| Production (5+ workers) | 8+ vCPU | 4+ GB | 50+ GB | ~30+ |
TIP
Document processing speed is primarily bottlenecked by LLM API latency, not DocIntel compute. More workers help with concurrency, not per-document speed.
Monitoring
| Endpoint | Description |
|---|---|
GET /health | Returns {"status": "ok"} — use for liveness probes |
GET /metrics | Prometheus-format metrics — jobs processed, latency histograms, error rates |
Prometheus scrape config
scrape_configs:
- job_name: docintel
static_configs:
- targets: ['docintel:8200']
metrics_path: /metrics
scrape_interval: 15sSecurity hardening
DocIntel runs as a non-root user inside the container and includes these security measures:
- SSRF guard — blocks RFC 1918, loopback, link-local, and cloud metadata endpoints
- Bearer token auth — all endpoints require
Authorization: Bearer <token> - Non-root container — runs as
uid=1000 - Read-only filesystem — mount
/tmpand/dataas the only writable paths - No ambient capabilities — drop all Linux capabilities
Systemd hardening (bare metal)
[Service]
User=docintel
Group=docintel
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/docintel