# سخت‌سازی و بازبینی Production استک Docker برای Coolify (MariaDB/Redis مستقل) ## پروژه `clinicpro` (backend / deploy / infra) ## ⚠️ قانون شماره ۱ — اول مستندات، بعد کد قبل از هرگونه تغییر در فایل‌های Docker، **مستندات رسمی Coolify را با WebFetch بخوان** و هر تصمیم را بر پایه‌ی آن‌ها بگیر، نه دانش قبلی. دستِ‌کم این صفحات: - https://coolify.io/docs/get-started/introduction - https://coolify.io/docs/applications/build-packs/docker-compose - https://coolify.io/docs/knowledge-base/docker/compose - https://coolify.io/docs/databases/mariadb - https://coolify.io/docs/databases/redis موضوعاتی که باید از داک تأیید شوند (نه از حافظه): Docker Compose Build Pack، **Connect to Predefined Network**، Environment Variables، Healthcheck، Persistent Storage (Volumes)، Reverse Proxy/Traefik، Domains، Resource Limits، Raw Docker Compose. اگر دانش قبلی با داک تناقض داشت → **داک ملاک است**. هر بخش که در داک پیدا نشد، در گزارش ذکر کن که «در داک نبود، بر اساس best practice تصمیم گرفته شد». ## زمینه استک دیپلوی این پروژه قبلاً ساخته و چند بار روی Coolify دیپلوی شده و چند باگ رفع شده (node20→22، FPM clear_env، preload perms، باگ‌های migration روی DB خالی، codeload flaky، نبود `symfony/redis-messenger`، اتصال به DB/Redis مستقل، healthcheck). این تسک یک **بازبینی کامل و سخت‌سازی** استکِ موجود است تا کاملاً با مستندات Coolify هم‌خوان شود و در دیپلوی خطا ندهد — **نه ساخت از صفر**. فایل‌های فعلی را بهبود بده، دوباره‌سازی نکن. معماری تثبیت‌شده: **MariaDB و Redis، Database Resourceهای مستقل Coolify هستند** (نه داخل compose). اپ از طریق شبکه‌ی predefined (`coolify`) و hostname `mariadb-`/`redis-` وصل می‌شود. هیچ `localhost` و هیچ hardcode. ## وضعیت فعلی پروژه (واقعی — بررسی شده) | واقعیت | مقدار | |---|---| | PHP | `>=8.2` (image: `php:8.2-fpm-alpine`) | | Symfony | `7.4.*` | | اکستنشن‌های نصب‌شده | `pdo_mysql`, `intl`, `opcache`, `redis` (phpredis 6.1.0 از سورس GitHub) | | اکستنشن‌های لازمِ composer | فقط `ext-ctype`, `ext-iconv` (هر دو bundled) | | Symfony cache adapter | `cache.adapter.redis` با `%env(REDIS_URL)%` (config/packages/cache.yaml) | | Session | `session.storage.factory.mock_file` (فایلی، نه redis) | | Messenger transports | `async` (redis، نیاز `symfony/redis-messenger` ✔ نصب شد)، `scheduler_default`، `failed` (doctrine) | | سرویس‌ها | `app` (RUN_INIT=1) + `worker-async` + `worker-scheduler`؛ image مشترک `clinicpro-app:latest` | | شبکه | `networks: [coolify]` external روی هر ۳ سرویس | | Healthcheck فعلی | app: `php get_headers /health`؛ workerها: `ps | grep messenger:consume` | | کاربر اجرا | **root** (supervisord `user=root`, بدون `USER` در Dockerfile) ⚠️ | | APCu | **نصب نیست** (cache از redis است؛ APCu فقط برای کش لوکال opcache/symfony مفید) | | Graceful shutdown | supervisord PID 1، بدون `STOPSIGNAL`/`stopwaitsecs` تنظیم‌شده ⚠️ | ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `clinicpro/Dockerfile` | multi-stage (vendor→assets→runtime)؛ نصب اکستنشن، perms، entrypoint | | `clinicpro/docker-compose.yml` | استک Coolify (app + 2 worker)، networks، healthcheck | | `clinicpro/.dockerignore` | استثناها | | `clinicpro/docker/entrypoint.sh` | wait-for-DB، JWT keygen، cache، migrate (فقط RUN_INIT=1) | | `clinicpro/docker/supervisord.conf` | php-fpm + nginx | | `clinicpro/docker/php/php.ini` | opcache + perf | | `clinicpro/docker/php/zz-pool.conf` | FPM pool: `clear_env = no` | | `clinicpro/docker/nginx/default.conf` | nginx | | `clinicpro/src/Shared/Controller/HealthController.php` | route `/health` | | `clinicpro/docs/DEPLOY.md` | راهنمای دیپلوی | | `clinicpro/.env.coolify.example` | نمونه env | ## وظایف > هر وظیفه: اول داکِ مرتبط را بخوان، بعد اعمال کن، بعد با `docker buildx build --platform linux/amd64` تست کن. هیچ مقدار hardcode، فقط `${VAR}`. هیچ custom network جز `coolify` external. ### ۱. اجرای غیر-root (Security Hardening) الان کانتینر root اجرا می‌شود. باید non-root شود **بدون شکستن** preload (که الان `opcache.preload_user = www-data` است) و nginx/fpm. - در `supervisord.conf`، php-fpm pool از قبل به‌صورت www-data اجرا می‌شود؛ ولی master supervisord و nginx master root هستند. بررسی کن آیا می‌توان کل استک را با `USER www-data` اجرا کرد یا nginx master نیاز root دارد (برای bind پورت ۸۰). - اگر nginx به پورت ۸۰ نیاز root دارد: یا nginx را روی پورت **8080 (non-privileged)** ببر و در compose/healthcheck و Coolify port را ۸۰۸۰ کن (Traefik به هر پورتی روت می‌کند)، سپس `USER www-data` در Dockerfile + `user=www-data` در supervisord. - مطمئن شو `var/cache`, `var/log`, `var/uploads`, `public/uploads`, `config/jwt` برای www-data نوشتنی‌اند (chown موجود است؛ تکمیلش کن). - داک Coolify درباره‌ی port/Traefik را بخوان تا مطمئن شوی تغییر پورت به ۸۰۸۰ مشکلی ایجاد نمی‌کند. > اگر non-root باعث ریسک شکستن شد و در زمان محدود قابل‌اطمینان نبود، حداقل nginx/fpm worker‌ها را non-root نگه دار (الان هستند) و در گزارش توضیح بده چرا master root ماند. ### ۲. healthcheck.sh مستقل + بازبینی healthcheckها پرامپت یک `docker/healthcheck.sh` می‌خواهد. به‌جای دستور inline طولانی در compose: - یک `docker/healthcheck.sh` بساز که `/health` را چک کند (با `php -r` get_headers یا اگر در image موجود است `curl -fsS`؛ از روی Dockerfile مطمئن شو کدام در runtime هست — `curl` در build استفاده شده ولی شاید در runtime نمانده باشد، چک کن). - در Dockerfile کپی + executable کن، در `docker-compose.yml` healthcheck سرویس `app` را به `["CMD","/usr/local/bin/healthcheck.sh"]` تغییر بده. - healthcheckهای worker (ps grep) را نگه دار یا به اسکریپت منتقل کن. - مقادیر `interval/timeout/retries/start_period` را با توصیه‌ی داک Coolify (اگر دارد) هم‌سو کن. ### ۳. opcache.ini جدا + بازبینی perf‌ها پرامپت `opcache.ini` جدا می‌خواهد. الان opcache داخل `php.ini` است. - بخش opcache را از `php.ini` به یک `docker/php/opcache.ini` منتقل کن و در Dockerfile جداگانه کپی کن (`/usr/local/etc/php/conf.d/`). تنظیمات فعلی opcache دست‌نخورده بماند (preload, validate_timestamps=0, ...). - بررسی کن `realpath_cache`, `memory_limit`, `upload_max_filesize`/`post_max_size` (الان 16M) با `MAX_FILE_SIZE_BYTES=5242880` (۵MB در env) هم‌خوان است — اگر آپلود تا ۵MB است، 16M کافی است، نگه دار. ### ۴. APCu — تصمیم مستند - چون Symfony cache از **redis** است نه apcu، نصب APCu **اجباری نیست**. ولی اگر می‌خواهی کش متادیتای Doctrine/annotation روی apcu لوکال باشد (سریع‌تر از redis برای read-heavy)، APCu را با همان روش source (یا `docker-php-ext-...`/pecl-free) اضافه کن. - تصمیم را در گزارش مستند کن: یا «APCu لازم نیست چون cache=redis» یا «اضافه شد برای X». hardcode نکن؛ اگر اضافه شد، در runtime enable شود. ### ۵. Graceful shutdown / signal handling - supervisord را برای shutdown تمیز تنظیم کن: `stopsignal`, `stopwaitsecs` برای php-fpm/nginx، و مطمئن شو سیگنال SIGTERM از داکر به فرزندها می‌رسد. - workerها (`messenger:consume`) به SIGTERM پاسخ می‌دهند (Symfony Messenger خودش graceful است با `--time-limit`)؛ مطمئن شو داکر مستقیم پروسه‌ی consume را اجرا می‌کند (الان `command: php bin/console messenger:consume ...` مستقیم است ✔) و `stop_grace_period` در compose برای workerها کافی است (پیش‌فرض 10s؛ شاید 30s بهتر باشد تا پیام در حال پردازش تمام شود). ### ۶. Resource limits (طبق داک Coolify) - داک Coolify درباره‌ی Resource Limits را بخوان. اگر در compose پشتیبانی می‌شود (`deploy.resources.limits` در سطح Coolify یا UI)، توصیه‌ی mem/cpu برای `app` و workerها را در `docs/DEPLOY.md` مستند کن. اگر Coolify limits را از UI می‌گیرد نه compose، فقط در داک ذکر کن. ### ۷. بازبینی نهایی هم‌خوانی با Coolify + به‌روزرسانی DEPLOY.md - مطمئن شو هیچ `localhost`/`127.0.0.1` برای DB/Redis نیست (همه از `${DATABASE_URL}`/`${REDIS_URL}`/`${MESSENGER_TRANSPORT_DSN}`). - مطمئن شو فقط شبکه‌ی `coolify` external است و هر ۳ سرویس به آن وصل‌اند. - `docs/DEPLOY.md` را با هر تغییر (پورت non-root، healthcheck.sh، resource limits) به‌روز کن. - چک‌لیست نهایی را در انتهای گزارش بده (پایین). ## تست (اجباری — بعد از تغییرات) روی **linux/amd64** (معماری Coolify): ```bash # build کامل docker buildx build --platform linux/amd64 --target runtime --load -t clinicpro-test -f Dockerfile . # اکستنشن‌ها لود؟ docker run --rm --platform linux/amd64 --entrypoint php clinicpro-test -m | grep -iE 'pdo_mysql|intl|redis|opcache|apcu' # non-root؟ docker run --rm --platform linux/amd64 --entrypoint id clinicpro-test # boot prod بدون خطا (با env ساختگی) docker run --rm --platform linux/amd64 -e APP_ENV=prod -e APP_DEBUG=0 -e APP_SECRET=x \ -e DATABASE_URL="mysql://u:p@127.0.0.1:3306/d?serverVersion=mariadb-11.8.0" \ -e JWT_PASSPHRASE=x -e CORS_ALLOW_ORIGIN='^x$' -e APP_BASE_URL=http://x \ -e ALLOWED_FRONTEND_HOSTS=x -e TRUSTED_PROXIES=10.0.0.0/8 -e API_IR_BASE_URL=x -e API_IR_TOKEN= \ -e MESSENGER_TRANSPORT_DSN=redis://127.0.0.1:6379/m -e REDIS_URL=redis://127.0.0.1:6379 \ --entrypoint php clinicpro-test bin/console about | grep -iE 'environment|debug' # اتصال واقعی DB+Redis مستقل + migration + /health (مثل سشن‌های قبل: کانتینر mariadb+redis روی یک docker network بساز، # سپس app را با DATABASE_URL/REDIS_URL به آن‌ها وصل کن، RUN_INIT=1، و /health را curl کن) # اعتبار YAML docker compose config # با env ساختگی ``` موارد ۱..۱۴ پرامپت اصلی (healthcheck، logها، اتصال DB/Redis، cache، migration، response، perms، restart، graceful shutdown، production mode) را با همین روش پوشش بده. هر مشکل را همان‌جا رفع کن. ## نکات مهم - **هیچ MariaDB/Redis در compose** — مستقل‌اند. compose فقط `app` + ۲ worker. - **هیچ custom network** جز `coolify` (external) — طبق داک، شبکه‌ی سفارشی routing Traefik را می‌شکند. - **هیچ مقدار hardcode** — همه `${VAR}`؛ اسرار از تب Coolify. هیچ پسورد/توکن در repo یا image. - **logها روی stdout/stderr** — supervisord الان همین کار را می‌کند (`/dev/stdout`)؛ حفظ کن. - `serverVersion=mariadb-11.8.0` در `DATABASE_URL` و نسخه‌ی Resource مستقل MariaDB باید یکی باشند. - فقط سرویس `app` (`RUN_INIT=1`) migration می‌زند؛ workerها نباید (race). - image مشترک `clinicpro-app:latest` (app build، workerها reuse) حفظ شود — خرابش نکن. - entrypoint فعلی wait-for-DB دارد (`doctrine:query:sql "SELECT 1"`)؛ اگر چیزی شکستی، آن را نگه‌دار. - تغییر صرفاً infra است؛ هیچ controller/Entity عوض نمی‌شود، پس `docs/api/*` نیاز به آپدیت ندارد — فقط `docs/DEPLOY.md`. - بعد از اتمام، **چک‌لیست نهایی** بده: - ✅ Build موفق · ✅ Image Production Ready · ✅ Coolify Compatible · ✅ Compose Compatible - ✅ Connect to Predefined Network · ✅ MariaDB External · ✅ Redis External - ✅ Security (non-root یا توضیح) · ✅ Performance (opcache/fpm/nginx) · ✅ Healthcheck معتبر - ✅ Zero Hardcoded Secret · ✅ Ready for Deployment