chore(deps): update docker.io/qdrant/qdrant docker tag to v1.18.1
389 lines
15 KiB
YAML
389 lines
15 KiB
YAML
services:
|
|
# Note: MariaDB is external service. You can find more information about the configuration here:
|
|
# https://hub.docker.com/_/mariadb
|
|
db:
|
|
# Note: Check the recommend version here: https://docs.nextcloud.com/server/latest/admin_manual/installation/system_requirements.html#server
|
|
image: docker.io/library/mariadb:11.8.6-noble@sha256:31c1aa20ba2d78c4cbc45b2675e40ffd6a3cd59897a9767ce88fc284309f55ab
|
|
restart: always
|
|
command: --transaction-isolation=READ-COMMITTED
|
|
volumes:
|
|
- db:/var/lib/mysql
|
|
ports:
|
|
- 127.0.0.1:3306:3306
|
|
environment:
|
|
- MYSQL_ROOT_PASSWORD=password
|
|
- MYSQL_PASSWORD=password
|
|
- MYSQL_DATABASE=nextcloud
|
|
- MYSQL_USER=nextcloud
|
|
|
|
# Note: Redis is an external service. You can find more information about the configuration here:
|
|
# https://hub.docker.com/_/redis
|
|
redis:
|
|
image: docker.io/library/redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
|
restart: always
|
|
|
|
app:
|
|
image: ${NEXTCLOUD_IMAGE:-docker.io/library/nextcloud:32.0.10@sha256:611669115cccef3f96aa8eb47bd07c4d57452d894ebcfc1d81f5e8ce368e7d2d}
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:8080:80
|
|
depends_on:
|
|
- redis
|
|
- db
|
|
volumes:
|
|
- nextcloud:/var/www/html
|
|
- ./app-hooks:/docker-entrypoint-hooks.d:ro
|
|
# Mount OIDC development directory outside /var/www/html to avoid rsync conflicts
|
|
# The post-installation hook will register /opt/apps as an additional app directory
|
|
#- ./third_party:/opt/apps:ro
|
|
#- ./third_party/astrolabe:/opt/apps/astrolabe:ro
|
|
#- ./third_party/oidc:/opt/apps/oidc:ro
|
|
environment:
|
|
- NEXTCLOUD_TRUSTED_DOMAINS=app
|
|
- NEXTCLOUD_ADMIN_USER=admin
|
|
- NEXTCLOUD_ADMIN_PASSWORD=admin
|
|
- MYSQL_PASSWORD=password
|
|
- MYSQL_DATABASE=nextcloud
|
|
- MYSQL_USER=nextcloud
|
|
- MYSQL_HOST=db
|
|
- REDIS_HOST=redis
|
|
# Set MCP_SERVER_URL to enable Astrolabe OAuth client auto-creation
|
|
# (before-starting hook creates OIDC client + stores credentials in config.php)
|
|
# Example: MCP_SERVER_URL=http://mcp-login-flow:8004 docker compose --profile login-flow up -d
|
|
- MCP_SERVER_URL=${MCP_SERVER_URL:-}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -Ss http://localhost/status.php | grep '\"installed\":true' || exit 1"]
|
|
interval: 10s
|
|
timeout: 30s
|
|
retries: 30
|
|
|
|
recipes:
|
|
image: docker.io/library/nginx:1.29.7-alpine@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1
|
|
restart: always
|
|
volumes:
|
|
- ./tests/fixtures/test_recipe.html:/usr/share/nginx/html/test_recipe.html:ro
|
|
- ./tests/fixtures/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
|
|
unstructured:
|
|
image: downloads.unstructured.io/unstructured-io/unstructured-api:0.1.7@sha256:ee89a87a545b6a7d56609607aced71b5b1acf0211400bf2d11a8f70f209a93bd
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:8005:8000
|
|
# Unstructured API runs on port 8000 internally
|
|
# We expose it on 8005 externally to avoid conflict
|
|
profiles:
|
|
- unstructured
|
|
|
|
mcp:
|
|
build: .
|
|
restart: always
|
|
command: ["--transport", "streamable-http"]
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8000:8000
|
|
- 127.0.0.1:9090:9090
|
|
volumes:
|
|
- mcp-data:/app/data
|
|
- ./settings.toml.example:/app/settings.toml:ro
|
|
environment:
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_USERNAME=admin
|
|
- NEXTCLOUD_PASSWORD=admin
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
|
|
# Pin the token DB inside the mcp-data volume so the runtime default
|
|
# (ephemeral tempfile) doesn't silently apply inside containers.
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
# Semantic search configuration (ADR-007, ADR-021)
|
|
- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=5
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=2
|
|
|
|
#- LOG_FORMAT=json
|
|
|
|
# Qdrant configuration (three modes):
|
|
# 1. Network mode: Set QDRANT_URL=http://qdrant:6333 (requires qdrant service)
|
|
# 2. In-memory mode: Set QDRANT_LOCATION=:memory: (default if nothing set)
|
|
# 3. Persistent local: Set QDRANT_LOCATION=/app/data/qdrant (stored in mcp-data volume)
|
|
- QDRANT_LOCATION=":memory:"
|
|
#- QDRANT_URL=http://qdrant:6333 # Uncomment for network mode
|
|
#- QDRANT_API_KEY=${QDRANT_API_KEY:-my_secret_api_key} # Only for network mode
|
|
|
|
# Observability
|
|
#- OTEL_SERVICE_NAME=nextcloud-mcp-docker-compose
|
|
#- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
|
|
|
|
# Collection naming: Auto-generated as {deployment-id}-{model-name}
|
|
# - Deployment ID: OTEL_SERVICE_NAME (if set) or hostname (fallback)
|
|
# - Model name: OLLAMA_EMBEDDING_MODEL
|
|
# - Example: "nextcloud-mcp-server-nomic-embed-text"
|
|
# - Changing models creates new collection (requires re-embedding)
|
|
# - Set QDRANT_COLLECTION to override auto-generation:
|
|
#- QDRANT_COLLECTION=nextcloud_content
|
|
|
|
# Ollama configuration (optional - uses SimpleEmbeddingProvider if not set)
|
|
# - OLLAMA_BASE_URL=http://ollama:11434
|
|
# - OLLAMA_EMBEDDING_MODEL=nomic-embed-text # Changing this creates new collection
|
|
# - OLLAMA_VERIFY_SSL=false
|
|
|
|
# Document chunking configuration (for vector embeddings)
|
|
# Tune these based on your embedding model and content type
|
|
# - DOCUMENT_CHUNK_SIZE=512 # Words per chunk (default: 512)
|
|
# - DOCUMENT_CHUNK_OVERLAP=50 # Overlapping words (default: 50, recommended: 10-20% of chunk size)
|
|
profiles:
|
|
- single-user
|
|
|
|
mcp-multi-user-basic:
|
|
build: .
|
|
restart: always
|
|
command: ["--transport", "streamable-http"]
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8003:8000
|
|
environment:
|
|
# Multi-user BasicAuth pass-through mode (ADR-020, ADR-022)
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8003
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
- MCP_DEPLOYMENT_MODE=multi_user_basic
|
|
- ENABLE_BACKGROUND_OPERATIONS=true
|
|
|
|
# Token storage (required for middleware initialization).
|
|
# Source the key from .env — see env.sample. To generate a fresh key:
|
|
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
- TOKEN_ENCRYPTION_KEY=${TOKEN_ENCRYPTION_KEY:?TOKEN_ENCRYPTION_KEY must be set in .env (see env.sample)}
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=60
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
|
|
|
# OAuth credentials for background sync (optional - uses DCR if not provided)
|
|
# Uncomment to avoid DCR:
|
|
# - NEXTCLOUD_OIDC_CLIENT_ID=your_client_id
|
|
# - NEXTCLOUD_OIDC_CLIENT_SECRET=your_client_secret
|
|
|
|
# Management API allowlist (ADR-018): only Astrolabe-issued tokens may
|
|
# call /api/management/*. Default test client_id used by the
|
|
# configure_astrolabe_for_mcp_server fixture.
|
|
- ALLOWED_MGMT_CLIENT=nextcloudMcpServerUIPublicClient
|
|
|
|
# NO admin credentials - credentials come from client Authorization header
|
|
volumes:
|
|
- multi-user-basic-data:/app/data
|
|
- ./settings.toml.example:/app/settings.toml:ro
|
|
profiles:
|
|
- multi-user-basic
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:26.6.2@sha256:f9ba7b2af90db8dc749a57ca9aedca51e840cb9224441ab546a968da941da900
|
|
command:
|
|
- "start-dev"
|
|
- "--import-realm"
|
|
- "--hostname=http://localhost:8888"
|
|
- "--hostname-strict=false"
|
|
- "--hostname-backchannel-dynamic=true"
|
|
- "--features=preview" # Enable Legacy V1 token exchange (supports both Standard V2 and Legacy V1)
|
|
ports:
|
|
- 127.0.0.1:8888:8080
|
|
environment:
|
|
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
|
|
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
|
|
volumes:
|
|
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm.json:ro
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /realms/nextcloud-mcp HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'HTTP/1.1 200'"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 30
|
|
profiles:
|
|
- keycloak
|
|
|
|
mcp-keycloak:
|
|
build: .
|
|
command: ["--transport", "streamable-http", "--oauth", "--port", "8002"]
|
|
restart: always
|
|
depends_on:
|
|
keycloak:
|
|
condition: service_healthy
|
|
app:
|
|
condition: service_started
|
|
ports:
|
|
- 127.0.0.1:8002:8002
|
|
environment:
|
|
# Generic OIDC configuration (external IdP mode - Keycloak)
|
|
# Provider auto-detected from OIDC_DISCOVERY_URL issuer
|
|
# Using internal Docker hostname for discovery to get consistent issuer
|
|
- OIDC_DISCOVERY_URL=http://keycloak:8080/realms/nextcloud-mcp/.well-known/openid-configuration
|
|
- NEXTCLOUD_OIDC_CLIENT_ID=nextcloud-mcp-server
|
|
- NEXTCLOUD_OIDC_CLIENT_SECRET=mcp-secret-change-in-production
|
|
- OIDC_JWKS_URI=http://keycloak:8080/realms/nextcloud-mcp/protocol/openid-connect/certs
|
|
|
|
# Nextcloud API endpoint (for accessing APIs with validated token)
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8002
|
|
- NEXTCLOUD_RESOURCE_URI=nextcloud # ADR-005: Keycloak uses client IDs as audiences, not URLs
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8888/realms/nextcloud-mcp
|
|
|
|
# Refresh token storage (ADR-002 Tier 1 & 2). Source from .env.
|
|
- ENABLE_BACKGROUND_OPERATIONS=true
|
|
- TOKEN_ENCRYPTION_KEY=${TOKEN_ENCRYPTION_KEY:?TOKEN_ENCRYPTION_KEY must be set in .env (see env.sample)}
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
# ADR-005: Token exchange mode (RFC 8693)
|
|
# Exchange MCP tokens (aud: nextcloud-mcp-server) for Nextcloud tokens (aud: http://localhost:8080)
|
|
# Provides strict audience separation between MCP session and Nextcloud API access
|
|
- ENABLE_TOKEN_EXCHANGE=true
|
|
- TOKEN_EXCHANGE_CACHE_TTL=300 # Cache exchanged tokens for 5 minutes (default)
|
|
|
|
# Login Flow v2 (ADR-022) with external IdP — derived from the
|
|
# auto-detected LOGIN_FLOW deployment mode; no separate flag needed.
|
|
- ENABLE_DCR=true
|
|
|
|
# OAuth scopes (optional - uses defaults if not specified)
|
|
- NEXTCLOUD_OIDC_SCOPES=openid profile email offline_access notes.read notes.write calendar.read calendar.write contacts.read contacts.write cookbook.read cookbook.write deck.read deck.write tables.read tables.write files.read files.write sharing.read sharing.write todo.read todo.write
|
|
|
|
# NO admin credentials - using external IdP OAuth only!
|
|
volumes:
|
|
- keycloak-tokens:/app/data
|
|
- keycloak-oauth-storage:/app/.oauth
|
|
- ./settings.toml.example:/app/settings.toml:ro
|
|
profiles:
|
|
- keycloak
|
|
|
|
# Login Flow v2 mode (ADR-022)
|
|
# Test with: docker compose --profile login-flow up --build -d
|
|
mcp-login-flow:
|
|
build: .
|
|
restart: always
|
|
# --oauth enables the OAuth/OIDC identity layer that Login Flow v2 builds on
|
|
# (user identity via OAuth session, Nextcloud access via app passwords)
|
|
command: ["--transport", "streamable-http", "--oauth", "--port", "8004"]
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8004:8004
|
|
environment:
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8004
|
|
#- NEXTCLOUD_MCP_SERVER_URL=https://nextcloud-mcp-dev.tail148d5.ts.net
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
|
|
# Login Flow v2 (ADR-022) — the deployment mode is the single switch;
|
|
# the browser-based app-password layer is derived automatically.
|
|
- MCP_DEPLOYMENT_MODE=login_flow
|
|
|
|
# Token storage (required for app password + session persistence).
|
|
# Source the key from .env — see env.sample. To generate a fresh key:
|
|
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
- TOKEN_ENCRYPTION_KEY=${TOKEN_ENCRYPTION_KEY:?TOKEN_ENCRYPTION_KEY must be set in .env (see env.sample)}
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
# Semantic search
|
|
- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=60
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
|
|
|
# Management API allowlist (ADR-018) — matches mcp-multi-user-basic so
|
|
# the same configure_astrolabe_for_mcp_server fixture (which creates the
|
|
# static `nextcloudMcpServerUIPublicClient` OIDC client) works for tests
|
|
# against this profile too.
|
|
- ALLOWED_MGMT_CLIENT=astrolabeMcpClientOAuth00000000000
|
|
volumes:
|
|
- login-flow-data:/app/data
|
|
- login-flow-oauth-storage:/app/.oauth
|
|
- ./settings.toml.example:/app/settings.toml:ro
|
|
profiles:
|
|
- login-flow
|
|
|
|
# Tailscale Funnel for Claude AI connector testing
|
|
# Usage: docker compose --profile login-flow --profile claude-funnel up --build -d
|
|
# Requires: TS_AUTHKEY in .env file (see env.sample.claude-funnel)
|
|
tailscale-mcp:
|
|
image: docker.io/tailscale/tailscale:v1.98.4@sha256:25cde9ad76020b0e29229136d0c38b5962e9a0e1774ffac9b0df68e4a37d6cf0
|
|
hostname: nextcloud-mcp-dev
|
|
restart: always
|
|
environment:
|
|
- TS_AUTHKEY=${TS_AUTHKEY}
|
|
- TS_STATE_DIR=/var/lib/tailscale
|
|
- TS_SERVE_CONFIG=/config/serve-config.json
|
|
- TS_HOSTNAME=nextcloud-mcp-dev
|
|
- TS_EXTRA_ARGS=--advertise-tags=tag:container
|
|
volumes:
|
|
- tailscale-state:/var/lib/tailscale
|
|
- ./claude-funnel/serve-config.json:/config/serve-config.json:ro
|
|
cap_add:
|
|
- NET_ADMIN
|
|
- SYS_MODULE
|
|
profiles:
|
|
- claude-funnel
|
|
|
|
nginx-claude-filter:
|
|
image: docker.io/library/nginx:1.29.7-alpine@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1
|
|
restart: always
|
|
depends_on:
|
|
- mcp-login-flow
|
|
volumes:
|
|
- ./claude-funnel/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
profiles:
|
|
- claude-funnel
|
|
|
|
qdrant:
|
|
image: docker.io/qdrant/qdrant:v1.18.1@sha256:45f8e3ddc2570a4d029877e1b5ec1045c19b3852b4e22a55c7f43b05aea0ca89
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:6333:6333 # REST API
|
|
- 127.0.0.1:6334:6334 # gRPC (optional)
|
|
volumes:
|
|
- qdrant-data:/qdrant/storage
|
|
environment:
|
|
- QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY:-my_secret_api_key}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "test -f /qdrant/.qdrant-initialized"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
profiles:
|
|
- qdrant
|
|
|
|
# Centralized Postgres backend for token storage. Used by:
|
|
# 1. Integration tests gated on @pytest.mark.postgres — they read
|
|
# TEST_DATABASE_URL=postgresql+asyncpg://mcp:mcp@localhost:5433/mcp.
|
|
# 2. Manual smoke testing of the HA (multi-replica) deployment story.
|
|
# Bring up with: docker compose --profile postgres up -d postgres-test
|
|
# See ADR-026 for the pluggable-database-backend design.
|
|
postgres-test:
|
|
image: docker.io/library/postgres:16-alpine@sha256:16bc17c64a573ef34162af9298258d1aec548232985b33ed7b1eac33ba35c229
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: mcp
|
|
POSTGRES_PASSWORD: mcp
|
|
POSTGRES_DB: mcp
|
|
ports:
|
|
- 127.0.0.1:5433:5432
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U mcp -d mcp"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
profiles:
|
|
- postgres
|
|
|
|
volumes:
|
|
nextcloud:
|
|
db:
|
|
keycloak-tokens:
|
|
keycloak-oauth-storage:
|
|
login-flow-data:
|
|
login-flow-oauth-storage:
|
|
qdrant-data:
|
|
mcp-data:
|
|
multi-user-basic-data:
|
|
tailscale-state:
|