services: marina: # Builds from this repo by default. Set MARINA_IMAGE to run a prebuilt # image instead (e.g. your registry's tag) — deploy pipelines do this. image: ${MARINA_IMAGE:-marina:local} build: . # Pull provider keys / secrets from a local .env file if present. # Copy .env.example → .env and uncomment what you need; every setting is # documented in config/environment.reference. env_file: - path: .env required: false # SECURE-BY-DEFAULT publish: ports bind the HOST's loopback interface, so a # fresh `docker compose up` is reachable from this machine only. To expose # Marina to a network, set MARINA_BIND_IP (0.0.0.0 for every interface, or a # specific host IP) — and BEFORE you do, enable real auth # (MARINA_AUTH=better-auth) or set MODEL_API_KEYS; see the SECURITY block # below and docs/guides/deployment.md. Reverse proxies and load balancers # reach the host by its routable address, not loopback, so a deployment # behind one MUST set this or every health check fails. ports: - "${MARINA_BIND_IP:-127.0.0.1}:3300:3300" # WebSocket + Web Chat + Dashboard + OpenAI/Ollama API - "${MARINA_BIND_IP:-127.0.0.1}:3301:3301" # MCP (optional — drop this line to keep it internal) # Telnet is plaintext + unauthenticated and OFF by default. To enable on a # trusted network: set TELNET_PORT=4000 in `environment:` and publish it: # - "${MARINA_BIND_IP:-127.0.0.1}:4000:4000" volumes: # Docker-managed storage works on first run without host UID/GID repair. # Operators who need host-visible files can set MARINA_DATA_VOLUME to an # absolute/bind path after making it writable by container UID 1000. - ${MARINA_DATA_VOLUME:-marina-data}:/app/data environment: # Persistence — keep DB + assets on the mounted volume (set in the image too) - DB_PATH=/app/data/marina.db - ASSETS_DIR=/app/data/assets # Code Mode workspace — a subdir of the same /app/data volume (no separate # mount). Marina creates it and `git init`s it on boot, so it persists # across redeploys and checkpoint/revert/diff work. Without this, Code Mode # falls back to /app (the app's own code) — see `code doctor`. - MARINA_CODE_ROOTS=/app/data/workspace - MARINA_CODE_DEFAULT_ROOT=/app/data/workspace - LOG_FORMAT=json # World to load: default | commons | research | personal | evolve | craft | markets | demos | empty - MARINA_WORLD=${MARINA_WORLD:-default} # The image binds 0.0.0.0 INSIDE the container (WS_HOST in the Dockerfile # — required for published ports to work). Marina's startup gate refuses a # non-loopback bind with passwordless login. The publish address above is # passed in here, so a loopback publish (the default) counts as loopback # for that check and boots; widening MARINA_BIND_IP to a routable address # trips the normal fatal refusal until you enable MARINA_AUTH=better-auth # (or knowingly set MARINA_ALLOW_INSECURE_PUBLIC=true). Keep this line in # step with the `ports:` host address. The trust profile still follows # the real bind (`public` here; the local overlay declares `local`). - MARINA_PUBLISHED_HOST=${MARINA_BIND_IP:-127.0.0.1} # Unified Canvas (heavy ReactFlow alternate UI) is retired from the standard # dashboard. Set true to expose it as a standalone surface at /?unified. # - MARINA_UNIFIED_CANVAS=true # Resume Marina-spawned agents on boot (e.g. after a redeploy). Each agent's # config is persisted to the DB (on the mounted volume), so with this set # they respawn into their rooms at startup. Unset, they resume only under # the `local` profile (docker-compose.local.yml) with a provider key; this # file's default `public` profile keeps them off. Spend is capped at # MARINA_DAILY_SPEND_CAP_USD (default 25 USD per UTC day; 0 = no cap). # - AGENT_AUTORESPAWN=true # ── SINGLE USER ON THIS MACHINE ── # This file derives the `public` trust profile (the container binds # 0.0.0.0): model API closed, gates enforced. For a personal instance add # the local overlay, which declares MARINA_PROFILE=local: # docker compose -f docker-compose.yml -f docker-compose.local.yml up -d # Safe only with the 127.0.0.1 port publish above — see that file. # ── SECURITY (read docs/guides/deployment.md before exposing publicly) ── # The /v1/* and /api/* LLM endpoints require auth unless you opt out. # Set real tokens for any internet-facing deployment: # - MODEL_API_KEYS=sk-marina-changeme # # DASHBOARD ACCESS — IMPORTANT. The dashboard/canvas/asset API is gated: # every /api/* read+write needs a session token. A fresh browser has none # (a token is only issued after logging in via the webchat), so WITHOUT one # of the two options below the dashboard shows NO content and the canvas # returns 401 on upload. Pick one: # • Trusted/internal network → set MARINA_OPEN_API=true (opens the whole # API with no token — NEVER do this on an internet-facing host). # • Public host → enable real auth instead: MARINA_AUTH=better-auth # (see docs/authentication.md), and have users sign in. # - MARINA_OPEN_API=true # CORS allow-list when the dashboard/clients live on another origin: # - ALLOWED_ORIGINS=https://marina.example.com # Auto-grant admin to these entity names on login: # - MARINA_ADMINS=YourName # ── LLM providers (needed for agent spawning / room agents) ── # Set the key for your provider HERE (or in .env). `marina/default` agents # — including room agents — use env keys first, then Admin-panel provider # keys. Env keys also survive DB/volume # resets, unlike Admin-panel keys. External providers are reached over # OUTBOUND HTTPS (443) — no published port is required for this. # - ANTHROPIC_API_KEY=sk-ant-... # - OPENAI_API_KEY=sk-... # - GEMINI_API_KEY=... # - OPENROUTER_API_KEY=sk-or-... # Behind a corporate proxy, also set HTTPS_PROXY / HTTP_PROXY here. # ── Local LLM (the in-cluster `llama` service defined below) ── # The code defaults base URLs to localhost (native installs); in compose, # marina reaches the llama.cpp container by service name, so pin it here. # LLAMA_BASE_URL/LLAMA_API_KEY/LLAMA_MODEL come from .env when the profile # is enabled. Do not inject a base URL unconditionally: that would make # Marina report a provider even while the profiled service is absent. # Address models as `llama/`, or leave agents on `marina/default`. # Running Ollama on the host instead of the llama service? Point at it # (Docker Desktop): - OLLAMA_BASE_URL=http://host.docker.internal:11434/v1 # VibeThinker (optional, `--profile vibethinker`). Reaches the vLLM service # by name; address models as `vibethinker/`. Key comes from .env. # ── Optional chat adapters ── # - TELEGRAM_TOKEN=your_token # - DISCORD_TOKEN=your_token # - DISCORD_CHANNEL_IDS=channel1,channel2 restart: unless-stopped # ── Local LLM server (llama.cpp) ─────────────────────────────────────────── # Internal-only: there is intentionally NO `ports:` mapping, so this is never # reachable from the host/ALB/internet — only the `marina` container can reach # it on the compose network at http://llama:8080. Hardened per llama.cpp # SECURITY.md + the Synacktiv reference (sandboxed, cap-dropped, read-only # model mount, GPU-pinned, api-key required). Requires the NVIDIA Container # Toolkit on the host. Tunables live in .env (host-local), not hardcoded here. llama: # Optional local-model stack. Keeping this behind a profile is essential: # the normal `docker compose up` path must not require NVIDIA hardware, a # model file, or LLAMA_API_KEY when the operator uses a cloud provider (or # wants to explore Marina without live agents). profiles: ["llama"] image: ghcr.io/ggml-org/llama.cpp@sha256:e502860c8aa147e74e7cf42568fa2a8407c578dd291c1b231f698a55dd83fef6 command: - "-m" # Use `:-` rather than `:?`: Compose interpolates profiled-out services # too, so parse-time required variables would break the Marina-only path. # llama.cpp reports the missing model/key when this profile is selected. - "/models/${LLAMA_MODEL:-}" - "--host" - "0.0.0.0" - "--port" - "8080" - "-ngl" - "99" # offload all layers; split across the visible GPUs - "-c" - "${LLAMA_CTX:-16384}" - "--jinja" # apply the model's Jinja2 chat template (required for OpenAI-standard tool calling) - "--flash-attn" # flash attention — explicit on; this build requires a value, bare -fa is rejected - "on" # (A10G / Ampere SM 8.6 supported; already auto-enabled, making it explicit) - "--api-key" - "${LLAMA_API_KEY:-}" # required even internally when this profile runs expose: - "8080" # network-internal only (NOT published) volumes: - ${LLAMA_MODELS_DIR:-/opt/models}:/models:ro # read-only; verify gguf sha256 on host environment: - NVIDIA_DRIVER_CAPABILITIES=compute,utility deploy: resources: reservations: devices: # Pin GPUs 0 + 1 — the 27B Q4_K_M (~17 GB) needs two A10Gs; GPUs 2 & 3 stay free. - driver: nvidia device_ids: ["0", "1"] capabilities: ["gpu"] cap_drop: ["ALL"] security_opt: - "no-new-privileges:true" read_only: true tmpfs: - /tmp mem_limit: 48g pids_limit: 1024 restart: unless-stopped # ── VibeThinker reasoning model (vLLM, OpenAI-compatible) ──────────────────── # Optional secondary model (WeiboAI VibeThinker, 1.5B/3B). No GGUF is published, # so it's served by vLLM rather than llama.cpp. Internal-only (no `ports:`) — # only `marina` reaches it at http://vibethinker:8000. Pinned to GPU 2, leaving # llama on GPUs 0+1 and GPU 3 free (the model needs <10 GB; an A10G has 24 GB). # Enable by running `--profile vibethinker`; otherwise this service stays down. # # NOT YET VALIDATED ON THE GPU HOST — confirm before relying on it: # - the image is pinned to vLLM v0.31.0 by digest; bump it deliberately, # - tool calling needs `--enable-auto-tool-choice` + a `--tool-call-parser` # matching VibeThinker's template; the right parser is model-specific, # - vLLM writes caches and uses shared memory, so it can't run `read_only` # like llama.cpp; a writable HF cache + shm_size are provided instead. # # VIBETHINKER_MODEL and VIBETHINKER_API_KEY are REQUIRED when you enable this # profile, but they use `:-` (not `:?`) defaults on purpose: `docker compose` # interpolates EVERY service on any command, even ones excluded by profile, so # a `:?` here would abort all compose operations (including the default # `marina`-only deploy) whenever these are unset. With `:-`, the file always # parses; only `--profile vibethinker up` needs the values set (vLLM errors # clearly on an empty --model if you forget). vibethinker: profiles: ["vibethinker"] image: vllm/vllm-openai:v0.31.0@sha256:c1c9f6fd5c109ba7f0546a59f5b2f15fb87f64c77782e90a27b648b42a8e67c3 command: - "--model" - "${VIBETHINKER_MODEL:-}" - "--served-model-name" - "vibethinker" - "--host" - "0.0.0.0" - "--port" - "8000" - "--max-model-len" - "${VIBETHINKER_CTX:-40960}" - "--api-key" - "${VIBETHINKER_API_KEY:-}" expose: - "8000" # network-internal only (NOT published) volumes: # Optional local model dir (read-only) + a writable HF cache for downloads. - ${VIBETHINKER_MODELS_DIR:-/opt/models}:/models:ro - ${HF_CACHE_DIR:-./marina-data/hf-cache}:/root/.cache/huggingface environment: - NVIDIA_DRIVER_CAPABILITIES=compute,utility - HF_HUB_OFFLINE=${HF_HUB_OFFLINE:-0} shm_size: "8gb" deploy: resources: reservations: devices: # Pin GPU 2 — single free A10G; the 1.5B/3B model fits with room to spare. - driver: nvidia device_ids: ["2"] capabilities: ["gpu"] security_opt: - "no-new-privileges:true" restart: unless-stopped volumes: marina-data: