Files
clinicpro/.claude/prompt/coolify-symfony-deploy.md
T

14 KiB
Raw Blame History

آماده‌سازی پروژه ClinicPro برای دیپلوی روی Coolify (Symfony + Nixpacks)

زمینه

پروژه قرار است روی Coolify با Build Pack = Nixpacks دیپلوی شود (طبق https://coolify.io/docs/applications/symfony). در حال حاضر پروژه فقط برای محیط لوکال ddev پیکربندی شده است (compose.yaml، .ddev/). برای Coolify نیاز به این موارد داریم که هیچ‌کدام الان وجود ندارند:

  1. کنترل کامل بر فرایند build — چون این پروژه علاوه بر PHP یک فرانت‌اند React 19 + Webpack Encore دارد که باید با yarn build کامپایل شود (Nixpacks پیش‌فرض Symfony فقط PHP را build می‌کند و دارایی‌های public/build/ ساخته نمی‌شوند).
  2. تولید کلیدهای JWT (lexik) هنگام دیپلوی — این کلیدها در .gitignore هستند و در ریپو نیستند.
  3. اجرای migrations بعد از هر دیپلوی.
  4. اجرای workerهای Messenger:
    • messenger:consume async → ارسال SMS
    • messenger:consume scheduler_default → انقضای نوبت‌های پرداخت‌نشده (هر ۱ دقیقه)
  5. تنظیم Trusted Proxies (چون Coolify پشت Traefik/reverse-proxy است؛ بدون آن https، IP کلاینت و کوکی‌ها درست کار نمی‌کنند).
  6. یک فایل .env.coolify.example که همهٔ متغیرهای محیطی لازم را برای وارد کردن در داشبورد Coolify فهرست کند.

نکتهٔ مهم معماری: دیتابیس پروژه MariaDB/MySQL است (نه PostgreSQL که داک Coolify مثال می‌زند). Redis برای Messenger و OTP استفاده می‌شود.

هدف

پروژه را طوری «coolify-ready» کن که با Build Pack = Nixpacks و چند متغیر محیطی، بدون تغییر در منطق برنامه، روی Coolify بالا بیاید — شامل build فرانت‌اند، تولید کلید JWT، migration، و راه‌اندازی worker‌ها.

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

فایل نقش وضعیت
nixpacks.toml کنترل فازهای build/install/start برای Nixpacks باید ساخته شود
config/packages/framework.yaml افزودن trusted_proxies و trusted_headers باید ویرایش شود
.env.coolify.example فهرست کامل env برای داشبورد Coolify باید ساخته شود
docs/deploy/coolify.md راهنمای گام‌به‌گام دیپلوی باید ساخته شود
.env.example مرجع موجود متغیرها (فقط خواندن) بدون تغییر
package.json اسکریپت build = encore production بدون تغییر
config/packages/messenger.yaml تعریف transport های async و scheduler_default بدون تغییر (مرجع worker)

وضعیت فعلی

  • package.json اسکریپت build دارد: "build": "encore production --progress".
  • خروجی build در public/build/ می‌رود (در .gitignore است → باید حین دیپلوی ساخته شود).
  • کلیدهای JWT در config/jwt/*.pem (در .gitignore).
  • framework.yaml فعلی هیچ trusted_proxies ندارد:
framework:
    secret: '%env(APP_SECRET)%'
    session: true
  • متغیرهای کلیدی از .env.example: APP_ENV, APP_SECRET, DATABASE_URL (mysql), JWT_*, CORS_ALLOW_ORIGIN, MESSENGER_TRANSPORT_DSN (redis), REDIS_URL, API_IR_*, APP_BASE_URL, ALLOWED_FRONTEND_HOSTS, DEFAULT_URI, MAX_FILE_SIZE_BYTES, UPLOAD_DIR.

وظایف

۱. ساخت nixpacks.toml در ریشهٔ پروژه

هدف: علاوه بر نصب وابستگی‌های PHP، فرانت‌اند را هم build کن و کلید JWT بساز. Nixpacks باید هم PHP و هم Node/Yarn را در محیط build داشته باشد.

# nixpacks.toml — کنترل دیپلوی Coolify
[phases.setup]
nixPkgs = ["php82", "php82Packages.composer", "nodejs_20", "yarn", "openssl"]

[phases.install]
cmds = [
  "composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist",
  "yarn install --frozen-lockfile"
]

[phases.build]
# build دارایی‌های فرانت‌اند (React/Encore) → public/build
# و تولید کلید JWT اگر وجود نداشته باشد (idempotent)
cmds = [
  "yarn build",
  "php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction"
]

[start]
# Nixpacks خودش php-fpm/nginx را با NIXPACKS_PHP_ROOT_DIR اجرا می‌کند؛
# این بخش را فقط در صورت نیاز override کن. در حالت عادی خالی بماند.

نکات:

  • نسخهٔ php82 باید با "php": ">=8.2" در composer.json هم‌خوان باشد. اگر Nixpacks نسخهٔ PHP را خودکار از composer تشخیص می‌دهد، می‌توان php را از nixPkgs حذف کرد — ولی صریح بودن امن‌تر است.
  • lexik:jwt:generate-keypair --skip-if-exists باعث می‌شود اگر کلید از قبل (مثلاً از volume) وجود داشته باشد، دوباره ساخته نشود. هشدار را در docs ذکر کن: اگر کلید JWT بین دیپلوی‌ها persist نشود، همهٔ توکن‌های صادرشده باطل می‌شوند → بهتر است JWT_SECRET_KEY/JWT_PUBLIC_KEY به‌صورت محتوای base64 در env داده شوند یا روی volume مانت شوند. هر دو گزینه را در docs توضیح بده.
  • اگر JWT_PASSPHRASE در env تنظیم نشده باشد، تولید کلید fail می‌شود — این را در docs قید کن.

۲. افزودن Trusted Proxies به config/packages/framework.yaml

Coolify پشت Traefik است. بدون این تنظیم، Symfony پروتکل https و IP واقعی را تشخیص نمی‌دهد.

framework:
    secret: '%env(APP_SECRET)%'
    session: true

    # Coolify/Traefik reverse proxy
    trusted_proxies: '%env(TRUSTED_PROXIES)%'
    trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port']

و در .env.coolify.example مقدار پیش‌فرض امن بده (طبق داک Coolify که از REMOTE_ADDR یا رنج شبکهٔ داکر استفاده می‌شود):

# IP/رنج reverse proxy؛ برای Coolify معمولاً رنج شبکهٔ داخلی داکر یا 'REMOTE_ADDR'
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1

نکته: اگر TRUSTED_PROXIES خالی بماند، Symfony خطا نمی‌دهد ولی پشت پراکسی درست کار نمی‌کند؛ مقدار پیش‌فرض را در env بگذار تا اگر کاربر فراموش کرد، رفتار منطقی باشد. اگر ترجیح می‌دهی fallback داشته باشی از '%env(default::TRUSTED_PROXIES)%' با مقدار پیش‌فرض در .env استفاده کن.

۳. ساخت .env.coolify.example

فهرست کامل و دسته‌بندی‌شدهٔ تمام متغیرهایی که باید در داشبورد Coolify (Environment Variables) وارد شوند. از روی .env.example بساز ولی این موارد Coolify-specific را اضافه کن:

# ───── Coolify / Nixpacks (الزامی طبق داک Coolify) ─────
APP_ENV=prod
APP_DEBUG=0
NIXPACKS_PHP_FALLBACK_PATH=/index.php
NIXPACKS_PHP_ROOT_DIR=/app/public
# Port Exposure را در UI روی 80 بگذار

# ───── Symfony Core ─────
APP_SECRET=        # php -r "echo bin2hex(random_bytes(32));"
DEFAULT_URI=https://your-domain.com
APP_BASE_URL=https://your-domain.com
ALLOWED_FRONTEND_HOSTS=your-domain.com

# ───── Reverse Proxy (Coolify/Traefik) ─────
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1

# ───── Database (MariaDB/MySQL service در Coolify) ─────
DATABASE_URL="mysql://USER:PASS@HOST:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"

# ───── JWT (lexik) ─────
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=        # الزامی برای تولید کلید در فاز build

# ───── CORS ─────
CORS_ALLOW_ORIGIN='^https://your-domain\.com$'

# ───── Messenger / Redis (Redis service در Coolify) ─────
MESSENGER_TRANSPORT_DSN=redis://HOST:6379/messages
REDIS_URL=redis://HOST:6379

# ───── Auth / OTP ─────
REFRESH_TOKEN_TTL=2592000
OTP_TTL=1200

# ───── api.ir (استعلام هویت) ─────
API_IR_BASE_URL=https://s.api.ir
API_IR_TOKEN=

# ───── File Upload ─────
MAX_FILE_SIZE_BYTES=5242880
UPLOAD_DIR=var/uploads

نکات مهم برای این فایل:

  • serverVersion در DATABASE_URL را با MariaDB هم‌خوان کن (mariadb-11.8.0)، نه 8.0، چون پروژه روی MariaDB 11.8 است.
  • توضیح بده که HOST در DSNها = نام internal service که Coolify برای DB/Redis می‌دهد.
  • کلیدهای SMS و درگاه پرداخت از DB («تنظیمات سایت») خوانده می‌شوند، نه env — این را با کامنت ذکر کن.

۴. ساخت راهنمای docs/deploy/coolify.md

یک سند گام‌به‌گام فارسی شامل:

  1. تنظیمات Coolify UI:
    • Build Pack = nixpacks
    • Port Exposure = 80
    • Health Check Path = /api/doc یا یک endpoint سبک (بررسی کن چه endpoint عمومی‌ای برای healthcheck مناسب است؛ از config/packages/security.yaml لیست public_endpoints را ببین).
  2. Environment Variables: ارجاع به .env.coolify.example + متغیرهای الزامی Nixpacks.
  3. Post-Deployment Command (در بخش مربوطهٔ Coolify):
    php bin/console doctrine:migrations:migrate --all-or-nothing --no-interaction
    
  4. Persistent Storage (Volumes) — این بخش حیاتی است، حتماً توضیح بده:
    • config/jwt/ → تا کلیدهای JWT بین دیپلوی‌ها باقی بمانند (در غیر این صورت همهٔ لاگین‌ها باطل می‌شوند).
    • public/uploads/ و var/uploads/ → فایل‌های آپلودی کاربران.
    • var/log/ (اختیاری).
  5. Worker‌ها (سرویس‌های جداگانه در Coolify): توضیح بده که باید دو سرویس/کانتینر اضافه با همان image ولی start command متفاوت ساخته شوند، یا از Supervisor در همان کانتینر استفاده شود:
    • php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M
    • php bin/console messenger:consume scheduler_default --time-limit=3600 گزینهٔ توصیه‌شده را مشخص کن (در Coolify معمولاً سرویس جداگانه تمیزتر است؛ یا یک supervisord.conf در همان کانتینر).
  6. اولین راه‌اندازی: ساخت admin اولیه:
    php bin/console app:create-admin
    

۵. (اختیاری ولی توصیه‌شده) فایل supervisord.conf برای worker‌ها

اگر تصمیم گرفتی worker‌ها در همان کانتینر اجرا شوند، یک supervisord.conf بساز که هر دو consumer را مدیریت کند و در nixpacks.toml به phase setup اضافه‌اش کن. در غیر این صورت، در docs گزینهٔ «سرویس جداگانه در Coolify» را به‌عنوان روش پیش‌فرض توضیح بده و این فایل را نساز.

تصمیم پیشنهادی: سرویس جداگانه در Coolify (ساده‌تر، بدون نیاز به supervisor و بدون پیچیده کردن کانتینر اصلی). فقط در docs مستند کن.

نکات مهم (محدودیت‌ها و edge caseها)

  • MariaDB نه PostgreSQL: داک رسمی Coolify مثال PostgreSQL می‌زند؛ این پروژه MySQL/MariaDB است. همه‌جا DSN را mysql://... با serverVersion=mariadb-11.8.0 نگه دار.
  • build فرانت‌اند الزامی است: بدون yarn build، صفحات admin (و دارایی‌های React) لود نمی‌شوند چون public/build/ خالی می‌ماند. این تفاوت اصلی با setup پیش‌فرض Symfony در داک Coolify است.
  • JWT keypair persistence: اگر volume برای config/jwt/ تعریف نشود، هر دیپلوی کلید جدید می‌سازد و کاربران logout می‌شوند. این مهم‌ترین نکتهٔ عملیاتی است — حتماً در docs برجسته شود.
  • JWT_PASSPHRASE باید قبل از build در env باشد وگرنه lexik:jwt:generate-keypair در فاز build شکست می‌خورد.
  • Scheduler: transport scheduler_default نوبت‌های پرداخت‌نشده را هر دقیقه منقضی می‌کند؛ اگر worker آن اجرا نشود، نوبت‌های رزرو ولی پرداخت‌نشده آزاد نمی‌شوند. این رفتار را در docs ذکر کن.
  • هیچ منطق برنامه‌ای را تغییر نده — فقط فایل‌های infra/config اضافه یا ویرایش شوند.
  • بدون تغییر در ddev: فایل‌های compose.yaml و .ddev/ دست‌نخورده بمانند تا محیط لوکال نشکند.
  • بعد از تغییر framework.yaml، ddev exec php bin/console cache:clear بزن تا مطمئن شوی config معتبر است.
  • این تغییرات API را عوض نمی‌کنند، پس نیازی به به‌روزرسانی docs/api/* نیست؛ ولی فایل جدید docs/deploy/coolify.md اضافه می‌شود.