merge: Coolify production hardening (non-root, healthcheck, opcache, graceful shutdown)
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# سختسازی و بازبینی 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
|
||||
+20
-3
@@ -85,19 +85,36 @@ RUN [ -f .env ] || printf 'APP_ENV=prod\nAPP_DEBUG=0\n' > .env
|
||||
|
||||
# Container configuration.
|
||||
COPY docker/php/php.ini /usr/local/etc/php/conf.d/zz-app.ini
|
||||
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/zz-opcache.ini
|
||||
COPY docker/php/zz-pool.conf /usr/local/etc/php-fpm.d/zz-pool.conf
|
||||
COPY docker/nginx/default.conf /etc/nginx/http.d/default.conf
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh \
|
||||
COPY docker/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Make nginx run as the non-root www-data user:
|
||||
# - drop the `user nginx;` directive (ignored + warns when master isn't root)
|
||||
# - put the pid file in a www-data-writable path (default /run/nginx is root-only)
|
||||
RUN sed -i '/^user /d' /etc/nginx/nginx.conf \
|
||||
&& sed -i '1i pid /tmp/nginx.pid;' /etc/nginx/nginx.conf
|
||||
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/healthcheck.sh \
|
||||
&& mkdir -p var/cache var/log var/uploads public/uploads config/jwt \
|
||||
# Source may be copied with restrictive (0600) host perms; ensure the runtime
|
||||
# user (www-data) can read all app files — opcache preload runs as www-data
|
||||
# and otherwise fails with "Permission denied" on /app/config/preload.php.
|
||||
&& chmod -R a+rX /app \
|
||||
&& chown -R www-data:www-data var public/uploads config/jwt
|
||||
&& chown -R www-data:www-data var public/uploads config/jwt \
|
||||
# nginx (run as www-data) needs to write its temp/cache/log dirs.
|
||||
&& chown -R www-data:www-data /var/lib/nginx /var/log/nginx 2>/dev/null || true
|
||||
|
||||
EXPOSE 80
|
||||
# Drop privileges: the entrypoint, supervisord, php-fpm and nginx all run as
|
||||
# www-data. nginx binds 8080 (non-privileged) so root is never required.
|
||||
USER www-data
|
||||
|
||||
EXPOSE 8080
|
||||
HEALTHCHECK --interval=15s --timeout=5s --retries=5 --start-period=60s \
|
||||
CMD ["/usr/local/bin/healthcheck.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
|
||||
+6
-4
@@ -64,11 +64,9 @@ services:
|
||||
RUN_INIT: "1" # runs JWT keygen + migrations on start (only this service)
|
||||
volumes: *app-volumes
|
||||
networks: [coolify]
|
||||
# Hit the real /health route through nginx+fpm — verifies the app actually
|
||||
# boots and serves (not just that port 80 is open). Uses PHP (always present)
|
||||
# so we don't depend on curl/wget being in the image; fails on non-2xx.
|
||||
# Hit the real /health route (nginx on 8080, non-root) via docker/healthcheck.sh.
|
||||
healthcheck:
|
||||
test: ["CMD", "php", "-r", "$h=@get_headers('http://127.0.0.1:80/health'); exit($h && strpos($h[0],'200')!==false ? 0 : 1);"]
|
||||
test: ["CMD", "/usr/local/bin/healthcheck.sh"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -85,6 +83,9 @@ services:
|
||||
volumes: *app-volumes
|
||||
networks: [coolify]
|
||||
depends_on: [app]
|
||||
# Messenger consumers handle SIGTERM gracefully — give the in-flight message
|
||||
# time to finish before the container is killed.
|
||||
stop_grace_period: 30s
|
||||
# No port to probe — just confirm the consumer process is alive (busybox ps).
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps -o args 2>/dev/null | grep -q '[m]essenger:consume async' || exit 1"]
|
||||
@@ -104,6 +105,7 @@ services:
|
||||
volumes: *app-volumes
|
||||
networks: [coolify]
|
||||
depends_on: [app]
|
||||
stop_grace_period: 30s
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps -o args 2>/dev/null | grep -q '[m]essenger:consume scheduler_default' || exit 1"]
|
||||
interval: 30s
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Container healthcheck — hits the real Symfony /health route through nginx+fpm,
|
||||
# so a green status means the app actually boots and serves (not just that the
|
||||
# port is open). Uses PHP (always present in the image) to avoid depending on
|
||||
# curl/wget. nginx listens on 8080 (non-root). Exit 0 = healthy, 1 = unhealthy.
|
||||
exec php -r '$h=@get_headers("http://127.0.0.1:8080/health"); exit($h && strpos($h[0]," 200")!==false ? 0 : 1);'
|
||||
@@ -1,5 +1,8 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
# Non-privileged port so the whole stack can run as www-data (non-root).
|
||||
# Coolify/Traefik routes to whatever port the service exposes — assign 8080
|
||||
# as the service port in Coolify.
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
root /app/public;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
; OPcache — production tuning for ClinicPro.
|
||||
; validate_timestamps=0: code never changes at runtime in an immutable image, so
|
||||
; skip stat() checks for max throughput. (A new deploy = a new image.)
|
||||
; preload: warms the Symfony container/classes into shared memory at FPM start;
|
||||
; runs as www-data, so app files must be readable by www-data (see Dockerfile).
|
||||
opcache.enable = 1
|
||||
opcache.enable_cli = 0
|
||||
opcache.memory_consumption = 256
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.validate_timestamps = 0
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.preload = /app/config/preload.php
|
||||
opcache.preload_user = www-data
|
||||
@@ -6,16 +6,6 @@ max_execution_time = 60
|
||||
expose_php = Off
|
||||
date.timezone = Asia/Tehran
|
||||
|
||||
; OPcache (production)
|
||||
opcache.enable = 1
|
||||
opcache.enable_cli = 0
|
||||
opcache.memory_consumption = 256
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.validate_timestamps = 0
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.preload = /app/config/preload.php
|
||||
opcache.preload_user = www-data
|
||||
|
||||
; Realpath cache (perf)
|
||||
realpath_cache_size = 4096k
|
||||
realpath_cache_ttl = 600
|
||||
|
||||
+18
-2
@@ -1,14 +1,28 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
# Run the whole stack as the non-root www-data user (security hardening).
|
||||
# nginx listens on 8080 (non-privileged) so root is not needed to bind the port.
|
||||
user=www-data
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
pidfile=/run/supervisord.pid
|
||||
pidfile=/tmp/supervisord.pid
|
||||
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm -F
|
||||
autorestart=true
|
||||
priority=10
|
||||
# Forward SIGTERM (graceful) and give workers time to finish in-flight requests.
|
||||
stopsignal=TERM
|
||||
stopwaitsecs=15
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
@@ -18,6 +32,8 @@ stderr_logfile_maxbytes=0
|
||||
command=nginx -g 'daemon off;'
|
||||
autorestart=true
|
||||
priority=20
|
||||
stopsignal=QUIT
|
||||
stopwaitsecs=15
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
|
||||
+17
-3
@@ -12,7 +12,7 @@
|
||||
|
||||
| سرویس | نقش | نکته |
|
||||
|---|---|---|
|
||||
| `app` | PHP-FPM + Nginx (وب) | تنها سرویسی که `RUN_INIT=1` دارد؛ مهاجرت DB و تولید کلید JWT را اجرا میکند. دامنهها را به این سرویس (پورت 80) وصل کن. |
|
||||
| `app` | PHP-FPM + Nginx (وب، non-root، پورت ۸۰۸۰) | تنها سرویسی که `RUN_INIT=1` دارد؛ مهاجرت DB و تولید کلید JWT را اجرا میکند. دامنهها را به این سرویس (پورت ۸۰۸۰) وصل کن. |
|
||||
| `worker-async` | مصرفکننده صف async (SMS و کارهای async) | `messenger:consume async` |
|
||||
| `worker-scheduler` | زمانبند | هر ۱ دقیقه نوبتهای پرداختنشده را منقضی میکند |
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
|
||||
## مرحله ۳ — دامنهها
|
||||
|
||||
همهی دامنههای سرو شونده را به سرویس **`app`** (پورت 80) اختصاص بده — هم دامنهی API و هم همهی دامنههای فرانتاند. Coolify لیست دامنهی جداشده با کاما را روی یک سرویس قبول میکند و TLS را خودش صادر میکند.
|
||||
همهی دامنههای سرو شونده را به سرویس **`app`** اختصاص بده — هم دامنهی API و هم همهی دامنههای فرانتاند. Coolify لیست دامنهی جداشده با کاما را روی یک سرویس قبول میکند و TLS را خودش صادر میکند.
|
||||
|
||||
> ⚠️ **پورت سرویس = `8080`** (نه ۸۰). کانتینر non-root اجرا میشود و nginx روی پورت غیرممتاز ۸۰۸۰ گوش میدهد. در Coolify port سرویس `app` را روی **۸۰۸۰** بگذار (Traefik به هر پورتی روت میکند — طبق داک، هر پورتی مجاز است).
|
||||
|
||||
> اجازهدادن CORS و host فرانتاندها از طریق متغیرهای `CORS_ALLOW_ORIGIN` / `ALLOWED_FRONTEND_HOSTS` کنترل میشود، نه دامنهی Coolify.
|
||||
|
||||
@@ -164,12 +166,24 @@ php bin/console app:create-admin
|
||||
|
||||
### بررسی سلامت
|
||||
|
||||
- healthcheck سرویس `app`: `php fsockopen 127.0.0.1:80`.
|
||||
- healthcheck سرویس `app`: `docker/healthcheck.sh` route واقعی `/health` را روی پورت ۸۰۸۰ میزند (نه فقط چک پورت).
|
||||
- workerها: healthcheck زندهبودن پروسهی `messenger:consume` با `ps`.
|
||||
- Swagger: `https://<APP_BASE_URL>/api/doc`
|
||||
- پنل ادمین: `https://<APP_BASE_URL>/admin`
|
||||
|
||||
---
|
||||
|
||||
## امنیت و منابع
|
||||
|
||||
- **non-root:** کل استک (supervisord + php-fpm + nginx) با کاربر `www-data` اجرا میشود؛ nginx روی پورت غیرممتاز ۸۰۸۰. هیچ پروسهای root نیست.
|
||||
- **بدون secret در image/repo:** همهی مقادیر حساس از تب Environment Variables کولیفای (`${...}`)؛ `.env` مخزن gitignore است و در image یک `.env` حداقلی فقط `APP_ENV=prod` ساخته میشود.
|
||||
- **Resource Limits:** داک Coolify limits را از UI منبع میگیرد (نه لزوماً از compose). پیشنهاد شروع:
|
||||
- `app`: حافظه ~۵۱۲MB–۱GB، CPU ~۱
|
||||
- هر worker: حافظه ~۲۵۶MB، CPU ~۰٫۵
|
||||
در صفحهی هر سرویس Coolify تنظیم کن و با مصرف واقعی تنظیم نهایی کن.
|
||||
|
||||
---
|
||||
|
||||
## دیپلویهای بعدی
|
||||
|
||||
push روی برنچ متصل (یا Deploy دستی). در هر ریدیپلوی:
|
||||
|
||||
Reference in New Issue
Block a user