Files
stiftung-management-system/compose.yml
SysAdmin Agent 3cdf49419e
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled
Fix GrampsWeb subpath: patch API/lang/font paths in JS at startup (STI-90)
GrampsWeb's frontend JS hardcodes absolute paths like "/api/...",
"/lang/...", "/fonts/..." which bypass <base href>. These now get
rewritten to "/ahnenforschung/api/..." etc. at container startup,
matching both double-quoted and template-literal (backtick) patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 09:57:29 +00:00

254 lines
8.5 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Production Docker Compose Configuration
# This file is used for production deployment via GitHub Actions
# For local development, use: docker-compose -f compose.dev.yml up
#
# IMPORTANT: This configuration requires ALL environment variables to be
# provided via the production server's .env file. No fallback values are
# included for security reasons.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
web:
build:
context: ./app
args:
APP_VERSION: ${APP_VERSION:-unknown}
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
- DJANGO_DEBUG=${DJANGO_DEBUG}
- DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS}
- LANGUAGE_CODE=${LANGUAGE_CODE}
- TIME_ZONE=${TIME_ZONE}
- REDIS_URL=${REDIS_URL}
- SESSION_COOKIE_NAME=${SESSION_COOKIE_NAME}
- CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME}
- GRAMPS_URL=${GRAMPS_URL}
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
- IMAP_HOST=${IMAP_HOST}
- IMAP_PORT=${IMAP_PORT}
- IMAP_USER=${IMAP_USER}
- IMAP_PASSWORD=${IMAP_PASSWORD}
- IMAP_FOLDER=${IMAP_FOLDER}
- IMAP_USE_SSL=${IMAP_USE_SSL}
ports:
- "8081:8000"
volumes:
- ./app:/app
command: ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]
worker:
build:
context: ./app
args:
APP_VERSION: ${APP_VERSION:-unknown}
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
- DJANGO_DEBUG=${DJANGO_DEBUG}
- REDIS_URL=${REDIS_URL}
- GRAMPS_URL=${GRAMPS_URL}
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
- IMAP_HOST=${IMAP_HOST}
- IMAP_PORT=${IMAP_PORT}
- IMAP_USER=${IMAP_USER}
- IMAP_PASSWORD=${IMAP_PASSWORD}
- IMAP_FOLDER=${IMAP_FOLDER}
- IMAP_USE_SSL=${IMAP_USE_SSL}
depends_on:
- redis
- db
command: ["celery", "-A", "core", "worker", "-l", "info"]
beat:
build:
context: ./app
args:
APP_VERSION: ${APP_VERSION:-unknown}
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
- DJANGO_DEBUG=${DJANGO_DEBUG}
- REDIS_URL=${REDIS_URL}
- GRAMPS_URL=${GRAMPS_URL}
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
- IMAP_HOST=${IMAP_HOST}
- IMAP_PORT=${IMAP_PORT}
- IMAP_USER=${IMAP_USER}
- IMAP_PASSWORD=${IMAP_PASSWORD}
- IMAP_FOLDER=${IMAP_FOLDER}
- IMAP_USE_SSL=${IMAP_USE_SSL}
depends_on:
- redis
- db
command: ["celery", "-A", "core", "beat", "-l", "info"]
mcp:
build:
context: ./app
args:
APP_VERSION: ${APP_VERSION:-unknown}
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
- DJANGO_DEBUG=0
- DJANGO_ALLOWED_HOSTS=localhost
- LANGUAGE_CODE=${LANGUAGE_CODE}
- TIME_ZONE=${TIME_ZONE}
- MCP_TOKEN_READONLY=${MCP_TOKEN_READONLY}
- MCP_TOKEN_EDITOR=${MCP_TOKEN_EDITOR}
- MCP_TOKEN_ADMIN=${MCP_TOKEN_ADMIN}
# Kein Port-Mapping nur internes Netz
# Start via: docker compose run --rm -e MCP_AUTH_TOKEN=<token> mcp
stdin_open: true
command: ["python", "-m", "mcp_server"]
ollama:
image: ollama/ollama:latest
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
# Django-App: http://ollama:11434
environment:
- OLLAMA_MAX_LOADED_MODELS=1
- OLLAMA_NUM_PARALLEL=1
- OLLAMA_DEFAULT_MODEL=${OLLAMA_DEFAULT_MODEL:-qwen2.5:3b}
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:11434/api/tags || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
# Beim ersten Start: Ollama starten, dann Modell laden (falls nicht vorhanden)
entrypoint: >
sh -c "
ollama serve &
OLLAMA_PID=$$!
echo '[ollama] Warte auf API...'
RETRIES=0
until curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; do
RETRIES=$$((RETRIES + 1))
[ $$RETRIES -ge 60 ] && echo '[ollama] FEHLER: API nicht bereit.' && exit 1
sleep 1
done
MODEL=$${OLLAMA_DEFAULT_MODEL:-qwen2.5:3b}
if ollama list | grep -q \"$$MODEL\"; then
echo \"[ollama] Modell '$$MODEL' bereits vorhanden.\"
else
echo \"[ollama] Lade Modell '$$MODEL'...\"
ollama pull \"$$MODEL\"
fi
wait $$OLLAMA_PID
"
grampsweb:
image: ghcr.io/gramps-project/grampsweb:latest
ports:
- "8090:5000"
environment:
- GRAMPSWEB_SECRET_KEY=${GRAMPSWEB_SECRET_KEY:-dev-grampsweb-secret-key-not-for-production}
- GRAMPSWEB_ADMIN_EMAIL=${GRAMPSWEB_ADMIN_EMAIL:-admin@localhost}
- GRAMPSWEB_ADMIN_PASSWORD=${GRAMPSWEB_ADMIN_PASSWORD:-gramps_dev_password}
- GRAMPSWEB_TREE=${GRAMPSWEB_TREE:-Stiftung}
- GRAMPSWEB_BASE_URL=${GRAMPSWEB_BASE_URL:-http://localhost:8090}
- GRAMPSWEB_CELERY_CONFIG__broker_url=redis://redis:6379/0
- GRAMPSWEB_CELERY_CONFIG__result_backend=redis://redis:6379/0
- GRAMPSWEB_RATELIMIT_STORAGE_URI=redis://redis:6379/1
- GRAMPSWEB_NEW_DB_BACKEND=sqlite
- GRAMPSWEB_SUBPATH=${GRAMPSWEB_SUBPATH:-/ahnenforschung}
command:
- sh
- -c
- |
if [ -n "$$GRAMPSWEB_SUBPATH" ] && [ "$$GRAMPSWEB_SUBPATH" != "/" ]; then
SUBPATH="$$GRAMPSWEB_SUBPATH"
case "$$SUBPATH" in */) ;; *) SUBPATH="$${SUBPATH}/" ;; esac
echo "[grampsweb] Patching static files for subpath $$SUBPATH ..."
find / -name index.html -path "*/gramps*" -o -name index.html -path "*/static/*" 2>/dev/null | while read f; do
if grep -q '<base href="/">' "$$f" 2>/dev/null; then
sed -i "s|<base href=\"/\">|<base href=\"$$SUBPATH\">|g" "$$f"
echo "[grampsweb] patched base href: $$f"
fi
done
for f in /app/static/*.js; do
if [ -f "$$f" ] && grep -q '/api/' "$$f" 2>/dev/null; then
sed -i "s|\"/api/|\"$${SUBPATH}api/|g" "$$f"
sed -i 's|`/api/|`'"$${SUBPATH}"'api/|g' "$$f"
sed -i "s|\"/lang/|\"$${SUBPATH}lang/|g" "$$f"
sed -i 's|`/lang/|`'"$${SUBPATH}"'lang/|g' "$$f"
sed -i "s|\"/fonts/|\"$${SUBPATH}fonts/|g" "$$f"
sed -i 's|`/fonts/|`'"$${SUBPATH}"'fonts/|g' "$$f"
sed -i "s|\"/assets/|\"$${SUBPATH}assets/|g" "$$f"
sed -i 's|`/assets/|`'"$${SUBPATH}"'assets/|g' "$$f"
echo "[grampsweb] patched JS paths: $$f"
fi
done
for f in /app/static/*.css; do
if [ -f "$$f" ] && grep -q '"/fonts/' "$$f" 2>/dev/null; then
sed -i "s|\"/fonts/|\"$${SUBPATH}fonts/|g" "$$f"
echo "[grampsweb] patched CSS paths: $$f"
fi
done
echo "[grampsweb] Done."
fi
exec gunicorn -w $${GUNICORN_NUM_WORKERS:-8} -b 0.0.0.0:5000 \
gramps_webapi.wsgi:app --timeout $${GUNICORN_TIMEOUT:-120} \
--limit-request-line 8190
volumes:
- gramps_data:/app/data
depends_on:
- db
- redis
volumes:
dbdata:
gramps_data:
ollama_data: