Files
clinicpro/.claude/prompt/coolify-docker-production-hardening.md

160 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# سخت‌سازی و بازبینی 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-<uuid>`/`redis-<uuid>` وصل می‌شود. هیچ `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