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:

  1. Duplicate maintenance. Odysseus wins updates. You win merge conflicts.
  2. 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:

  1. Clone Odysseus
  2. Patch MCP tool logic (always include, don’t wait for keywords)
  3. Add psycopg2-binary
  4. Build image: 0.1.BUILDNUMBER
  5. Push to registry
  6. Update deployment.yaml image tag
  7. 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:

  1. Cloning Odysseus in CI/CD (no source duplication)
  2. CloudNativePG database (HA + backups)
  3. Traefik ingress + TLS
  4. Kustomize overlays (dev/staging/prod)
  5. ExternalSecrets + Bao vault
  6. 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.

🚀