services: qdrant: image: qdrant/qdrant:latest container_name: modulos-qdrant ports: - "6333:6333" - "6334:6334" volumes: - ./qdrant_storage:/qdrant/storage restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "if command -v curl >/dev/null 2>&1; then curl -fsS http://localhost:6333/readyz >/dev/null; \ elif command -v wget >/dev/null 2>&1; then wget -qO- http://localhost:6333/readyz >/dev/null; \ else bash -lc \"exec 3<>/dev/tcp/127.0.0.1/6333; printf 'GET /readyz HTTP/1.0\\r\\n\\r\\n' >&3; head -n1 <&3 | grep -q '200'\"; fi" ] interval: 20s timeout: 5s retries: 5 backend: build: ./backend container_name: modulos-backend ports: - "2281:8000" command: > bash -lc ' set -euo pipefail; exec uvicorn app:app --host 0.0.0.0 --port 8000 ' user: "10001:10001" # run as appuser at runtime environment: # point to your Windows Ollama box - OLLAMA_URL=http://192.168.8.73:11434 # talk to qdrant inside this compose - QDRANT_URL=http://qdrant:6333 # defaults for collection/models (override if you like) - QDRANT_COLLECTION=planning_docs - EMBED_MODEL=nomic-embed-text - CHAT_MODEL=llama3.1:8b-instruct-q5_K_M #- CHAT_MODEL=llama3.1:8b # optional CORS, comma-separated list - CORS_ORIGINS=http://localhost:3000,http://192.168.8.69:2380,https://llm.modulos.com.au,https://api.modulos.com.au,https://tasplanning.report # Optional demo gate: - DEMO_REQUIRE_TOKEN=0 - DEMO_TOKEN=DHv2xUx7vWzGg3O8UihDbUVQ7Svrub1FJXHv2Y3jyOTRrOTO19cLytm3b8Y8A6eC - TPR_DB=/data/telemetry.db - TPR_IP_SECRET=${TPR_IP_SECRET:-mmOwQgqljUs1CPiKW3O9vvL4XGalAHojOEmB7SJLBxXBPXHbBoDCMyS8fPc62aDk} depends_on: qdrant: condition: service_healthy volumes: - ./backend:/app - /home/modulos_llm/telemetry_data:/data restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/readyz"] interval: 20s timeout: 3s retries: 5 web: build: context: . dockerfile: ./web/Dockerfile container_name: modulos-web ports: - "2380:80" volumes: - ./public:/var/www/html - cache:/var/www/html/cache - /home/modulos_llm/telemetry_data:/data environment: - TPR_DB=/data/telemetry.db - DEBIAN_FRONTEND=noninteractive - GOOGLE_APPLICATION_CREDENTIALS=/var/www/secure/service-account.json - GDOC_PARENT_ID=1Zkv6eThu-3szvRpug3fXiAuTDWLsWxxl - GDOC_SHARE_EMAIL=modulos.aust@gmail.com - GMAPS_API_KEY=${GMAPS_API_KEY} - SMTP_HOST=mail.modulos.com.au - SMTP_PORT=465 - SMTP_USER=no-reply@modulos.com.au - SMTP_PASS=${SMTP_PASS} - SMTP_FROM=no-reply@modulos.com.au - SMTP_FROM_NAME=Tas Planning Assistant - NOTIFY_EMAIL=ben@modulos.com.au - APP_API_BASE=https://api.modulos.com.au/ask - CORS_ORIGINS=${CORS_ORIGINS:-https://tasplanning.report,https://modulosdesign.com.au,https://llm.modulos.com.au} - APP_ENV=${APP_ENV:-production} depends_on: - backend restart: unless-stopped command: > bash -lc ' set -euo pipefail; apt-get update -y; apt-get install -y --no-install-recommends git unzip libzip-dev netcat-openbsd; rm -rf /var/lib/apt/lists/*; a2enmod rewrite headers >/dev/null || true; # Only build zip if not loaded php -m | grep -qi "^zip$" || docker-php-ext-install zip; # Write clean confs (printf avoids YAML here-doc pitfalls) printf "%s\n" "" \ " AllowOverride All" \ " Require all granted" \ "" \ > /etc/apache2/conf-enabled/allowoverride.conf printf "%s\n" "SetEnvIfNoCase X-Forwarded-Proto ^https$ HTTPS=on" \ > /etc/apache2/conf-enabled/proxy-https.conf # Extra: set HTTPS via mod_rewrite for libs reading env later printf "%s\n" "RewriteEngine On" \ "RewriteCond %{HTTP:X-Forwarded-Proto} =https" \ "RewriteRule .* - [E=HTTPS:on]" \ > /etc/apache2/conf-enabled/rewrite-https.conf printf "ServerName localhost\n" > /etc/apache2/conf-enabled/servername.conf printf "PassEnv GMAPS_API_KEY\nPassEnv CORS_ORIGINS\nPassEnv APP_ENV\n" > /etc/apache2/conf-enabled/passenv.conf; chown -R www-data:www-data /var/www/html/cache || true; apachectl -t; exec apache2-foreground ' sqliteweb: image: coleifer/sqlite-web:latest container_name: modulos-sqliteweb depends_on: - backend user: "10001:10001" volumes: - /home/modulos_llm/telemetry_data:/data # same volume the backend uses # external: true environment: - SQLITE_DATABASE=telemetry.db # looks for /data/telemetry.db # optional: protect with a password # - SQLITE_WEB_PASSWORD=${SQLITE_WEB_PASSWORD:-} ports: - "8091:8080" restart: unless-stopped composer: build: context: . dockerfile: ./web/Dockerfile working_dir: /app volumes: - ./public:/app # external: true command: ["composer", "install", "--no-interaction", "--no-progress", "--prefer-dist"] restart: unless-stopped depends_on: - web volumes: cache: external: true name: modulos_llm_cache telemetry_data: {}