# documentation: https://docs.appflowy.io/docs/documentation/appflowy-cloud
# slogan: AppFlowy is the AI collaborative workspace where you achieve more without losing control of your data.
# tags: appflowy,notion,open-source,productivity,notes,ai,self-hosted
# logo: svgs/appflowy.svg
# category: productivity
# port: 80

services:
  nginx:
    image: nginx:1.29.2
    environment:
      - SERVICE_URL_ADMIN
    volumes:
      - type: bind
        source: ./nginx/nginx.conf
        target: /etc/nginx/nginx.conf
        read_only: true
        content: |
          # Please do not directly edit this file.

          error_log  /var/log/nginx/error.log notice;
          pid        /var/run/nginx.pid;


          events {
              worker_connections  1024;
          }


          http {
              resolver 127.0.0.11 valid=10s;
              map $http_upgrade $connection_upgrade {
                  default upgrade;
                  '' close;
              }

              server {
                  listen 80;
                  client_max_body_size 10M;
                  underscores_in_headers on;

                  # Backend services
                  set $appflowy_cloud_backend "http://appflowy_cloud:8000";
                  set $gotrue_backend "http://gotrue:9999";
                  set $admin_frontend_backend "http://admin_frontend:3000";
                  # GoTrue Authentication Service
                  location /gotrue/ {
                      proxy_pass $gotrue_backend;

                      rewrite ^/gotrue(/.*)$ $1 break;
                      
                      # Allow headers like redirect_to to be handed over to the gotrue
                      # for correct redirecting
                      proxy_set_header Host $http_host;
                      proxy_pass_request_headers on;
                  }

                  # WebSocket endpoint - handle both /ws and /ws/v2
                  location /ws {
                      proxy_pass $appflowy_cloud_backend;

                      proxy_http_version 1.1;
                      proxy_set_header Upgrade $http_upgrade;
                      proxy_set_header Connection "Upgrade";
                      proxy_set_header Host $host;
                      proxy_set_header X-Real-IP $remote_addr;
                      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                      proxy_set_header X-Forwarded-Proto $scheme;
                      proxy_read_timeout 86400s;
                  }

                  # API endpoints
                  location /api {
                      proxy_pass $appflowy_cloud_backend;
                      proxy_set_header X-Request-Id $request_id;
                      proxy_set_header Host $http_host;

                      # Special handling for publish endpoint
                      location ~* ^/api/workspace/([a-zA-Z0-9_-]+)/publish$ {
                          proxy_pass $appflowy_cloud_backend;
                          proxy_request_buffering off;
                          client_max_body_size 256M;
                      }

                      # Chat endpoint (streaming)
                      location /api/chat {
                          proxy_pass $appflowy_cloud_backend;

                          proxy_http_version 1.1;
                          proxy_set_header Connection "";
                          chunked_transfer_encoding on;
                          proxy_buffering off;
                          proxy_cache off;

                          proxy_read_timeout 600s;
                          proxy_connect_timeout 600s;
                          proxy_send_timeout 600s;
                      }

                      # Import endpoint
                      location /api/import {
                          proxy_pass $appflowy_cloud_backend;

                          proxy_set_header X-Request-Id $request_id;
                          proxy_set_header Host $http_host;

                          proxy_read_timeout 600s;
                          proxy_connect_timeout 600s;
                          proxy_send_timeout 600s;

                          proxy_request_buffering off;
                          proxy_buffering off;
                          proxy_cache off;
                          client_max_body_size 2G;
                      }
                  }

                  # Admin Frontend
                  # Optional Module, comment this section if you did not deploy admin_frontend in docker-compose.yml
                  location /console {
                      proxy_pass $admin_frontend_backend;

                      proxy_set_header X-Scheme $scheme;
                      proxy_set_header Host $host;
                      proxy_set_header X-Real-IP $remote_addr;
                      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                      proxy_set_header X-Forwarded-Proto $scheme;

                      proxy_http_version 1.1;
                      proxy_set_header Connection "";

                      proxy_buffering off;
                      proxy_cache off;

                      proxy_read_timeout 60s;
                      proxy_connect_timeout 60s;
                      proxy_send_timeout 60s;
                  }

                  # Redirect root path to /console
                  location = / {
                      return 301 /console;
                  }

                  # Health check endpoint
                  location /health {
                      access_log off;
                      return 200 "healthy\n";
                      add_header Content-Type text/plain;
                  }
              }
          }
    depends_on:
      - appflowy_cloud
      - gotrue
      - admin_frontend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  appflowy_web:
    environment:
      - SERVICE_URL_APPFLOWYWEB
      - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN}
      - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_ADMIN}/gotrue
      - APPFLOWY_WS_BASE_URL=${SERVICE_URL_ADMIN}/ws/v2
    image: appflowyinc/appflowy_web:0.9.132
    depends_on:
      - appflowy_cloud

  appflowy_cloud:
    image: appflowyinc/appflowy_cloud:0.9.149
    environment:
      - RUST_LOG=${RUST_LOG:-info}
      - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN}
      - APPFLOWY_ENVIRONMENT=production
      - APPFLOWY_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}
      - APPFLOWY_REDIS_URI=redis://redis:6379
      - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT}
      - APPFLOWY_GOTRUE_BASE_URL=http://gotrue:9999
      - APPFLOWY_S3_CREATE_BUCKET=${APPFLOWY_S3_CREATE_BUCKET:-true}
      - APPFLOWY_S3_USE_MINIO=true
      - APPFLOWY_S3_MINIO_URL=http://minio:9000
      - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO}
      - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1}
      - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST:-smtp.gmail.com}
      - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT:-587}
      - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME:-notify@appflowy.io}
      - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL:-notify@appflowy.io}
      - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD:-email_sender_password}
      - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND:-none}
      - APPFLOWY_ACCESS_CONTROL=true
      - APPFLOWY_DATABASE_MAX_CONNECTIONS=40
      - AI_SERVER_HOST=ai
      - AI_SERVER_PORT=5001
      - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY:-}
      - APPFLOWY_WEB_URL=${SERVICE_URL_APPFLOWYWEB}
    healthcheck:
      test: "curl --fail http://127.0.0.1:8000/api/health || exit 1"
      interval: 5s
      timeout: 5s
      retries: 12
    
    depends_on:
      gotrue:
        condition: service_healthy
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  gotrue:
    image: appflowyinc/gotrue:0.9.149
    environment:
      - GOTRUE_API_HOST=0.0.0.0
      - GOTRUE_API_PORT=9999
      - PORT=9999
      - GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL:-admin@example.com}
      - GOTRUE_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}
      - GOTRUE_DISABLE_SIGNUP=false
      - GOTRUE_SITE_URL=appflowy-flutter://
      - GOTRUE_URI_ALLOW_LIST=**
      - GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT}
      - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200}
      - GOTRUE_DB_DRIVER=postgres
      - API_EXTERNAL_URL=${SERVICE_URL_ADMIN}/gotrue
      - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}?search_path=auth
      - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST}
      - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT:-587}
      - GOTRUE_SMTP_USER=${GOTRUE_SMTP_USER}
      - GOTRUE_SMTP_PASS=${GOTRUE_SMTP_PASS}
      - GOTRUE_MAILER_URLPATHS_CONFIRMATION=/verify
      - GOTRUE_MAILER_URLPATHS_INVITE=/verify
      - GOTRUE_MAILER_URLPATHS_RECOVERY=/verify
      - GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/verify
      - GOTRUE_SMTP_ADMIN_EMAIL=${GOTRUE_SMTP_ADMIN_EMAIL}
      - GOTRUE_SMTP_MAX_FREQUENCY=1ns
      - GOTRUE_RATE_LIMIT_EMAIL_SENT=100
      - GOTRUE_MAILER_AUTOCONFIRM=true
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: "curl --fail http://127.0.0.1:9999/health || exit 1"
      interval: 5s
      timeout: 5s
      retries: 12

  admin_frontend:
    image: appflowyinc/admin_frontend:0.9.149
    environment:
      - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_ADMIN}/gotrue
      - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN}
    depends_on:
      gotrue:
        condition: service_healthy
      appflowy_cloud:
        condition: service_started

  ai:
    image: appflowyinc/appflowy_ai:0.9.149
    environment:
      - OPENAI_API_KEY=${AI_OPENAI_API_KEY:-}
      - AI_SERVER_PORT=5001
      - DEFAULT_AI_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account
      - DEFAULT_AI_COMPLETION_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account
      - AI_APPFLOWY_HOST=${SERVICE_URL_APPFLOWYWEB}
      - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT}

      - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
      - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
      - AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
      - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO}
      - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1}
      - AI_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}
      - AI_REDIS_URL=redis://redis:6379
      - AI_USE_MINIO=true
      - AI_MINIO_URL=http://minio:9000
    depends_on:
      postgres:
        condition: service_healthy

  appflowy_worker:
    image: appflowyinc/appflowy_worker:0.9.149
    environment:
      - RUST_LOG=info
      - APPFLOWY_ENVIRONMENT=production
      - APPFLOWY_WORKER_ENVIRONMENT=production
      - APPFLOWY_WORKER_REDIS_URL=redis://redis:6379
      - APPFLOWY_WORKER_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}
      - APPFLOWY_WORKER_DATABASE_NAME=${POSTGRES_DB:-appflowy}
      - APPFLOWY_WORKER_IMPORT_TICK_INTERVAL=30
      - APPFLOWY_S3_USE_MINIO=true
      - APPFLOWY_S3_MINIO_URL=http://minio:9000
      - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO}
      - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1}
      - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST:-smtp.gmail.com}
      - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT:-587}
      - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME:-notify@appflowy.io}
      - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL:-notify@appflowy.io}
      - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD:-email_sender_password}
      - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND:-none}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  minio:
    image: minio/minio:RELEASE.2025-09-07T16-13-09Z
    environment:
      - MINIO_ROOT_USER=${SERVICE_USER_MINIO}
      - MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO}
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    volumes:
      # Coolify will manage this named volume for persistent object storage.
      - minio_data:/data

  postgres:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_USER=${SERVICE_USER_POSTGRES}
      - POSTGRES_DB=${POSTGRES_DB:-appflowy}
      - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
      - POSTGRES_HOST=postgres
    volumes:
      # Coolify will manage this named volume for persistent database storage.
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD", "pg_isready", "-U", "${SERVICE_USER_POSTGRES}", "-d", "${POSTGRES_DB:-appflowy}" ]
      interval: 5s
      timeout: 5s
      retries: 12

  redis:
    image: redis:8.2.2-alpine3.22
    volumes:
      - "redis_data:/data"
    healthcheck:
      test:
        - CMD-SHELL
        - "redis-cli -h localhost -p 6379 ping"
      interval: 5s
      timeout: 5s
      retries: 3
