The Journey Begins: From Chat to Workspace
Somewhere in 2024, a developer named PewDiePie (yes, really) builds something remarkable. He creates Odysseus because running local AI feels fun and powerful, but all available options felt like steps backward.
No subscriptions. No cloud lock-in. No vendor raising your API pricing tomorrow. Just you, your hardware, and your AI models.
Two years later: 74,000 GitHub stars, 2,800 forks, and it’s grown into a full AI workstation with agents, deep research, coding tools, email/calendar integration, and memory—all running on your own hardware with complete data privacy.
This is the story of how we brought this monster to Kubernetes.
What Is Odysseus, Really?
Don’t think “ChatGPT clone number 47.” Odysseus is a self-hosted interface for talking to language models—chat, autonomous agents, tools, model serving, email, research, and more. Local-first, privacy-first, and no telemetry.
But here’s the real kicker: it’s not one thing.
Chat — Talk to local models, OpenAI, Anthropic, or OpenRouter. Switch between them. Easy.
Agents — Give it tools (bash, files, web, memory) and it does the rest itself. Built with opencode, MCP, web, files, shell, skills, and memory.
Cookbook — Hardware-aware model recommendations. One-click download, serve via vLLM/llama.cpp. 270+ models catalogued.
Deep Research — Multi-step web research that gathers, reads, and synthesizes sources into a written report.
Email — IMAP/SMTP inbox with AI-triage, tags, summaries, reminders, and draft replies.
Documents — Writing-first editor with AI-edits, suggestions, Markdown, HTML, CSV.
Memory — Persistent context that your agent and you share. ChromaDB vector store. Semantic search.
So not: “I can chat.”
But: “I have a complete workstation.”
The Technical Story: Bringing It to Kubernetes
We have a problem. Odysseus runs great in Docker Compose on localhost. But how do you bring this to production?
Step 1: The Misstep
We start with the obvious: copy the Odysseus source into our deployment repo. Add a Dockerfile. Build via CI/CD. Simple.
But now you have two problems:
- Duplicate maintenance. Odysseus wins updates. You win merge conflicts.
- Source sync lag. You’re always one version behind.
Step 2: The Epiphany
Better idea: why not clone in the CI/CD workflow?
- name: Clone Odysseus source
run: |
git clone --depth 1 --branch dev \
https://github.com/pewdiepie-archdaemon/odysseus.git /tmp/odysseus-src
cp -r /tmp/odysseus-src/* .
echo "psycopg2-binary" >> requirements.txt # Odysseus needs this
Now:
- You have only K8s manifests in your repo
- Every build pulls latest Odysseus source from GitHub
- No merge conflicts. No source duplication.
- Always up-to-date.
Genius? Yes. Lazy? Also yes. Lazy is efficient.
Step 3: PostgreSQL + CloudNativePG
Odysseus requires a database. Local: docker-compose db. Production?
CloudNativePG. Three instances. HA. Automatic failover. Daily + hourly backups to Garage S3.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: odysseus-db
spec:
instances: 3
bootstrap:
initdb:
database: odysseus
owner: odysseus
backup:
barmanObjectStore:
destinationPath: s3://odysseus-backups
endpointURL: https://garage.local:9000
Database-as-code. Backups as first-class citizens.
Step 4: Secrets Management
Odysseus wants API keys (OpenAI, Anthropic, etc.). Where do you put them?
OpenBao vault. ExternalSecrets operator. K8s syncs your secrets automatically.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: odysseus-secrets
spec:
secretStoreRef:
name: bao-secretstore
target:
name: odysseus-secrets
data:
- secretKey: openai-api-key
remoteRef:
key: odysseus/providers
property: openai_api_key
Fallback? K8s Secret. For dev.
Step 5: Ingress + TLS
Traefik. Security headers. Let’s Encrypt.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: odysseus
spec:
ingressClassName: traefik
tls:
- hosts:
- odysseus.local
secretName: odysseus-tls
rules:
- host: odysseus.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: odysseus
port:
number: 80
HTTPS by default. Redirects. HSTS. XSS protection. Everything.
Step 6: Kustomize Overlays
Three environments: dev, staging, prod.
Dev: 1 replica, DEBUG logging, HTTP cookies. Staging: 1 replica, INFO logging, HTTPS. Prod: 2 replicas, WARN logging, higher resources.
k8s/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── kustomization.yaml
└── overlays/
├── dev/kustomization.yaml
├── staging/kustomization.yaml
└── prod/kustomization.yaml
One command per environment:
kubectl apply -k k8s/overlays/prod/
Kustomize merges, patches, customizes. No templating hell.
Step 7: CI/CD Automation
Every push to main:
- Clone Odysseus
- Patch MCP tool logic (always include, don’t wait for keywords)
- Add psycopg2-binary
- Build image:
0.1.BUILDNUMBER - Push to registry
- Update
deployment.yamlimage tag - Commit + push back (with
[skip ci])
K8s sees the update, rolling deploy, done.
The Problems We Hit
Problem 1: Dockerfile Syntax Errors
Odysseus Dockerfile expects entrypoint.sh in root. We copied only selective files.
Fix: cp -r /tmp/odysseus-src/* . — copy everything.
Problem 2: psycopg2 Missing
SQLAlchemy PostgreSQL dialect requires psycopg2, but not in requirements.txt.
Fix: echo "psycopg2-binary" >> requirements.txt in the workflow.
Problem 3: Health Probes
K8s probes to /healthz and /readyz. Odysseus redirects to /login (302).
Fix: That’s normal. Odysseus is authenticated. Probes check connectivity, not auth status.
Problem 4: Database Migrations
Odysseus has SQLite-specific migrations (PRAGMA table_info()). PostgreSQL doesn’t understand that.
Fix: The migrations run, errors are handled, tables are created. App runs.
The End Result: Production Ready
After everything:
- ✅ Odysseus runs in K8s
- ✅ CloudNativePG database with HA + backups
- ✅ Traefik ingress + TLS
- ✅ ExternalSecrets + Bao vault
- ✅ Kustomize overlays (dev/staging/prod)
- ✅ Forgejo CI/CD automation
- ✅ Zero source duplication
- ✅ Always-latest Odysseus
# Deploy dev
kubectl apply -k k8s/overlays/dev/
# Deploy production
kubectl apply -k k8s/overlays/prod/
# Watch it roll out
kubectl rollout status deployment/odysseus -n odysseus
Lessons Learned
1. Clone in CI/CD, Not in Repo
Odysseus wins updates constantly. Duplicate source in your repo is a nightmare. Clone upstream in workflows. Always latest.
2. Kustomize > Helm for Simple Cases
Overlays > templating. Merge > render. Intuitive > magic.
3. Don’t Fight Upstream Quirks
Odysseus migrations throw errors in PostgreSQL. They’re handled. App runs. Don’t fix, accept.
4. Test Local First
Docker Compose dev → K8s is a leap. Docker Compose must be perfect.
5. Security > Convenience
Bao vault. ExternalSecrets. RBAC. Non-root user. Takes time. Worth it.
The Future
22,400+ GitHub users now. Growing. Active maintainers. v1.0 released.
What we want:
- Multi-user RBAC — Per-user privilege gates
- Model sharing — Deploy models centrally, share via registry
- Skill marketplace — Community-built Odysseus skills
- Agent replay — Record/replay agent runs for debugging
But the foundation? Solid.
TL;DR
Odysseus is self-hosted AI: chat, agents, research, email, documents, calendar, memory. Local-first, privacy-first, open source.
We brought it to Kubernetes by:
- Cloning Odysseus in CI/CD (no source duplication)
- CloudNativePG database (HA + backups)
- Traefik ingress + TLS
- Kustomize overlays (dev/staging/prod)
- ExternalSecrets + Bao vault
- Forgejo workflows (push → build → deploy)
Result: Production-grade, privacy-first, self-hosted AI workspace.
Pull requests welcome. Stars encouraged. Fork if needed.
Want more?
Have fun. Keep it local. Keep it private. Keep it open.
🚀