221 lines
12 KiB
Markdown
221 lines
12 KiB
Markdown
# جدا کردن 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 آنها گره خورده:
|
|
|
|
```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 باید `<service>-<uuid>` باشد (UUID از صفحهی Resource در Coolify گرفته میشود):
|
|
|
|
```bash
|
|
# اتصال به 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):
|
|
|
|
```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 `<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`).
|
|
```
|