# جدا کردن 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"** روی استک اپ فعال شود و دیتابیس با شناسه‌ی کامل `-` به‌عنوان hostname رفرنس داده شود (مثل `postgresql-abc123...`؛ برای ما `mariadb-` / `redis-`). - **هرگز `networks:` سفارشی** در compose تعریف نکن — Coolify خودش مدیریت می‌کند؛ شبکه‌ی سفارشی routing Traefik را می‌شکند و باعث قطعی متناوب می‌شود. - **مقادیر اتصال (DATABASE_URL / REDIS_URL / MESSENGER_TRANSPORT_DSN)** دیگر به نام سرویس داخلی اشاره نمی‌کنند؛ از تب Environment Variables در Coolify ست می‌شوند و به hostname دیتابیس مستقل (`mariadb-` / `redis-`) اشاره می‌کنند. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `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 آن‌ها گره خورده: ```yaml # 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). نمونه‌ی نهایی موردانتظار (شِمای کلی): ```yaml 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 باید `-` باشد (UUID از صفحه‌ی Resource در Coolify گرفته می‌شود): ```bash # اتصال به MariaDB مستقل Coolify (hostname = mariadb- از صفحه‌ی Resource) DATABASE_URL="mysql://clinic:PASSWORD@mariadb-XXXXXXXX:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4" # اتصال به Redis مستقل Coolify (hostname = redis-) REDIS_URL="redis://redis-XXXXXXXX:6379" MESSENGER_TRANSPORT_DSN="redis://redis-XXXXXXXX:6379/messages" ``` - توضیح بده که اگر Redis مستقل Coolify رمز دارد، فرمت `redis://:PASSWORD@redis-:6379` است. ### ۴. تحمل قطعی DB در استارت‌آپ (چون دیگر `depends_on: service_healthy` نیست) چون اپ دیگر منتظر healthcheck دیتابیس نمی‌ماند، ممکن است هنگام بوت، DB هنوز آماده نباشد و `doctrine:migrations:migrate` در `entrypoint.sh` شکست بخورد. در `docker/entrypoint.sh`، قبل از migration یک انتظار کوتاه برای آماده‌شدن DB اضافه کن (مثلاً حلقه‌ای که با یک کوئری ساده‌ی Doctrine اتصال را چک می‌کند، چند بار با تأخیر retry): ```sh # منتظر آماده‌شدن دیتابیس مستقل بمان (حداکثر ~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 `-`. - توضیح بده که volumeهای `mariadb_data`/`redis_data` دیگر در استک اپ نیستند؛ ماندگاری داده برعهده‌ی خود Resourceهاست. ## نکات مهم - **هیچ `networks:` سفارشی** اضافه نشود؛ اتصال cross-resource فقط با "Connect to Predefined Network" + hostname `-`. - مقادیر `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`). ```