Files
clinicpro/.claude/prompt/split-db-redis-to-coolify-resources.md
T

12 KiB
Raw Blame History

جدا کردن MariaDB و Redis از docker-compose به Resourceهای مستقل Coolify

پروژه

clinicpro (backend / deploy)

زمینه

الان clinicpro/docker-compose.yml یک استک کامل است: app + worker-async + worker-scheduler + mariadb + redis. هر سه سرویس اپ از یک image مشترک (clinicpro-app:latest) استفاده می‌کنند و mariadb/redis داخل همین compose به‌صورت سرویس تعریف شده‌اند با volumeهای محلی (mariadb_data, redis_data).

کاربر می‌خواهد در Coolify، MariaDB و Redis را به‌صورت Database Resource مستقل بسازد و مدیریت کند (بکاپ، monitoring، آپدیت جدا، ری‌استارت مستقل از اپ). یعنی این دو باید از docker-compose.yml اپ حذف شوند و اپ به آن‌ها از طریق شبکه‌ی Coolify وصل شود.

هدف

docker-compose.yml فقط شامل سرویس‌های اپلیکیشن باشد (app + دو worker). MariaDB و Redis از compose حذف شوند و اپ به Resourceهای مستقل Coolify وصل شود.

قوانین Coolify (از داکیومنت‌ها — رعایت اجباری)

  • شبکه: Coolify برای هر Resource یک شبکه‌ی bridge ایزوله (بر اساس UUID) می‌سازد. سرویس‌های داخل یک compose با نام سرویس همدیگر را می‌بینند. اما برای دسترسی به یک Resource دیگر (دیتابیس مستقل)، باید گزینه‌ی "Connect to Predefined Network" روی استک اپ فعال شود و دیتابیس با شناسه‌ی کامل <service>-<uuid> به‌عنوان hostname رفرنس داده شود (مثل postgresql-abc123...؛ برای ما mariadb-<uuid> / redis-<uuid>).
  • هرگز networks: سفارشی در compose تعریف نکن — Coolify خودش مدیریت می‌کند؛ شبکه‌ی سفارشی routing Traefik را می‌شکند و باعث قطعی متناوب می‌شود.
  • مقادیر اتصال (DATABASE_URL / REDIS_URL / MESSENGER_TRANSPORT_DSN) دیگر به نام سرویس داخلی اشاره نمی‌کنند؛ از تب Environment Variables در Coolify ست می‌شوند و به hostname دیتابیس مستقل (mariadb-<uuid> / redis-<uuid>) اشاره می‌کنند.

فایل‌های مرتبط

فایل نقش
clinicpro/docker-compose.yml استک دیپلوی Coolify — باید mariadb/redis از آن حذف شود
clinicpro/.env.coolify.example نمونه env vars برای Coolify — باید برای اتصال به دیتابیس مستقل به‌روز شود
clinicpro/docs/DEPLOY.md راهنمای دیپلوی — باید مراحل ساخت Resource مستقل اضافه شود
clinicpro/docker/entrypoint.sh فقط روی app (RUN_INIT=1) migration می‌زند — منطق بدون تغییر، فقط باید به DB مستقل وصل شود

وضعیت فعلی

استک فعلی mariadb/redis را داخل خودش دارد و depends_on به healthcheck آن‌ها گره خورده:

# x-app-env
  DATABASE_URL: "mysql://clinic:${DB_PASSWORD}@mariadb:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"
  MESSENGER_TRANSPORT_DSN: redis://redis:6379/messages
  REDIS_URL: redis://redis:6379

# x-app-depends
  mariadb:
    condition: service_healthy
  redis:
    condition: service_started

services:
  app: ...
    depends_on: *app-depends
  worker-async: ...
    depends_on: *app-depends
  worker-scheduler: ...
    depends_on: *app-depends

  mariadb:
    image: mariadb:11.8
    ...
    volumes:
      - mariadb_data:/var/lib/mysql
    healthcheck: ...

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data

volumes:
  jwt_keys:
  uploads_public:
  uploads_var:
  mariadb_data:    # ← متعلق به دیتابیس داخلی
  redis_data:      # ← متعلق به redis داخلی

وظایف

۱. حذف سرویس‌های mariadb و redis از docker-compose.yml

  • بلوک سرویس mariadb و redis کامل حذف شوند.
  • volumeهای mariadb_data و redis_data از بخش volumes: حذف شوند (دیگر داخل این استک نیستند؛ داده‌ی DB مستقل توسط خود Resource نگه داشته می‌شود). jwt_keys, uploads_public, uploads_var بمانند.

۲. حذف depends_on به mariadb/redis

x-app-depends (و ارجاع depends_on: *app-depends در هر سه سرویس) دیگر بی‌معناست — این سرویس‌ها در استک نیستند. دو گزینه:

  • پیشنهادی: anchor x-app-depends و همه‌ی depends_on: *app-depends حذف شوند. به‌جای آن، تحمل قطعی موقت در استارت‌آپ تضمین شود (وظیفه ۴).
  • اگر می‌خواهی ترتیب استارت app قبل از workerها حفظ شود، فقط workerها می‌توانند depends_on: [app] ساده داشته باشند (بدون condition healthcheck).

نمونه‌ی نهایی موردانتظار (شِمای کلی):

x-app-env: &app-env
  APP_ENV: prod
  APP_DEBUG: "0"
  APP_SECRET: ${APP_SECRET}
  # به دیتابیس/Redis مستقل Coolify اشاره می‌کند — مقدار واقعی از تب Environment Variables
  DATABASE_URL: ${DATABASE_URL}
  MESSENGER_TRANSPORT_DSN: ${MESSENGER_TRANSPORT_DSN}
  REDIS_URL: ${REDIS_URL}
  JWT_SECRET_KEY: "%kernel.project_dir%/config/jwt/private.pem"
  JWT_PUBLIC_KEY: "%kernel.project_dir%/config/jwt/public.pem"
  JWT_PASSPHRASE: ${JWT_PASSPHRASE}
  CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN}
  DEFAULT_URI: ${APP_BASE_URL}
  APP_BASE_URL: ${APP_BASE_URL}
  ALLOWED_FRONTEND_HOSTS: ${ALLOWED_FRONTEND_HOSTS}
  TRUSTED_PROXIES: ${TRUSTED_PROXIES}
  API_IR_BASE_URL: ${API_IR_BASE_URL}
  API_IR_TOKEN: ${API_IR_TOKEN}
  REFRESH_TOKEN_TTL: "2592000"
  OTP_TTL: "1200"
  MAX_FILE_SIZE_BYTES: "5242880"
  UPLOAD_DIR: var/uploads

x-app-volumes: &app-volumes
  - jwt_keys:/app/config/jwt
  - uploads_public:/app/public/uploads
  - uploads_var:/app/var/uploads

services:
  app:
    image: clinicpro-app:latest
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      <<: *app-env
      RUN_INIT: "1"
    volumes: *app-volumes
    healthcheck:
      test: ["CMD", "php", "-r", "exit(@fsockopen('127.0.0.1', 80) ? 0 : 1);"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 60s

  worker-async:
    image: clinicpro-app:latest
    restart: unless-stopped
    command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v
    environment:
      <<: *app-env
      RUN_INIT: "0"
    volumes: *app-volumes
    depends_on: [app]

  worker-scheduler:
    image: clinicpro-app:latest
    restart: unless-stopped
    command: php bin/console messenger:consume scheduler_default --time-limit=3600 -v
    environment:
      <<: *app-env
      RUN_INIT: "0"
    volumes: *app-volumes
    depends_on: [app]

volumes:
  jwt_keys:
  uploads_public:
  uploads_var:

توجه: DATABASE_URL/REDIS_URL/MESSENGER_TRANSPORT_DSN از hardcode داخل compose به ${...} تبدیل شدند تا از Coolify (با hostname دیتابیس مستقل) تزریق شوند. DB_PASSWORD/DB_ROOT_PASSWORD دیگر در این compose استفاده نمی‌شوند (متعلق به Resource دیتابیس‌اند).

۳. به‌روزرسانی .env.coolify.example

  • متغیرهای DB_PASSWORD / DB_ROOT_PASSWORD که مخصوص mariadb داخلی بودند را حذف یا به بخش «این‌ها را موقع ساخت Database Resource ست کن» منتقل کن.
  • سه متغیر اتصال اضافه/مستندسازی شوند که به Resource مستقل اشاره می‌کنند. hostname باید <service>-<uuid> باشد (UUID از صفحه‌ی Resource در Coolify گرفته می‌شود):
# اتصال به MariaDB مستقل Coolify (hostname = mariadb-<uuid> از صفحه‌ی Resource)
DATABASE_URL="mysql://clinic:PASSWORD@mariadb-XXXXXXXX:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"

# اتصال به Redis مستقل Coolify (hostname = redis-<uuid>)
REDIS_URL="redis://redis-XXXXXXXX:6379"
MESSENGER_TRANSPORT_DSN="redis://redis-XXXXXXXX:6379/messages"
  • توضیح بده که اگر Redis مستقل Coolify رمز دارد، فرمت redis://:PASSWORD@redis-<uuid>:6379 است.

۴. تحمل قطعی DB در استارت‌آپ (چون دیگر depends_on: service_healthy نیست)

چون اپ دیگر منتظر healthcheck دیتابیس نمی‌ماند، ممکن است هنگام بوت، DB هنوز آماده نباشد و doctrine:migrations:migrate در entrypoint.sh شکست بخورد. در docker/entrypoint.sh، قبل از migration یک انتظار کوتاه برای آماده‌شدن DB اضافه کن (مثلاً حلقه‌ای که با یک کوئری ساده‌ی Doctrine اتصال را چک می‌کند، چند بار با تأخیر retry):

# منتظر آماده‌شدن دیتابیس مستقل بمان (حداکثر ~60s) قبل از migration
if [ "${RUN_INIT:-1}" = "1" ]; then
    i=0
    until php bin/console dbal:run-sql "SELECT 1" >/dev/null 2>&1; do
        i=$((i+1))
        [ "$i" -ge 30 ] && echo "DB not reachable after 60s" && exit 1
        echo "waiting for database... ($i)"
        sleep 2
    done
    # سپس: JWT keygen + cache + migrations (منطق فعلی)
fi

منطق فعلی JWT keygen / cache:clear / cache:warmup / migrate دست‌نخورده بماند؛ فقط حلقه‌ی انتظار قبل از آن اضافه شود. (در صورت نبودن دستور dbal:run-sql، از doctrine:query:sql "SELECT 1" استفاده کن — اول با ddev exec php bin/console list doctrine | grep -i sql چک کن کدام موجود است.)

۵. به‌روزرسانی docs/DEPLOY.md

بخش «استک» و «متغیرهای محیطی» را اصلاح کن:

  • جدول سرویس‌ها: mariadb و redis از استک اپ حذف شده‌اند و حالا Database Resource مستقل Coolify هستند.
  • مراحل جدید: (الف) ساخت Resource مستقل MariaDB در Coolify، (ب) ساخت Resource مستقل Redis، (ج) فعال‌کردن "Connect to Predefined Network" روی استک اپ، (د) ست‌کردن DATABASE_URL/REDIS_URL/MESSENGER_TRANSPORT_DSN با hostname <service>-<uuid>.
  • توضیح بده که volumeهای mariadb_data/redis_data دیگر در استک اپ نیستند؛ ماندگاری داده برعهده‌ی خود Resourceهاست.

نکات مهم

  • هیچ networks: سفارشی اضافه نشود؛ اتصال cross-resource فقط با "Connect to Predefined Network" + hostname <service>-<uuid>.
  • مقادیر DATABASE_URL/REDIS_URL/MESSENGER_TRANSPORT_DSN نباید داخل compose hardcode شوند؛ از ${...} و تب Coolify بیایند (چون UUID برای هر نصب فرق دارد).
  • serverVersion=mariadb-11.8.0 در DATABASE_URL حفظ شود (Doctrine برای تولید SQL درست لازم دارد) — مطمئن شو نسخه‌ی Resource مستقل MariaDB هم 11.8 انتخاب شود تا با migrationها هم‌خوان باشد.
  • فقط سرویس app (RUN_INIT=1) migration می‌زند؛ workerها (RUN_INIT=0) نباید — این رفتار حفظ شود تا race در migration پیش نیاید.
  • image: clinicpro-app:latest مشترک حفظ شود (app یک‌بار build، workerها reuse) — این فیکس قبلی برای جلوگیری از سه‌بار build است؛ خرابش نکن.
  • بعد از تغییر، با docker compose config اعتبار YAML را چک کن.
  • این تغییر فقط deploy/infra است؛ هیچ controller/route/Entity تغییر نمی‌کند، پس نیازی به آپدیت docs/api/* نیست (فقط docs/DEPLOY.md).