# ============================================================================= # AnythingMCP — Full Stack Deployment (Single Container) # ============================================================================= # Usage: docker compose up -d # UI: http://localhost:3000 # API: http://localhost:4000 # MCP: http://localhost:4000/mcp # ============================================================================= # Project name — also the prefix for the auto-created network and volumes. # Override COMPOSE_PROJECT_NAME (in .env or the environment) to run more than # one AnythingMCP stack on the same host without name collisions. name: ${COMPOSE_PROJECT_NAME:-amcp} services: # Caddy Reverse Proxy (optional — enables HTTPS with automatic Let's Encrypt) # Activated by COMPOSE_PROFILES=proxy in .env (setup.sh sets this automatically) caddy: image: caddy:2-alpine profiles: ["proxy"] container_name: ${COMPOSE_PROJECT_NAME:-amcp}-caddy ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: app: condition: service_healthy restart: unless-stopped # NestJS Backend + Next.js Frontend (single container) app: image: helpcodeai/anythingmcp:latest build: context: . dockerfile: Dockerfile container_name: ${COMPOSE_PROJECT_NAME:-amcp}-app # Bind published ports to loopback by default so the API/MCP endpoint is # not exposed on every network interface out of the box. To reach it from # the LAN or the public internet, set APP_BIND_IP=0.0.0.0 in .env (and put # it behind the Caddy proxy / a firewall). The Caddy service talks to the # app over the internal Docker network, so it is unaffected by this bind. ports: - "${APP_BIND_IP:-127.0.0.1}:${FRONTEND_PORT:-3000}:3000" - "${APP_BIND_IP:-127.0.0.1}:${BACKEND_PORT:-4000}:4000" environment: - NODE_ENV=production - PORT=4000 - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:4000} - DATABASE_URL=postgresql://amcp:${POSTGRES_PASSWORD:-amcp}@postgres:5432/anythingmcp - JWT_SECRET=${JWT_SECRET:-change-me-in-production-min-32-chars} - ENCRYPTION_KEY=${ENCRYPTION_KEY:-change-me-in-production-exactly-32} # `plaintext` only to roll back past encrypted connector variables (docs/operations/backup-restore.md) - ENV_VARS_AT_REST=${ENV_VARS_AT_REST:-} - CORS_ORIGIN=${CORS_ORIGIN:-http://localhost:3000} - SERVER_URL=${SERVER_URL:-http://localhost:4000} - FRONTEND_URL=${FRONTEND_URL:-http://localhost:3000} # MCP Authentication (oauth2 | legacy | both | none) # In `legacy`/`both` mode the /mcp endpoint requires MCP_API_KEY or # MCP_BEARER_TOKEN. If neither is set, requests are REFUSED (401) unless # MCP_ALLOW_ANONYMOUS=true is set explicitly — this avoids silently # exposing configured MCP tools to unauthenticated network clients. - MCP_AUTH_MODE=${MCP_AUTH_MODE:-legacy} - MCP_API_KEY=${MCP_API_KEY:-} - MCP_BEARER_TOKEN=${MCP_BEARER_TOKEN:-} # Set to true ONLY for trusted local/dev use to allow unauthenticated # /mcp access when no MCP credentials are configured (legacy mode). - MCP_ALLOW_ANONYMOUS=${MCP_ALLOW_ANONYMOUS:-false} # Registration (false = invite-only after first admin, true = anyone can register) - ALLOW_OPEN_REGISTRATION=${ALLOW_OPEN_REGISTRATION:-false} # Email (SMTP) for verification codes, invitations and password resets. # setup.sh writes these into .env; without them here the app never saw # them. Empty SMTP_HOST = no instance-wide mail (workspace admins can # still set their own SMTP in Settings). SMTP_PORT defaults to 587; # SMTP_SECURE=true for implicit TLS (port 465 implies it). - SMTP_HOST=${SMTP_HOST:-} - SMTP_PORT=${SMTP_PORT:-} - SMTP_USER=${SMTP_USER:-} - SMTP_PASS=${SMTP_PASS:-} - SMTP_FROM=${SMTP_FROM:-} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-} - SMTP_REPLY_TO=${SMTP_REPLY_TO:-} - SMTP_SECURE=${SMTP_SECURE:-} # Uncomment to enable Redis (optional — used for response caching and rate limiting) # - REDIS_URL=redis://redis:6379 # Deutsche Bahn connector: URL of a MOTIS instance. Set together with # COMPOSE_PROFILES=motis to run the bundled one (see deploy/motis); the # adapter then fills its MOTIS_URL in automatically at import. Leave # unset to be asked for a URL when installing the adapter. - MOTIS_INTERNAL_URL=${MOTIS_INTERNAL_URL:-} # Hosts the SSRF guard may reach even though they resolve to a private # address. Needed for the bundled MOTIS (`motis`) and any other API you # run on the same Docker network. - SSRF_ALLOWED_HOSTS=${SSRF_ALLOWED_HOSTS:-} # Largest upstream response one tool call reads, in bytes. Empty = the # default, 10 MB (see .env.example). - TOOL_MAX_RESPONSE_BYTES=${TOOL_MAX_RESPONSE_BYTES:-} # Knowledge Graph LLM (optional): hosted providers need their API key, # a local OpenAI-compatible endpoint needs KG_LLM_BASE_URL and no key # (KG_LLM_API_KEY is only sent when set). From inside this container # `localhost` is the app itself — point at the host or a sibling # service instead, e.g. http://host.docker.internal:11434/v1 # (Linux: extra_hosts: ["host.docker.internal:host-gateway"]). - KG_LLM_ENABLED=${KG_LLM_ENABLED:-} - KG_LLM_PROVIDER=${KG_LLM_PROVIDER:-} - KG_LLM_MODEL=${KG_LLM_MODEL:-} - KG_LLM_BASE_URL=${KG_LLM_BASE_URL:-} - KG_LLM_API_KEY=${KG_LLM_API_KEY:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} # Prometheus /metrics — a separate listener, off by default (docs/operations/observability.md). # METRICS_TOKEN is required once METRICS_ENABLED=true; the listener refuses to start without it. - METRICS_ENABLED=${METRICS_ENABLED:-false} - METRICS_PORT=${METRICS_PORT:-9464} - METRICS_HOST=${METRICS_HOST:-0.0.0.0} - METRICS_TOKEN=${METRICS_TOKEN:-} - METRICS_TOOL_LABELS=${METRICS_TOOL_LABELS:-false} depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4000/health"] interval: 10s timeout: 5s retries: 5 start_period: 30s restart: unless-stopped # PostgreSQL Database postgres: image: postgres:17-alpine container_name: ${COMPOSE_PROJECT_NAME:-amcp}-postgres volumes: - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_USER=amcp - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-amcp} - POSTGRES_DB=anythingmcp healthcheck: test: ["CMD-SHELL", "pg_isready -U amcp -d anythingmcp"] interval: 5s timeout: 3s retries: 5 restart: unless-stopped # MOTIS routing engine for the Deutsche Bahn connector (optional). # Activated by COMPOSE_PROFILES=motis in .env, together with # MOTIS_INTERNAL_URL=http://motis:8080 and SSRF_ALLOWED_HOSTS=motis. Pulls # the open gtfs.de train timetable on first boot and refreshes it weekly; # about 12 MB of GTFS, a one-second import, ~100 MB of RAM at rest. # Details and feed licences: deploy/motis/README.md motis: build: context: ./deploy/motis image: anythingmcp-motis:2.11.3-1 profiles: ["motis"] container_name: ${COMPOSE_PROJECT_NAME:-amcp}-motis volumes: - motis_data:/data healthcheck: # 127.0.0.1, not localhost: MOTIS binds IPv4 only and localhost resolves # to ::1 first inside the container, so the probe was refused every time. # /metrics, not /: MOTIS answers 404 on the root, which --spider treats as # a failure. Between the two, the container reported unhealthy for its # whole life while serving traffic perfectly. test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/metrics"] interval: 30s timeout: 5s retries: 3 start_period: 180s restart: unless-stopped # Redis Cache (optional — uncomment to enable response caching and rate limiting) # redis: # image: redis:7-alpine # container_name: ${COMPOSE_PROJECT_NAME:-amcp}-redis # volumes: # - redis_data:/data # healthcheck: # test: ["CMD", "redis-cli", "ping"] # interval: 5s # timeout: 3s # retries: 5 # restart: unless-stopped volumes: postgres_data: motis_data: # Imported timetable for the optional MOTIS service caddy_data: # Let's Encrypt certificates (used when Caddy proxy is enabled) caddy_config: # Caddy configuration cache # redis_data: # Uncomment if Redis is enabled