9.6 KiB
9.6 KiB
Deployment Guide
Prerequisites
- Podman (or Docker) with compose plugin
- Ollama running with models:
gpt-oss:20b,qwen3-embedding:8b - At least 8 GB RAM for the marker-api container (model loading)
- GPU recommended but not required for marker-pdf OCR
Quick Start (Podman/Docker Compose)
1. Update Environment Variables
Create .env in the project root:
OLLAMA_URL=http://10.0.1.127:11434
GPT_OSS_MODEL=gpt-oss:20b
MARKER_API_URL=http://marker-api:8001
DB_HOST=postgres
DB_PORT=5432
DB_NAME=research
DB_USER=research
DB_PASSWORD=research123
VECTOR_DIM=4096
2. Update podman-compose.yml VECTOR_DIM
In podman-compose.yml, under app.environment, set:
VECTOR_DIM: 4096
3. Build & Start
# Build all images
podman-compose build
# Start services (PostgreSQL, marker-api, app)
podman-compose up -d
# Or with Docker:
# docker compose up -d
4. Verify Services
# Health check
curl http://localhost:8000/health
# Should return:
# {"status":"ok","db":"connected","ollama":"connected","marker_api":"ok"}
5. Upload Documents
curl -X POST http://localhost:8000/api/documents/upload \
-F "file=@document.pdf"
6. Run Research
curl -X POST http://localhost:8000/api/research/run \
-H "Content-Type: application/json" \
-d '{"query":"Your question","doc_id":"<doc_id_from_upload>","skills":["summarizer"]}'
Manual Container Setup (Podman, host networking)
Use this if you need direct host network access (e.g., Ollama on 10.0.1.127):
PostgreSQL + pgvector
podman run -d --name pv-db --network host \
-e POSTGRES_DB=research \
-e POSTGRES_USER=research \
-e POSTGRES_PASSWORD=research123 \
-v pgdata:/var/lib/postgresql/data \
-v ./migrations/init.sql:/docker-entrypoint-initdb.d/01-init.sql \
docker.io/pgvector/pgvector:pg16
Marker OCR API
cd marker-api
# Build (slow first time - downloads marker-pdf + surya models)
podman build -t marker-api .
# Run with model cache volume
podman run -d --name marker-api --network host \
-v marker_models:/root/.cache \
-v marker_ws:/workspace \
-e OLLAMA_URL=http://10.0.1.127:11434 \
localhost/marker-api
Model warmup (required before first use):
podman exec marker-api poetry run python3 -c "
from marker.scripts.convert_single import convert_single_cli
import sys
# Run once to trigger model download
sys.argv = ['warmup', '/dev/null', '--output_dir', '/tmp/warmup', '--page_range', '0-0']
try:
convert_single_cli(sys.argv[1:], standalone_mode=False)
except: pass
print('Models downloaded')
"
Then commit the warm image to persist models:
podman commit marker-api localhost/marker-api:latest
App Container
podman build -t agentic-app .
podman run -d --name agentic-app --network host \
-v app_ws:/workspace \
-e OLLAMA_URL=http://10.0.1.127:11434 \
-e GPT_OSS_MODEL=gpt-oss:20b \
-e MARKER_API_URL=http://localhost:8001 \
-e DB_HOST=localhost \
-e DB_PORT=5432 \
-e DB_NAME=research \
-e DB_USER=research \
-e DB_PASSWORD=research123 \
-e VECTOR_DIM=4096 \
localhost/agentic-app
Kubernetes Deployment
1. PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
template:
spec:
containers:
- name: postgres
image: pgvector/pgvector:pg16
env:
- name: POSTGRES_DB
value: research
- name: POSTGRES_USER
value: research
- name: POSTGRES_PASSWORD
value: research123
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
- name: init-sql
mountPath: /docker-entrypoint-initdb.d/01-init.sql
subPath: init.sql
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: init-sql
data:
init.sql: |
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL,
doc_id TEXT,
mime_type TEXT,
file_path TEXT,
status TEXT DEFAULT 'pending',
page_count INTEGER DEFAULT 0,
full_text TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id UUID REFERENCES documents(id) ON DELETE CASCADE,
content TEXT NOT NULL,
vector VECTOR(4096),
page_num INTEGER,
block_index INTEGER,
polygon JSONB,
chunk_type TEXT DEFAULT 'text',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- (full schema in migrations/init.sql)
2. Marker-API Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: marker-api
spec:
replicas: 1
template:
spec:
containers:
- name: marker-api
image: marker-api:latest
ports:
- containerPort: 8001
env:
- name: OLLAMA_URL
value: http://ollama-service:11434
volumeMounts:
- name: model-cache
mountPath: /root/.cache
resources:
requests:
memory: 4Gi
cpu: 2
limits:
memory: 8Gi
3. App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: agentic-app
spec:
replicas: 1
template:
spec:
containers:
- name: app
image: agentic-app:latest
ports:
- containerPort: 8000
env:
- name: OLLAMA_URL
value: http://ollama-service:11434
- name: GPT_OSS_MODEL
value: gpt-oss:20b
- name: MARKER_API_URL
value: http://marker-api:8001
- name: DB_HOST
value: postgres
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: research
- name: DB_USER
value: research
- name: DB_PASSWORD
value: research123
- name: VECTOR_DIM
value: "4096"
---
apiVersion: v1
kind: Service
metadata:
name: agentic-app
spec:
ports:
- port: 8000
targetPort: 8000
selector:
app: agentic-app
type: LoadBalancer
4. Apply
kubectl apply -f k8s/
# Expose app
kubectl port-forward svc/agentic-app 8000:8000
Architecture
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Ollama │ │ marker-api │ │ PostgreSQL │
│ Host │◄────┤ (OCR/PDF) │ │ + pgvector │
│ :11434 │ │ :8001 │ │ :5432 │
└─────────────┘ └──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────┐
│ agentic-app (FastAPI) │
│ :8000 │
│ - Upload/chunk documents │
│ - Run LLM research skills │
│ - Vector search (pgvector) │
│ - Pipeline mode (3 stages) │
└──────────────────────────────┘
Configuration Reference
| Variable | Default | Description |
|---|---|---|
OLLAMA_URL |
http://10.0.1.127:11434 |
Ollama API endpoint |
GPT_OSS_MODEL |
gpt-oss:20b |
LLM for research |
MARKER_API_URL |
http://localhost:8001 |
Marker OCR service |
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 | PostgreSQL port |
DB_NAME |
research |
Database name |
DB_USER |
research |
Database user |
DB_PASSWORD |
research123 |
Database password |
VECTOR_DIM |
4096 |
Embedding dimension |
APP_HOST |
0.0.0.0 |
App bind address |
APP_PORT |
8000 | App listen port |
DOC_STORE_DIR |
/workspace/documents |
Uploaded file storage |
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
vector dimension mismatch 1536 and 4096 |
VECTOR_DIM env var is 1536 |
Set VECTOR_DIM=4096 in compose file & rebuild |
'NoneType' object has no attribute 'batch_chunk' |
db module not initialized at import time |
Restart app container |
inconsistent types deduced for parameter $1 |
UUID vs text type conflict in upsert_document |
Use $1::uuid / $1::text casts (fixed in current code) |
OCR failed: No JSON output |
Marker model not downloaded | Run warmup step, commit image |
expected str, got list for vector insert |
asyncpg can't serialize Python list to vector type |
Convert to string [x,y,z] with ::vector cast (fixed in current code) |
Notes
- The marker-api container downloads ~3.3 GB of surya models on first run (layout, text_recognition, table_recognition, text_detection, ocr_error_detection). Commit the container to an image to persist these across restarts.
- The app container defaults to reading only pages 0-5 per PDF for fast development. To process full documents, remove
--page_range 0-5inmarker-api/server.py:58.