refactor: separate MariaDB and Redis into independent Coolify resources
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
# جدا کردن 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`).
|
||||
```
|
||||
+22
-6
@@ -2,11 +2,25 @@
|
||||
# Coolify Environment Variables — ClinicPro (Docker Compose)
|
||||
# ------------------------------------------------------------
|
||||
# Copy these into the Coolify resource's "Environment Variables" tab.
|
||||
# Only the variables referenced as ${...} in docker-compose.yml
|
||||
# need to be set here. DATABASE_URL / MESSENGER_TRANSPORT_DSN / REDIS_URL
|
||||
# are built INSIDE the compose file from the mariadb/redis service names.
|
||||
# Only the variables referenced as ${...} in docker-compose.yml need to be set.
|
||||
#
|
||||
# MariaDB and Redis are SEPARATE Coolify Database Resources (not in the compose).
|
||||
# So DATABASE_URL / REDIS_URL / MESSENGER_TRANSPORT_DSN must be set HERE, pointing
|
||||
# at those resources by their internal hostname (mariadb-<uuid> / redis-<uuid>).
|
||||
# Steps:
|
||||
# 1. Create a standalone MariaDB 11.8 resource + a standalone Redis resource.
|
||||
# 2. Enable "Connect to Predefined Network" on this app stack.
|
||||
# 3. Copy each resource's internal hostname/credentials into the URLs below.
|
||||
# ============================================================
|
||||
|
||||
# ── Database / Redis connections (point at the SEPARATE Coolify resources) ──
|
||||
# Replace mariadb-XXXXXXXX / redis-XXXXXXXX with the real hostname shown on each
|
||||
# resource's page (Internal URL). serverVersion MUST match the MariaDB resource (11.8).
|
||||
DATABASE_URL="mysql://clinic:DB_PASSWORD@mariadb-XXXXXXXX:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"
|
||||
REDIS_URL="redis://redis-XXXXXXXX:6379"
|
||||
MESSENGER_TRANSPORT_DSN="redis://redis-XXXXXXXX:6379/messages"
|
||||
# If the Redis resource has a password: redis://:PASSWORD@redis-XXXXXXXX:6379
|
||||
|
||||
# ── Backend's own domain (single URL — used for payment callbacks & absolute URLs) ──
|
||||
# This is the API host, NOT a frontend domain.
|
||||
APP_BASE_URL=https://api.nobat724.com
|
||||
@@ -24,11 +38,12 @@ CORS_ALLOW_ORIGIN='^https://(ahvaz\-nobat\.ir|arak\-nobat\.ir|ardabil\-nobat\.ir
|
||||
# ── Secrets (REQUIRED — set before the first deploy) ──
|
||||
APP_SECRET= # php -r "echo bin2hex(random_bytes(32));"
|
||||
JWT_PASSPHRASE= # openssl rand -hex 32 (must exist before first start: JWT keypair is generated with it)
|
||||
DB_PASSWORD= # application DB user password
|
||||
DB_ROOT_PASSWORD= # MariaDB root password
|
||||
|
||||
# NOTE: DB_PASSWORD / DB_ROOT_PASSWORD are NO LONGER set here. The DB credentials
|
||||
# now belong to the standalone MariaDB resource — set them when you create that
|
||||
# resource, then embed the user password inside DATABASE_URL above.
|
||||
|
||||
# Tip: in Coolify you may use magic vars instead of hardcoding, e.g.
|
||||
# DB_PASSWORD=${SERVICE_PASSWORD_DB}
|
||||
# APP_SECRET=${SERVICE_HEX_APPSECRET}
|
||||
|
||||
# ── Reverse proxy (Coolify/Traefik) ──
|
||||
@@ -44,3 +59,4 @@ API_IR_TOKEN=
|
||||
# • SMS keys (kavenegar/rangineh) and payment gateway keys (mellat/sep) are read
|
||||
# from the DB ("Site Settings"), NOT from env. No need to set them here.
|
||||
# • REFRESH_TOKEN_TTL / OTP_TTL / MAX_FILE_SIZE_BYTES are fixed in the compose file.
|
||||
# • MariaDB & Redis are separate Coolify resources — see DATABASE_URL/REDIS_URL above.
|
||||
|
||||
+17
-42
@@ -1,6 +1,11 @@
|
||||
# Coolify deployment — Build Pack: Docker Compose
|
||||
# This is the production deploy stack (MariaDB + Redis). The ddev local env uses
|
||||
# its own compose.yaml; do not confuse the two.
|
||||
# This is the production APP stack (app + workers ONLY). MariaDB and Redis are
|
||||
# run as SEPARATE Coolify Database Resources, not part of this compose.
|
||||
# - Create a standalone MariaDB resource and a standalone Redis resource in Coolify.
|
||||
# - Enable "Connect to Predefined Network" on THIS app stack.
|
||||
# - Set DATABASE_URL / REDIS_URL / MESSENGER_TRANSPORT_DSN in Coolify's env tab,
|
||||
# pointing at the resources via their internal hostnames (mariadb-<uuid> / redis-<uuid>).
|
||||
# The ddev local env uses its own compose.yaml; do not confuse the two.
|
||||
# Select this file (docker-compose.yml) as the Compose file in the Coolify resource.
|
||||
# Do NOT define custom `networks:` — Coolify manages the network; custom ones
|
||||
# break Traefik routing (per Coolify docs).
|
||||
@@ -10,9 +15,13 @@ x-app-env: &app-env
|
||||
APP_ENV: prod
|
||||
APP_DEBUG: "0"
|
||||
APP_SECRET: ${APP_SECRET}
|
||||
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
|
||||
# Connection strings point at the SEPARATE Coolify DB/Redis resources.
|
||||
# Real values are injected from Coolify's Environment Variables tab — the
|
||||
# hostname is mariadb-<uuid> / redis-<uuid> from each resource's page, so they
|
||||
# cannot be hardcoded here (the UUID differs per installation).
|
||||
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}
|
||||
@@ -34,13 +43,6 @@ x-app-volumes: &app-volumes
|
||||
- uploads_public:/app/public/uploads
|
||||
- uploads_var:/app/var/uploads
|
||||
|
||||
# Shared dependency gate on DB + Redis being up.
|
||||
x-app-depends: &app-depends
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
services:
|
||||
# Web app (PHP-FPM + Nginx).
|
||||
# In Coolify, assign ALL serving domains to THIS service (port 80) — the backend
|
||||
@@ -51,7 +53,7 @@ services:
|
||||
app:
|
||||
# Shared image tag — app builds it once; the two workers reuse the SAME image
|
||||
# (image: below, no build:). Without this Coolify builds the Dockerfile 3×,
|
||||
# tripling apk/pecl network fetches and intermittently failing on the host.
|
||||
# tripling apk network fetches and intermittently failing on the host.
|
||||
image: clinicpro-app:latest
|
||||
build:
|
||||
context: .
|
||||
@@ -61,7 +63,6 @@ services:
|
||||
<<: *app-env
|
||||
RUN_INIT: "1" # runs JWT keygen + migrations on start (only this service)
|
||||
volumes: *app-volumes
|
||||
depends_on: *app-depends
|
||||
healthcheck:
|
||||
test: ["CMD", "php", "-r", "exit(@fsockopen('127.0.0.1', 80) ? 0 : 1);"]
|
||||
interval: 15s
|
||||
@@ -78,7 +79,7 @@ services:
|
||||
<<: *app-env
|
||||
RUN_INIT: "0"
|
||||
volumes: *app-volumes
|
||||
depends_on: *app-depends
|
||||
depends_on: [app]
|
||||
|
||||
# Scheduler consumer — expires unpaid appointments every minute. Reuses app's image.
|
||||
worker-scheduler:
|
||||
@@ -89,35 +90,9 @@ services:
|
||||
<<: *app-env
|
||||
RUN_INIT: "0"
|
||||
volumes: *app-volumes
|
||||
depends_on: *app-depends
|
||||
|
||||
mariadb:
|
||||
image: mariadb:11.8
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MARIADB_DATABASE: clinic_pro
|
||||
MARIADB_USER: clinic
|
||||
MARIADB_PASSWORD: ${DB_PASSWORD}
|
||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- mariadb_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
depends_on: [app]
|
||||
|
||||
volumes:
|
||||
jwt_keys:
|
||||
uploads_public:
|
||||
uploads_var:
|
||||
mariadb_data:
|
||||
redis_data:
|
||||
|
||||
@@ -8,6 +8,20 @@ chown -R www-data:www-data var public/uploads config/jwt 2>/dev/null || true
|
||||
# One-time init tasks — only the web service runs these (RUN_INIT=1).
|
||||
# Workers set RUN_INIT=0 so DB migrations / JWT generation don't race.
|
||||
if [ "${RUN_INIT:-1}" = "1" ]; then
|
||||
# MariaDB is a SEPARATE Coolify resource now, so there is no compose
|
||||
# depends_on: service_healthy gate. Wait for it to accept connections
|
||||
# (up to ~60s) before migrating, otherwise the first boot races the DB.
|
||||
i=0
|
||||
until php bin/console doctrine:query:sql "SELECT 1" >/dev/null 2>&1; do
|
||||
i=$((i + 1))
|
||||
if [ "$i" -ge 30 ]; then
|
||||
echo "Database not reachable after 60s — aborting." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "waiting for database... ($i)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Generate JWT keypair if not already persisted on the jwt_keys volume.
|
||||
php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
; Production PHP settings for ClinicPro on Coolify
|
||||
memory_limit = 256M
|
||||
memory_limit = 1024M
|
||||
upload_max_filesize = 16M
|
||||
post_max_size = 16M
|
||||
post_max_size = 32M
|
||||
max_execution_time = 60
|
||||
expose_php = Off
|
||||
date.timezone = Asia/Tehran
|
||||
|
||||
+48
-22
@@ -8,22 +8,25 @@
|
||||
|
||||
## معماری استک
|
||||
|
||||
`docker-compose.yml` پنج سرویس بالا میآورد:
|
||||
`docker-compose.yml` فقط سرویسهای **اپلیکیشن** را بالا میآورد. **MariaDB و Redis جداگانه** بهصورت Database Resource مستقل Coolify اجرا میشوند (نه داخل این compose):
|
||||
|
||||
| سرویس | نقش | نکته |
|
||||
|---|---|---|
|
||||
| `app` | PHP-FPM + Nginx (وب) | تنها سرویسی که `RUN_INIT=1` دارد؛ مهاجرت DB و تولید کلید JWT را اجرا میکند. دامنهها را به این سرویس (پورت 80) وصل کن. |
|
||||
| `worker-async` | مصرفکننده صف async (SMS و کارهای async) | `messenger:consume async` |
|
||||
| `worker-scheduler` | زمانبند | هر ۱ دقیقه نوبتهای پرداختنشده را منقضی میکند |
|
||||
| `mariadb` | پایگاهداده MariaDB 11.8 | healthcheck دارد؛ بقیه منتظرش میمانند |
|
||||
| `redis` | صف Messenger + کش | `appendonly yes` (ماندگار) |
|
||||
|
||||
**ولومهای ماندگار** (دادهها در ریدیپلوی حفظ میشوند):
|
||||
| Resource مستقل Coolify | نقش |
|
||||
|---|---|
|
||||
| **MariaDB 11.8** (Database Resource جدا) | پایگاهداده — مدیریت/بکاپ/ریاستارت مستقل |
|
||||
| **Redis** (Database Resource جدا) | صف Messenger + کش |
|
||||
|
||||
**ولومهای ماندگار استک اپ** (در ریدیپلوی حفظ میشوند):
|
||||
|
||||
- `jwt_keys` → کلیدهای JWT
|
||||
- `uploads_public` و `uploads_var` → فایلهای آپلودی
|
||||
- `mariadb_data` → داده DB
|
||||
- `redis_data` → داده Redis
|
||||
|
||||
> دادهی MariaDB و Redis توسط خودِ Resourceهای مستقل نگه داشته میشود (volume در آنها، نه در این استک).
|
||||
|
||||
---
|
||||
|
||||
@@ -35,16 +38,27 @@
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۱ — ساخت منبع (Resource) در Coolify
|
||||
## مرحله ۱ — ساخت دیتابیسهای مستقل (MariaDB + Redis)
|
||||
|
||||
اول این دو Resource را جدا بساز (قبل از استک اپ):
|
||||
|
||||
1. **New Resource → Database → MariaDB**، نسخه **11.8** (باید با `serverVersion` در `DATABASE_URL` و migrationها همخوان باشد). نام دیتابیس `clinic_pro`، یوزر `clinic`، یک رمز قوی ست کن.
|
||||
2. **New Resource → Database → Redis**.
|
||||
3. از صفحهی هر Resource، **Internal URL / hostname** (به شکل `mariadb-<uuid>` و `redis-<uuid>`) و credentials را یادداشت کن — در مرحله ۴ لازم میشود.
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۲ — ساخت منبع اپ (Resource) در Coolify
|
||||
|
||||
1. **New Resource → Docker Compose** (Build Pack: `Docker Compose`).
|
||||
2. ریپو و برنچ را انتخاب کن.
|
||||
3. فیلد **Compose file** را روی `docker-compose.yml` بگذار.
|
||||
4. `networks:` سفارشی تعریف **نکن** — شبکه را Coolify مدیریت میکند؛ شبکهی سفارشی روتینگ Traefik را میشکند.
|
||||
4. **"Connect to Predefined Network"** را روی این استک **فعال کن** — تا اپ بتواند به Resourceهای مستقل MariaDB/Redis (که در شبکهی دیگری هستند) وصل شود.
|
||||
5. `networks:` سفارشی تعریف **نکن** — شبکه را Coolify مدیریت میکند؛ شبکهی سفارشی روتینگ Traefik را میشکند.
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۲ — دامنهها
|
||||
## مرحله ۳ — دامنهها
|
||||
|
||||
همهی دامنههای سرو شونده را به سرویس **`app`** (پورت 80) اختصاص بده — هم دامنهی API و هم همهی دامنههای فرانتاند. Coolify لیست دامنهی جداشده با کاما را روی یک سرویس قبول میکند و TLS را خودش صادر میکند.
|
||||
|
||||
@@ -52,27 +66,37 @@
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۳ — متغیرهای محیطی
|
||||
## مرحله ۴ — متغیرهای محیطی
|
||||
|
||||
از `.env.coolify.example` کپی کن و در تب **Environment Variables** منبع Coolify بگذار.
|
||||
از `.env.coolify.example` کپی کن و در تب **Environment Variables** منبع اپ Coolify بگذار.
|
||||
|
||||
فقط متغیرهایی که در `docker-compose.yml` بهصورت `${...}` ارجاع شدهاند لازماند. `DATABASE_URL` / `MESSENGER_TRANSPORT_DSN` / `REDIS_URL` داخل خود compose از روی نام سرویسها ساخته میشوند.
|
||||
### اتصال به دیتابیسهای مستقل (الزامی)
|
||||
|
||||
چون MariaDB/Redis جدا هستند، رشتههای اتصال **اینجا** ست میشوند و به hostname داخلی Resource اشاره میکنند (`mariadb-<uuid>` / `redis-<uuid>` از مرحله ۱):
|
||||
|
||||
```bash
|
||||
DATABASE_URL="mysql://clinic:DB_PASSWORD@mariadb-XXXXXXXX:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"
|
||||
REDIS_URL="redis://redis-XXXXXXXX:6379"
|
||||
MESSENGER_TRANSPORT_DSN="redis://redis-XXXXXXXX:6379/messages"
|
||||
# اگر Redis رمز دارد: redis://:PASSWORD@redis-XXXXXXXX:6379
|
||||
```
|
||||
|
||||
> `serverVersion=mariadb-11.8.0` باید با نسخهی Resource مستقل MariaDB یکی باشد.
|
||||
|
||||
### اسرار (الزامی — قبل از اولین دیپلوی)
|
||||
|
||||
```bash
|
||||
APP_SECRET= # php -r "echo bin2hex(random_bytes(32));"
|
||||
JWT_PASSPHRASE= # openssl rand -hex 32 (باید قبل از اولین استارت موجود باشد؛ کلید JWT با همین ساخته میشود)
|
||||
DB_PASSWORD= # رمز کاربر DB اپلیکیشن
|
||||
DB_ROOT_PASSWORD= # رمز root مریادیبی
|
||||
```
|
||||
|
||||
> رمز DB دیگر اینجا (`DB_PASSWORD`/`DB_ROOT_PASSWORD`) ست نمیشود — هنگام ساخت Resource مستقل MariaDB تعیین میشود و داخل `DATABASE_URL` بالا قرار میگیرد.
|
||||
|
||||
> ⚠️ `JWT_PASSPHRASE` را بعد از اولین دیپلوی عوض نکن — کلید JWT یکبار با همین passphrase تولید و روی ولوم `jwt_keys` ماندگار میشود. تغییرش همهی توکنها را میشکند.
|
||||
|
||||
در Coolify میتوانی بهجای هاردکد از magic var استفاده کنی:
|
||||
|
||||
```bash
|
||||
DB_PASSWORD=${SERVICE_PASSWORD_DB}
|
||||
APP_SECRET=${SERVICE_HEX_APPSECRET}
|
||||
```
|
||||
|
||||
@@ -115,20 +139,21 @@ API_IR_TOKEN= # خالی => fail-closed (تأیید نماینده رد می
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۴ — دیپلوی
|
||||
## مرحله ۵ — دیپلوی
|
||||
|
||||
روی **Deploy** بزن. در اولین استارت بهصورت خودکار اینها اتفاق میافتد (`entrypoint.sh` + `RUN_INIT=1` روی سرویس `app`):
|
||||
|
||||
1. مالکیت `var`, `public/uploads`, `config/jwt` به `www-data` داده میشود.
|
||||
2. کلید JWT اگر روی ولوم نباشد ساخته میشود (`--skip-if-exists`).
|
||||
3. کش prod پاک و warmup میشود.
|
||||
4. مهاجرتهای DB با `--all-or-nothing` اعمال میشوند (ترنزکشن).
|
||||
2. منتظر آمادهشدن DB مستقل میماند (تا ~۶۰ ثانیه؛ چون دیگر `depends_on: service_healthy` نیست).
|
||||
3. کلید JWT اگر روی ولوم نباشد ساخته میشود (`--skip-if-exists`).
|
||||
4. کش prod پاک و warmup میشود.
|
||||
5. مهاجرتهای DB با `--all-or-nothing` اعمال میشوند (ترنزکشن).
|
||||
|
||||
> ورکرها `RUN_INIT=0` دارند تا مهاجرت/تولید کلید با هم تداخل نکنند.
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۵ — پس از اولین دیپلوی
|
||||
## مرحله ۶ — پس از اولین دیپلوی
|
||||
|
||||
### ساخت اولین ادمین
|
||||
|
||||
@@ -151,7 +176,7 @@ push روی برنچ متصل (یا Deploy دستی). در هر ریدیپل
|
||||
|
||||
- ایمیج دوباره build میشود (vendor + اسمبل فرانتاند multi-stage).
|
||||
- مهاجرتهای جدید روی استارت `app` اعمال میشوند.
|
||||
- ولومها حفظ میشوند (DB، آپلودها، کلیدهای JWT، Redis سالم میمانند).
|
||||
- ولومهای استک اپ (آپلودها، کلیدهای JWT) حفظ میشوند. دادهی MariaDB/Redis در Resourceهای مستقل مستقل از این ریدیپلوی سالم میماند.
|
||||
|
||||
---
|
||||
|
||||
@@ -160,7 +185,8 @@ push روی برنچ متصل (یا Deploy دستی). در هر ریدیپل
|
||||
| نشانه | علت محتمل |
|
||||
|---|---|
|
||||
| ارورهای CORS در فرانت | `CORS_ALLOW_ORIGIN` با دامنه نمیخواند؛ از `gen-cors-env.php` بازتولید کن |
|
||||
| `app` بالا نمیآید، منتظر DB میماند | healthcheck `mariadb` رد نشده؛ لاگ mariadb را ببین |
|
||||
| `app` با «waiting for database...» میماند و بعد ۶۰ ثانیه میمیرد | اپ به Resource مستقل MariaDB نمیرسد؛ چک کن: «Connect to Predefined Network» فعال است، hostname در `DATABASE_URL` درست (`mariadb-<uuid>`) و رمز/نسخهی Resource درست است |
|
||||
| `redis` در دسترس نیست / صف کار نمیکند | `REDIS_URL`/`MESSENGER_TRANSPORT_DSN` به `redis-<uuid>` درست اشاره نمیکند یا رمز جا افتاده |
|
||||
| ۴۰۱/توکن نامعتبر بعد از ریدیپلوی | `JWT_PASSPHRASE` تغییر کرده یا ولوم `jwt_keys` پاک شده |
|
||||
| IPها/HTTPS اشتباه پشت پراکسی | `TRUSTED_PROXIES` ست نشده |
|
||||
| مهاجرت اجرا نشد | فقط `app` با `RUN_INIT=1` اجرا میکند؛ مطمئن شو override نشده |
|
||||
|
||||
Reference in New Issue
Block a user