mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-25 15:22:24 +01:00
* chore: harden and update supporting services * chore(compose): pin ChromaDB and ntfy images by tag and digest chromadb/chroma:latest and a bare binwiederhier/ntfy change underneath an existing install on the next pull, so two hosts on the same commit can run different sidecar builds. SearXNG is already pinned this way. Pin chroma 1.5.9 and ntfy v2.32.0 to the multi-arch index digests that :latest currently resolves to, so nothing changes at runtime today. The standalone GPU files carry the same references, as test_gpu_compose_standalone.py requires. Add a test that every sidecar image has a release tag and a digest. --------- Co-authored-by: Léo <leograndcontact@gmail.com>
213 lines
10 KiB
YAML
213 lines
10 KiB
YAML
# Standalone AMD ROCm GPU Compose file for stack-management UIs (Portainer,
|
|
# Coolify, Dockhand, etc.) that accept only a single Compose file and do not
|
|
# reliably honor COMPOSE_FILE or multiple `-f` overlays.
|
|
#
|
|
# This is equivalent to: docker-compose.yml + docker/gpu.amd.yml.
|
|
# The base docker-compose.yml plus the docker/gpu.amd.yml overlay remain the
|
|
# source of truth — CLI users should keep using the COMPOSE_FILE overlay
|
|
# workflow. Keep this file in sync with both when either changes.
|
|
#
|
|
# Requires ROCm drivers on the host (kfd + DRI devices) and the host user
|
|
# running Docker in the `video` and `render` groups. Set RENDER_GID to your
|
|
# host's numeric render group id when needed. See docker/gpu.amd.yml for details.
|
|
services:
|
|
odysseus:
|
|
# Official multi-arch GHCR image (linux/amd64 + linux/arm64), published by
|
|
# the "ci / docker publish" workflow on every push to main and dev.
|
|
# Docker pulls this image when it is reachable, and only falls back to the
|
|
# local build below when the pull fails (e.g. no network on the host), so
|
|
# hosts without a build toolchain (Portainer stacks, etc.) get the
|
|
# registry build. For production, pin an immutable tag via ODYSSEUS_IMAGE
|
|
# - e.g. ghcr.io/odysseus-dev/odysseus:1.0.2-7c8070f (X.Y.Z-<sha>) - since
|
|
# :latest and bare :X.Y.Z tags move on every main push.
|
|
image: ${ODYSSEUS_IMAGE:-ghcr.io/odysseus-dev/odysseus:latest}
|
|
build: .
|
|
ports:
|
|
- "${APP_BIND:-127.0.0.1}:${APP_PORT:-7011}:7000"
|
|
volumes:
|
|
- ${APP_DATA_DIR:-./data}:/app/data:z
|
|
- ${APP_LOGS_DIR:-./logs}:/app/logs:z
|
|
# Cookbook remote-server SSH identity. Odysseus can generate a key here;
|
|
# add the shown public key to each remote server's authorized_keys.
|
|
- ${APP_DATA_DIR:-./data}/ssh:/app/.ssh:z
|
|
# Cookbook local model cache. Inside Docker, "Local" means the Odysseus
|
|
# container, so persist its HuggingFace cache under ./data/huggingface.
|
|
- ${APP_DATA_DIR:-./data}/huggingface:/app/.cache/huggingface:z
|
|
# Cookbook-installed Python CLIs/packages (vLLM, llama-cpp-python, etc.)
|
|
# land under /app/.local for the odysseus user. Persist them so a
|
|
# container recreate does not silently remove installed serve engines.
|
|
- ${APP_DATA_DIR:-./data}/local:/app/.local:z
|
|
extra_hosts:
|
|
# Lets the container reach local services on the Docker host, including
|
|
# Ollama at http://host.docker.internal:11434.
|
|
- "host.docker.internal:host-gateway"
|
|
environment:
|
|
- LLM_HOST=${LLM_HOST:-localhost}
|
|
- LLM_HOSTS=${LLM_HOSTS:-}
|
|
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-}
|
|
- RESEARCH_LLM_ENDPOINT=${RESEARCH_LLM_ENDPOINT:-}
|
|
- HF_TOKEN=${HF_TOKEN:-}
|
|
- HUGGING_FACE_HUB_TOKEN=${HUGGING_FACE_HUB_TOKEN:-}
|
|
- SEARXNG_INSTANCE=http://searxng:8080
|
|
- CHROMADB_HOST=chromadb
|
|
- CHROMADB_PORT=8000
|
|
- DATABASE_URL=${DATABASE_URL:-sqlite:///./data/app.db}
|
|
- AUTH_ENABLED=${AUTH_ENABLED:-true}
|
|
- LOCALHOST_BYPASS=${LOCALHOST_BYPASS:-false}
|
|
- COMPANION_BASE_URL=${COMPANION_BASE_URL:-}
|
|
- ODYSSEUS_ADMIN_USER=${ODYSSEUS_ADMIN_USER:-admin}
|
|
- ODYSSEUS_ADMIN_PASSWORD=${ODYSSEUS_ADMIN_PASSWORD:-}
|
|
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost,http://127.0.0.1}
|
|
- SECURE_COOKIES=${SECURE_COOKIES:-}
|
|
- EMBEDDING_URL=${EMBEDDING_URL:-}
|
|
- EMBEDDING_MODEL=${EMBEDDING_MODEL:-}
|
|
- EMBEDDING_API_KEY=${EMBEDDING_API_KEY:-}
|
|
- FASTEMBED_MODEL=${FASTEMBED_MODEL:-sentence-transformers/all-MiniLM-L6-v2}
|
|
- FASTEMBED_CACHE_PATH=${FASTEMBED_CACHE_PATH:-}
|
|
- CLEANUP_INTERVAL_HOURS=${CLEANUP_INTERVAL_HOURS:-24}
|
|
- ODYSSEUS_INPROCESS_POLLERS=${ODYSSEUS_INPROCESS_POLLERS:-1}
|
|
- ODYSSEUS_INPROCESS_TASKS=${ODYSSEUS_INPROCESS_TASKS:-1}
|
|
- ODYSSEUS_QWEN_NATIVE_COMPACT_BUILTINS=${ODYSSEUS_QWEN_NATIVE_COMPACT_BUILTINS:-1}
|
|
- ODYSSEUS_QWEN_SUPPRESS_LOCAL_CONTEXT=${ODYSSEUS_QWEN_SUPPRESS_LOCAL_CONTEXT:-0}
|
|
- ODYSSEUS_CAPTURE_MODEL_REQUESTS=${ODYSSEUS_CAPTURE_MODEL_REQUESTS:-0}
|
|
- ODYSSEUS_MCP_EMAIL_OWNER=${ODYSSEUS_MCP_EMAIL_OWNER:-}
|
|
- ODYSSEUS_SCRIPT_HOST=${ODYSSEUS_SCRIPT_HOST:-localhost}
|
|
- ODYSSEUS_CHAT_UPLOAD_MAX_BYTES=${ODYSSEUS_CHAT_UPLOAD_MAX_BYTES:-10485760}
|
|
- ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES=${ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES:-104857600}
|
|
- ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES=${ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES:-26214400}
|
|
- ODYSSEUS_MEMORY_IMPORT_MAX_BYTES=${ODYSSEUS_MEMORY_IMPORT_MAX_BYTES:-10485760}
|
|
- ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES=${ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES:-26214400}
|
|
- ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES=${ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES:-26214400}
|
|
- ODYSSEUS_EDITOR_DRAFT_MAX_BYTES=${ODYSSEUS_EDITOR_DRAFT_MAX_BYTES:-268435456}
|
|
- ODYSSEUS_STT_MAX_AUDIO_BYTES=${ODYSSEUS_STT_MAX_AUDIO_BYTES:-26214400}
|
|
- ODYSSEUS_ICS_MAX_BYTES=${ODYSSEUS_ICS_MAX_BYTES:-10485760}
|
|
- ODYSSEUS_TTS_CACHE_MAX_BYTES=${ODYSSEUS_TTS_CACHE_MAX_BYTES}
|
|
# Host workspace translation is opt-in. Keep the public compose file
|
|
# user-neutral; configure these in a local .env or use the host-workspace
|
|
# overlay with ODYSSEUS_HOST_WORKSPACE_DIR.
|
|
- ODYSSEUS_WORKSPACE_HOST_ROOT=${ODYSSEUS_WORKSPACE_HOST_ROOT:-}
|
|
- ODYSSEUS_WORKSPACE_CONTAINER_ROOT=${ODYSSEUS_WORKSPACE_CONTAINER_ROOT:-/workspace}
|
|
- ODYSSEUS_WORKSPACE_DEFAULT=${ODYSSEUS_WORKSPACE_DEFAULT:-}
|
|
- DATA_BRAVE_API_KEY=${DATA_BRAVE_API_KEY:-}
|
|
- GOOGLE_API_KEY=${GOOGLE_API_KEY:-}
|
|
- GOOGLE_PSE_CX=${GOOGLE_PSE_CX:-}
|
|
- GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID:-}
|
|
- GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET:-}
|
|
- GOOGLE_OAUTH_REDIRECT_URI=${GOOGLE_OAUTH_REDIRECT_URI:-}
|
|
# Externally reachable origin for MCP OAuth callbacks. The container
|
|
# always listens on 7000 and cannot see the host port map above, so
|
|
# remote MCP OAuth needs this set whenever the browser reaches
|
|
# Odysseus on anything other than http://localhost:7000.
|
|
- OAUTH_REDIRECT_BASE_URL=${OAUTH_REDIRECT_BASE_URL:-}
|
|
- TAVILY_API_KEY=${TAVILY_API_KEY:-}
|
|
- SERPER_API_KEY=${SERPER_API_KEY:-}
|
|
# PUID / PGID — the user/group the container drops to before
|
|
# running uvicorn (entrypoint also chowns /app/data + /app/logs
|
|
# to match, so bind-mounted files stay editable from the host).
|
|
# 1000 is the default first user on most Linux installs. If your
|
|
# host user has a different id, override here or via .env, e.g.:
|
|
# PUID=1001
|
|
# PGID=1001
|
|
# Find yours with: id -u / id -g
|
|
- PUID=${PUID:-1000}
|
|
- PGID=${PGID:-1000}
|
|
depends_on:
|
|
searxng:
|
|
condition: service_healthy
|
|
chromadb:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
# AMD ROCm overlay (from docker/gpu.amd.yml).
|
|
devices:
|
|
- /dev/kfd
|
|
- /dev/dri
|
|
group_add:
|
|
- video
|
|
- ${RENDER_GID:-render}
|
|
|
|
chromadb:
|
|
image: docker.io/chromadb/chroma:1.5.9@sha256:1e0b73a187a28757c572acba508c46f48c9e8b0acaf5c20e6d95cdedce1acdf6
|
|
ports:
|
|
- "${CHROMADB_BIND:-127.0.0.1}:8100:8000"
|
|
volumes:
|
|
# The image runs `chroma run /config.yaml`, whose persist_path is "/data".
|
|
# It declares no VOLUME, so anything written outside a mount lives in the
|
|
# container's writable layer and dies with it. /chroma/chroma is where
|
|
# pre-1.0 images kept the store; mounting there now catches nothing.
|
|
- chromadb-data:/data
|
|
environment:
|
|
- ANONYMIZED_TELEMETRY=FALSE
|
|
restart: unless-stopped
|
|
|
|
searxng:
|
|
# Pinned, not :latest — odysseus waits on searxng's healthcheck
|
|
# (depends_on: condition: service_healthy), so a broken upstream `latest`
|
|
# tag blocks the whole app from starting. 2026.6.2 crashes on boot with
|
|
# `KeyError: 'default_doi_resolver'`, failing the healthcheck (issue #1414).
|
|
# Update all default/GPU pins together after native startup and trust checks.
|
|
image: docker.io/searxng/searxng:2026.10.24-ef0ec03da@sha256:7c7cbe509429e82755104b2a95f52085a98633c95d53f44e4e905fa3a59f08c8
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
if [ ! -s /etc/searxng/settings.yml ] || grep -q 'odysseus-local-searxng-json-2026-05-30\|__SEARXNG_SECRET__' /etc/searxng/settings.yml; then
|
|
secret="$${SEARXNG_SECRET:-}"
|
|
if [ -z "$$secret" ]; then
|
|
secret="$$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"
|
|
fi
|
|
sed "s|__SEARXNG_SECRET__|$$secret|g" /tmp/searxng-settings.yml.template > /etc/searxng/settings.yml
|
|
fi
|
|
# Advisory: a settings file the migration cannot parse or rewrite must
|
|
# not be what stops searxng from booting. It explains itself on stderr
|
|
# and we carry on, letting searxng report anything genuinely wrong.
|
|
/usr/local/searxng/.venv/bin/python /tmp/migrate-searxng-settings.py /etc/searxng/settings.yml || true
|
|
exec /usr/local/searxng/entrypoint.sh
|
|
ports:
|
|
- "127.0.0.1:8080:8080"
|
|
volumes:
|
|
- searxng-data:/etc/searxng
|
|
- ./config/searxng/settings.yml:/tmp/searxng-settings.yml.template:ro,z
|
|
- ./scripts/migrate_searxng_settings.py:/tmp/migrate-searxng-settings.py:ro,z
|
|
environment:
|
|
- SEARXNG_BASE_URL=http://localhost:8080/
|
|
- SEARXNG_SECRET=${SEARXNG_SECRET:-}
|
|
# The official searxng image runs as the non-root `searxng` user, but its
|
|
# entrypoint still needs to chown /etc/searxng on first boot, drop privs via
|
|
# su-exec, and (with our wrapper above) write settings.yml into the named
|
|
# volume. Without these capabilities the wrapper aborts at the redirection
|
|
# with EACCES and the container fails its healthcheck with permission
|
|
# errors during setup. Mirrors the cap set recommended by the upstream
|
|
# searxng-docker compose file. See issue #721.
|
|
cap_drop:
|
|
- ALL
|
|
cap_add:
|
|
- CHOWN
|
|
- SETGID
|
|
- SETUID
|
|
- DAC_OVERRIDE
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8080/', timeout=5).read(1)\""]
|
|
interval: 5s
|
|
timeout: 6s
|
|
retries: 20
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
ntfy:
|
|
image: docker.io/binwiederhier/ntfy:v2.32.0@sha256:59250e1d8be2ee2186c4560322f625faef22ace380331f05b63f8842dbd62602
|
|
command: serve
|
|
ports:
|
|
- "${NTFY_BIND:-127.0.0.1}:8091:80"
|
|
volumes:
|
|
- ntfy-cache:/var/cache/ntfy
|
|
environment:
|
|
- NTFY_BASE_URL=${NTFY_BASE_URL:-http://localhost:8091}
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
searxng-data:
|
|
chromadb-data:
|
|
ntfy-cache:
|