docker-compose.yml
Rama main · réplica de lectura del repositorio original.
# lynox — single-instance Docker Compose setup
#
# Quick start:
# 1. Copy .env.example to .env and fill in ANTHROPIC_API_KEY
# 2. Required on native Linux, before the first start: mkdir -p ~/.lynox,
# then the line under "Who the container runs as" in .env.example
# (Docker Desktop: only the mkdir)
# 3. docker compose up
# 4. Open http://localhost:3000 and enter the access token:
# docker compose exec lynox cat /home/lynox/.lynox/.access-token
# (the log shows only its first 8 characters, on the first start)
#
# Full docs: https://docs.lynox.ai/getting-started/
services:
lynox:
image: ghcr.io/lynox-ai/lynox:latest
# Explicit platform pin — safe default while the multi-arch manifest
# rolls out. Apple Silicon users can remove this line once :latest is
# multi-arch to get a native arm64 image instead of Rosetta emulation.
platform: linux/amd64
restart: unless-stopped
read_only: true
# The user the engine runs as. It has to own ~/.lynox on the host, which
# is mounted below. Set LYNOX_UID/LYNOX_GID in .env before the first start
# (see .env.example and https://docs.lynox.ai/setup/docker/). Without them it
# runs as the image's own user, 1001, which can only write ~/.lynox if that
# directory belongs to 1001.
user: "${LYNOX_UID:-1001}:${LYNOX_GID:-1001}"
ports:
- "3000:3000"
tmpfs:
- /tmp:size=512M
- /workspace:size=256M,uid=${LYNOX_UID:-1001},gid=${LYNOX_GID:-1001}
# ONNX embedding cache for the Knowledge Graph (Xenova/transformers.js).
# Without this writable tmpfs the read_only root filesystem rejects the
# subdirectory `mkdir` and every web-research call spams the engine log
# with ENOENT; semantic search has to re-download from huggingface on
# every restart anyway, so a tmpfs is exactly the right lifetime.
- /home/lynox/.cache:size=512M,uid=${LYNOX_UID:-1001},gid=${LYNOX_GID:-1001}
cap_drop:
- ALL
security_opt:
- no-new-privileges
pids_limit: 512
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
environment:
# An id the image has no passwd entry for would otherwise get / as its home.
- HOME=/home/lynox
# ── Required ──────────────────────────────────────
- ANTHROPIC_API_KEY # Your Anthropic API key (starts with sk-ant-)
# ── Recommended ──────────────────────────────────
# Set these explicitly for production. If omitted, they are
# auto-generated on first start and saved to the volume.
# - LYNOX_HTTP_SECRET # Access token for Web UI login
# - LYNOX_VAULT_KEY # Encrypt data at rest (generate: openssl rand -base64 48)
# ── Required for the /login form (CSRF) ──────────
# SvelteKit's CSRF guard rejects POST forms when the request URL
# protocol doesn't match the browser's Origin header. adapter-node
# defaults to https when ORIGIN is unset, so http://localhost gets
# 403'd on every form submit (e.g. token-based login). Set ORIGIN
# to your actual bind URL — scheme + host + port, no path.
- ORIGIN=http://localhost:3000
# ── Web Search ────────────────────────────────────
- SEARXNG_URL=http://searxng:8080 # Free, unlimited via SearXNG below.
# Without SEARXNG_URL set, lynox falls back to a best-effort
# DuckDuckGo HTML-scrape — works for casual research, but SearXNG
# is the only full-quality backend.
# ── Optional features ─────────────────────────────
# - GOOGLE_CLIENT_ID # Google Workspace OAuth
# - GOOGLE_CLIENT_SECRET # Google Workspace OAuth
# - LYNOX_BUGSINK_DSN # Error reporting (opt-in, Bugsink)
# ⚠ CRITICAL: This volume persists your data AND vault key.
# Without it, all data and encryption keys are lost on container restart.
# Create it first: mkdir -p ~/.lynox. The long form makes a missing
# directory fail the start; the short form would let Docker create it
# owned by root, which only sudo can repair.
volumes:
- type: bind
source: ${HOME}/.lynox
target: /home/lynox/.lynox
bind:
create_host_path: false
networks:
- internal
# ── SearXNG — free web search ──────────────────────
searxng:
image: searxng/searxng:latest
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=64M
cap_drop:
- ALL
security_opt:
- no-new-privileges
pids_limit: 128
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
volumes:
- ./searxng/settings.yml:/etc/searxng/settings.yml:ro
networks:
- internal
# What this network is, and what it is not — measured 2026-08-10, because the
# docs used to call it "network isolation" and it is not that.
#
# It is NOT `internal: true`. Outbound from both containers is unrestricted;
# the engine needs it to reach the LLM API, so `internal: true` would break it.
#
# What it DOES buy is opt-in membership: a service added without a `networks:`
# key joins Compose's implicit `default` network instead and gets no route and
# no DNS to the services here — measured both ways with real containers.
#
# ⚠ Two limits, both measured, so nobody mistakes this for a boundary:
# - A sidecar with the Docker socket defeats it in one command
# (`docker network connect`), so it stops accidents, not adversaries.
# - It cuts BOTH ways. Anything you add that must reach `lynox` — the
# cloudflared sidecar in setup/remote-access.md is the live example — needs
# `networks: [internal]`, or it starts happily and silently cannot connect.
networks:
internal:
driver: bridge