x-default-logging: &default-logging driver: json-file options: max-size: "10m" max-file: "5" services: nginx: image: nginx:alpine volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: app: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/health || exit 1"] interval: 30s timeout: 10s retries: 3 restart: unless-stopped logging: *default-logging networks: - edge - backend app: image: remind-app:latest build: context: . dockerfile: Dockerfile expose: - "5000" env_file: - path: .env required: false environment: - FLASK_ENV=production - FLASK_DEBUG=0 - SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY} - SECRET_ROTATION_EPOCH=${SECRET_ROTATION_EPOCH:-0} - AI_PROVIDER_API_KEY=${AI_PROVIDER_API_KEY:?Set AI_PROVIDER_API_KEY} - AI_PROVIDER_MODEL_NAME=${AI_PROVIDER_MODEL_NAME:?Set AI_PROVIDER_MODEL_NAME} - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-} - TELEGRAM_CLIENT_ID=${TELEGRAM_CLIENT_ID:-} - TURNSTILE_SITE_KEY=${TURNSTILE_SITE_KEY:-} - TURNSTILE_SECRET_KEY=${TURNSTILE_SECRET_KEY:-} - BACKEND_URL=${BACKEND_URL:?Set BACKEND_URL} - ALLOWED_HOSTS=${ALLOWED_HOSTS:-},${TUNNEL_HOST:-} - CORS_ORIGINS=${CORS_ORIGINS:-},${TUNNEL_ORIGIN:-} - DB_PASSWORD=${DB_PASSWORD:?Set DB_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD:?Set REDIS_PASSWORD} - DATABASE_URL=postgresql://remind:${DB_PASSWORD:?Set DB_PASSWORD}@db:5432/remind_db - REDIS_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/0 - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - EMAIL_SENDER=${EMAIL_SENDER:?Set EMAIL_SENDER} - EMAIL_PASSWORD=${EMAIL_PASSWORD:?Set EMAIL_PASSWORD} - GITHUB_APP_ID=${GITHUB_APP_ID:?Set GITHUB_APP_ID} - GITHUB_APP_CLIENT_ID=${GITHUB_APP_CLIENT_ID:?Set GITHUB_APP_CLIENT_ID} - GITHUB_APP_CLIENT_SECRET=${GITHUB_APP_CLIENT_SECRET:?Set GITHUB_APP_CLIENT_SECRET} - GITHUB_APP_SLUG=${GITHUB_APP_SLUG:?Set GITHUB_APP_SLUG} - GITHUB_PUBLIC_BASE_URL=${GITHUB_PUBLIC_BASE_URL:?Set GITHUB_PUBLIC_BASE_URL} - GITHUB_OAUTH_ENCRYPTION_KEY=${GITHUB_OAUTH_ENCRYPTION_KEY:-} - LOCALHOST_MODE=False - CORS_ALLOW_LOCAL_ORIGINS=False - ALLOW_CROSS_SUBDOMAIN_SESSION_COOKIE=False - PUBLIC_METRICS_ENABLED=False - PUBLIC_OPENAPI_ENABLED=False - ALLOW_GUEST_CHATS_SAVE=False - ENABLE_STRICT_HTTPS=False - VALIDATE_USER_AGENT=True - PYTHON_RUNNER_ENABLED=True - PYTHON_RUNNER_QUEUE=/var/lib/remind-python-runner volumes: - ./database:/app/database - ./logs:/app/logs - python_runner_queue:/var/lib/remind-python-runner depends_on: redis: condition: service_healthy db: condition: service_healthy init-storage: condition: service_completed_successfully init-python-runner-storage: condition: service_completed_successfully migrate: condition: service_completed_successfully restart: unless-stopped logging: *default-logging healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5000/health"] interval: 30s timeout: 10s retries: 3 cap_drop: - ALL security_opt: - no-new-privileges:true deploy: resources: limits: cpus: '0.80' memory: 2G reservations: cpus: '0.25' memory: 512M networks: - backend - edge telegram-bot: image: remind-app:latest command: python telegram_bot.py profiles: ["telegram"] env_file: - path: .env required: false environment: - FLASK_ENV=production - FLASK_DEBUG=0 - SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY} - SECRET_ROTATION_EPOCH=${SECRET_ROTATION_EPOCH:-0} - AI_PROVIDER_API_KEY=${AI_PROVIDER_API_KEY:?Set AI_PROVIDER_API_KEY} - AI_PROVIDER_MODEL_NAME=${AI_PROVIDER_MODEL_NAME:?Set AI_PROVIDER_MODEL_NAME} - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} - TELEGRAM_BOT_USERNAME=${TELEGRAM_BOT_USERNAME:-} - TELEGRAM_BOT_API_BASE=${TELEGRAM_BOT_API_BASE:-https://api.telegram.org} - TELEGRAM_DRAFT_INTERVAL_SECONDS=${TELEGRAM_DRAFT_INTERVAL_SECONDS:-0.55} - TELEGRAM_MAX_CONTEXT_MESSAGES=${TELEGRAM_MAX_CONTEXT_MESSAGES:-4} - TELEGRAM_MAX_CONTEXT_MESSAGES_INLINE=${TELEGRAM_MAX_CONTEXT_MESSAGES_INLINE:-4} - TELEGRAM_THINKING_LEVEL=${TELEGRAM_THINKING_LEVEL:-low} - TELEGRAM_THINKING_LEVEL_MESSAGE=${TELEGRAM_THINKING_LEVEL_MESSAGE:-medium} - TELEGRAM_THINKING_LEVEL_INLINE=${TELEGRAM_THINKING_LEVEL_INLINE:-} - BACKEND_URL=${BACKEND_URL:?Set BACKEND_URL} - DB_PASSWORD=${DB_PASSWORD:?Set DB_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD:?Set REDIS_PASSWORD} - DATABASE_URL=postgresql://remind:${DB_PASSWORD:?Set DB_PASSWORD}@db:5432/remind_db - REDIS_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/0 - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - ENABLE_STRICT_HTTPS=False - ALLOW_CROSS_SUBDOMAIN_SESSION_COOKIE=False - PUBLIC_METRICS_ENABLED=False - PUBLIC_OPENAPI_ENABLED=False volumes: - ./database:/app/database - ./logs:/app/logs depends_on: redis: condition: service_healthy db: condition: service_healthy init-storage: condition: service_completed_successfully migrate: condition: service_completed_successfully healthcheck: disable: true restart: unless-stopped logging: *default-logging cap_drop: - ALL security_opt: - no-new-privileges:true deploy: resources: limits: cpus: '0.80' memory: 2G networks: - backend - edge cloudflared: image: cloudflare/cloudflared:latest command: tunnel --no-autoupdate --url http://nginx:80 profiles: ["tunnel"] depends_on: nginx: condition: service_healthy restart: unless-stopped logging: *default-logging cap_drop: - ALL security_opt: - no-new-privileges:true networks: - edge worker: image: remind-app:latest command: celery -A celery_worker.celery worker --loglevel=info --concurrency=2 env_file: - path: .env required: false environment: - FLASK_ENV=production - FLASK_DEBUG=0 - SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY} - SECRET_ROTATION_EPOCH=${SECRET_ROTATION_EPOCH:-0} - AI_PROVIDER_API_KEY=${AI_PROVIDER_API_KEY:?Set AI_PROVIDER_API_KEY} - AI_PROVIDER_MODEL_NAME=${AI_PROVIDER_MODEL_NAME:?Set AI_PROVIDER_MODEL_NAME} - DB_PASSWORD=${DB_PASSWORD:?Set DB_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD:?Set REDIS_PASSWORD} - DATABASE_URL=postgresql://remind:${DB_PASSWORD:?Set DB_PASSWORD}@db:5432/remind_db - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - REDIS_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/0 - ENABLE_STRICT_HTTPS=False - ALLOW_CROSS_SUBDOMAIN_SESSION_COOKIE=False - PUBLIC_METRICS_ENABLED=False - PUBLIC_OPENAPI_ENABLED=False - PYTHON_RUNNER_ENABLED=True - PYTHON_RUNNER_QUEUE=/var/lib/remind-python-runner volumes: - ./database:/app/database - ./logs:/app/logs - python_runner_queue:/var/lib/remind-python-runner depends_on: redis: condition: service_healthy db: condition: service_healthy init-storage: condition: service_completed_successfully init-python-runner-storage: condition: service_completed_successfully migrate: condition: service_completed_successfully healthcheck: disable: true restart: unless-stopped logging: *default-logging cap_drop: - ALL security_opt: - no-new-privileges:true deploy: resources: limits: cpus: '0.80' memory: 2G networks: - backend - edge init-storage: image: remind-app:latest user: "0:0" command: - sh - -c - chown -R 1001:1001 /app/database /app/logs && chmod -R u+rwX,go-rwx /app/database /app/logs volumes: - ./database:/app/database - ./logs:/app/logs restart: "no" cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - FOWNER security_opt: - no-new-privileges:true logging: *default-logging init-python-runner-storage: image: remind-python-runner:latest build: context: . dockerfile: Dockerfile.python-runner user: "0:0" command: - sh - -c - chown 0:0 /jobs && chmod 700 /jobs && mkdir -p /jobs/requests /jobs/responses && chown -R 1001:0 /jobs && chmod 2770 /jobs /jobs/requests /jobs/responses volumes: - python_runner_queue:/jobs network_mode: none restart: "no" cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - FOWNER security_opt: - no-new-privileges:true logging: *default-logging python-runner: image: remind-python-runner:latest build: context: . dockerfile: Dockerfile.python-runner environment: - PYTHON_RUNNER_QUEUE=/jobs volumes: - python_runner_queue:/jobs depends_on: init-python-runner-storage: condition: service_completed_successfully network_mode: none read_only: true tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=640m,mode=0711 - /dev/shm:rw,noexec,nosuid,nodev,size=1m,mode=000 user: "0:0" cap_drop: - ALL cap_add: - SETUID - SETGID - KILL - CHOWN - DAC_OVERRIDE security_opt: - no-new-privileges:true pids_limit: 64 mem_limit: 768m cpus: 0.75 ulimits: nofile: soft: 128 hard: 128 nproc: soft: 64 hard: 64 restart: unless-stopped stop_grace_period: 5s logging: *default-logging migrate: image: remind-app:latest command: python -m scripts.prepare_database env_file: - path: .env required: false environment: - FLASK_ENV=production - FLASK_DEBUG=0 - SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY} - AI_PROVIDER_API_KEY=${AI_PROVIDER_API_KEY:?Set AI_PROVIDER_API_KEY} - AI_PROVIDER_MODEL_NAME=${AI_PROVIDER_MODEL_NAME:?Set AI_PROVIDER_MODEL_NAME} - BACKEND_URL=${BACKEND_URL:?Set BACKEND_URL} - DB_PASSWORD=${DB_PASSWORD:?Set DB_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD:?Set REDIS_PASSWORD} - DATABASE_URL=postgresql://remind:${DB_PASSWORD:?Set DB_PASSWORD}@db:5432/remind_db - REDIS_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/0 - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:?Set REDIS_PASSWORD}@redis:6379/1 - EMAIL_SENDER=${EMAIL_SENDER:?Set EMAIL_SENDER} - EMAIL_PASSWORD=${EMAIL_PASSWORD:?Set EMAIL_PASSWORD} - LOCALHOST_MODE=False - CORS_ALLOW_LOCAL_ORIGINS=False - ALLOW_CROSS_SUBDOMAIN_SESSION_COOKIE=False - ENABLE_STRICT_HTTPS=False volumes: - ./database:/app/database - ./logs:/app/logs depends_on: db: condition: service_healthy redis: condition: service_healthy init-storage: condition: service_completed_successfully restart: "no" cap_drop: - ALL security_opt: - no-new-privileges:true networks: - backend logging: *default-logging db: image: postgres:15-alpine environment: POSTGRES_USER: remind POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD} POSTGRES_DB: remind_db volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U remind -d remind_db"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped logging: *default-logging networks: - backend redis: image: redis:7-alpine environment: - REDISCLI_AUTH=${REDIS_PASSWORD:?Set REDIS_PASSWORD} volumes: - redis_data:/data restart: unless-stopped command: - redis-server - --appendonly - "yes" - --maxmemory - 256mb - --maxmemory-policy - allkeys-lru - --protected-mode - "yes" - --bind - 0.0.0.0 - --requirepass - ${REDIS_PASSWORD:?Set REDIS_PASSWORD} - --rename-command - CONFIG - "" - --rename-command - FLUSHALL - "" - --rename-command - FLUSHDB - "" - --rename-command - SHUTDOWN - "" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 10 start_period: 15s logging: *default-logging networks: - backend volumes: postgres_data: redis_data: python_runner_queue: networks: edge: backend: internal: true