Files
clinicpro/.claude/prompt/deploy-liara-docker.md
T

14 KiB
Raw Blame History

دیپلوی ClinicPro (Symfony) روی لیارا با Docker

پروژه

clinicpro (Backend Symfony 7.4 + پنل ادمین React، image داکر چندمرحله‌ای).

این یک راهنمای دیپلوی + تسک پیاده‌سازی است. هدف: انتقال استک فعلی (که برای Coolify / docker-compose ساخته شده) به لیارا، جایی که docker-compose پشتیبانی نمی‌شود.

منبع: مستندات لیارا — quick-start، deploy-docker-compose، set-envs، use-disk، configure-supercronic.


زمینه

پروژه همین الان یک استک داکر کامل دارد که برای Coolify تنظیم شده:

  • Dockerfile — multi-stage (vendor → assets → runtime PHP-FPM + Nginx via Supervisor)، non-root www-data، EXPOSE 8080، healthcheck روی /health.
  • docker-compose.yml۳ سرویس: app (وب) + worker-async + worker-scheduler؛ MariaDB و Redis سرویس‌های جدا (Coolify Database Resources).
  • docker/supervisord.conf — فعلاً فقط php-fpm و nginx را اجرا می‌کند (workerها داخلش نیستند؛ در compose سرویس مجزا بودند).
  • docker/entrypoint.sh — وقتی RUN_INIT=1: صبر برای DB → تولید کلید JWT → cache:clear/warmupdoctrine:migrations:migrate.
  • .env.coolify.example — مرجع کامل متغیرهای محیطی.
  • دیسک‌های ماندگار (volume) فعلی: jwt_keys → /app/config/jwt، uploads_public → /app/public/uploads، uploads_var → /app/var/uploads.

مشکل / هدف

لیارا از docker-compose مستقیم پشتیبانی نمی‌کند («لیارا، به صورت مستقیم از Docker Compose پشتیبانی نمی‌کند»). پس ساختار ۳-سرویسی + DB/Redis جداگانه نمی‌تواند عیناً منتقل شود. باید بازچینش شود:

جزء فعلی (compose) معادل در لیارا
سرویس app (PHP-FPM+Nginx) یک برنامه داکر روی لیارا (همین Dockerfile، پورت 8080)
worker-async + worker-scheduler داخل همان image با Supervisor اجرا شوند (توصیه‌شده، تک‌برنامه) — یا برنامه‌های داکر مجزا
سرویس MariaDB جدا دیتابیس مدیریت‌شدهٔ MariaDB لیارا (شبکهٔ خصوصی)
سرویس Redis جدا Redis مدیریت‌شدهٔ لیارا (شبکهٔ خصوصی)
volumeها (jwt, uploads) دیسک‌های لیارا که در liara.json mount می‌شوند
env tab کولیفای liara env set / کنسول لیارا

تصمیم معماری (پیش‌فرض توصیه‌شده): تک‌برنامهٔ داکر. هر دو worker را به supervisord.conf اضافه کن تا در همان کانتینر کنار php-fpm/nginx اجرا شوند. ارزان‌ترین و نزدیک‌ترین گزینه به image فعلی. (دلیل اینکه supercronic مناسب نیست: هر دو worker پروسهٔ بلندمدت messenger:consume هستند نه job دوره‌ای cron — جای آن‌ها Supervisor است نه crontab. supercronic فقط اگر scheduler را به یک دستور one-shot تبدیل کنی به کار می‌آید؛ در «گزینه‌های جایگزین» پایین آمده.)


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

فایل نقش تغییر
Dockerfile image رانتایم احتمالاً بدون تغییر (سازگار با لیارا است)
docker/supervisord.conf پروسه‌منیجر کانتینر افزودن دو program برای workerها
docker/entrypoint.sh init و migration بازبینی wait-for-DB (بدون compose depends_on)
liara.json پیکربندی دیپلوی لیارا فایل جدید — ساخته شود
.env.liara.example مرجع env لیارا فایل جدید — اختیاری ولی توصیه‌شده
.dockerignore استثناهای build بدون تغییر (.env عمداً نگه داشته می‌شود)

وضعیت فعلی (کد واقعی)

docker/supervisord.conf فقط دو program دارد:

[program:php-fpm]
command=php-fpm -F
autorestart=true
priority=10
...

[program:nginx]
command=nginx -g 'daemon off;'
autorestart=true
priority=20
...

docker-compose.yml workerها را اینطور اجرا می‌کند (همین دستورها باید به supervisord منتقل شوند):

worker-async:
  command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v
worker-scheduler:
  command: php bin/console messenger:consume scheduler_default --time-limit=3600 -v

وظایف

۱. افزودن workerها به Supervisor

به انتهای docker/supervisord.conf این دو program را اضافه کن. مهم: فقط یک پروسه باید init/migration بزند؛ این کار را entrypoint با RUN_INIT کنترل می‌کند و چون اینجا تک‌کانتینر است مشکلی نیست (entrypoint قبل از exec supervisord یک‌بار اجرا می‌شود، نه per-program).

[program:worker-async]
command=php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v
autorestart=true
priority=30
# مصرف‌کنندهٔ messenger با SIGTERM پیام در حال پردازش را تمام و سپس خارج می‌شود.
stopsignal=TERM
stopwaitsecs=30
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:worker-scheduler]
command=php bin/console messenger:consume scheduler_default --time-limit=3600 -v
autorestart=true
priority=30
stopsignal=TERM
stopwaitsecs=30
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

--time-limit=3600 باعث می‌شود پروسه هر ساعت سالم خارج شود و Supervisor با autorestart=true دوباره بالا بیاورد (جلوگیری از نشت حافظه / اتصال‌های بیات). دقیقاً رفتار compose.

۲. ساخت liara.json در ریشهٔ پروژه

{
  "app": "clinicpro-api",
  "platform": "docker",
  "port": 8080,
  "healthCheck": {
    "command": "/usr/local/bin/healthcheck.sh",
    "interval": 30,
    "timeout": 5,
    "initialDelaySeconds": 60
  },
  "disks": [
    { "name": "jwt",          "mountTo": "/app/config/jwt" },
    { "name": "uploads",      "mountTo": "/app/public/uploads" },
    { "name": "var-uploads",  "mountTo": "/app/var/uploads" }
  ]
}

نکات:

  • platform: "docker" → لیارا از Dockerfile ریشه build می‌کند.
  • port: 8080 چون nginx داخل image روی 8080 (non-root) گوش می‌دهد و EXPOSE 8080 ست شده.
  • مسیر mount دیسک‌ها absolute و با احتساب WORKDIR /app نوشته شده (طبق مستند use-disk).
  • پیش از دیپلوی، در کنسول لیارا این سه دیسک را با همین نام‌ها بساز: jwt، uploads، var-uploads.
  • اگر فیلد healthCheck در نسخهٔ liara.json پشتیبانی نشد، حذفش کن و healthcheck را از کنسول ست کن؛ خود image هم HEALTHCHECK داخلی دارد.

۳. ساخت دیتابیس و Redis مدیریت‌شدهٔ لیارا

در کنسول لیارا:

  1. یک دیتابیس MariaDB 11.8 بساز (هماهنگ با serverVersion=mariadb-11.8.0). شبکهٔ خصوصی را فعال کن.
  2. یک Redis بساز، شبکهٔ خصوصی فعال.
  3. هاست داخلی هرکدام را از صفحهٔ سرویس بردار (روی شبکهٔ خصوصی، مثلاً clinicpro-db / clinicpro-redis).
  4. برنامهٔ داکر و دو سرویس را روی همان شبکهٔ خصوصی قرار بده.

۴. ست‌کردن متغیرهای محیطی روی لیارا

با CLI (liara env set KEY=VALUE --app clinicpro-api) یا تب Environment در کنسول. حداقل متغیرهای موردنیاز (از .env.coolify.example گرفته شده، فقط هاست‌ها به سرویس‌های لیارا اشاره می‌کنند):

APP_ENV=prod
APP_DEBUG=0
APP_SECRET=<php -r "echo bin2hex(random_bytes(32));">
JWT_PASSPHRASE=<openssl rand -hex 32>

# هاست داخلی = نام سرویس دیتابیس لیارا روی شبکهٔ خصوصی
DATABASE_URL=mysql://<user>:<pass>@<db-private-host>:3306/<db>?serverVersion=mariadb-11.8.0&charset=utf8mb4
REDIS_URL=redis://<redis-private-host>:6379
MESSENGER_TRANSPORT_DSN=redis://<redis-private-host>:6379/messages

JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem

APP_BASE_URL=https://<your-liara-domain>
DEFAULT_URI=https://<your-liara-domain>
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1

ALLOWED_FRONTEND_HOSTS=<از docker/gen-cors-env.php>
CORS_ALLOW_ORIGIN=<از docker/gen-cors-env.php>

API_IR_BASE_URL=https://s.api.ir
API_IR_TOKEN=<token>

REFRESH_TOKEN_TTL=2592000
OTP_TTL=1200
MAX_FILE_SIZE_BYTES=5242880
UPLOAD_DIR=var/uploads

نکات:

  • در compose این مقادیر در x-app-env بودند؛ روی لیارا چون env-tab وجود دارد همه باید اینجا ست شوند.
  • ALLOWED_FRONTEND_HOSTS و CORS_ALLOW_ORIGIN را با اجرای php docker/gen-cors-env.php تولید کن (از docker/frontend-domains.json می‌خواند).
  • نیازی به ست‌کردن RUN_INIT نیست؛ entrypoint پیش‌فرض 1 می‌گیرد و چون تک‌برنامه است درست است.
  • کلیدهای SMS و درگاه پرداخت از DB خوانده می‌شوند، نه env.

۵. اولین دیپلوی

# نصب CLI (یک‌بار)
npm i -g @liara/cli
liara login

# از ریشهٔ clinicpro:
liara deploy --app clinicpro-api --platform docker --port 8080

liara.json بیشتر این فلگ‌ها را پوشش می‌دهد، پس liara deploy خالی هم کافی است. در اولین بالا آمدن، entrypoint.sh صبر می‌کند تا DB جواب دهد، کلید JWT می‌سازد (روی دیسک jwt ماندگار)، و migrationها را می‌زند.

۶. تأیید سلامت

  • لاگ‌ها: liara logs --app clinicpro-api — باید php-fpm، nginx، و هر دو worker بالا باشند.
  • GET https://<domain>/health200.
  • ورود ادمین در /admin و تست یک endpoint تا اتصال DB/Redis تأیید شود.

گزینه‌های جایگزین (در صورت نیاز، نه پیش‌فرض)

الف) workerها به‌صورت برنامهٔ داکر مجزا (به‌جای افزودن به Supervisor): همین repo را دوبار دیگر با liara.json متفاوت دیپلوی کن که command را override کند تا فقط messenger:consume ... اجرا شود و RUN_INIT=0 و بدون پورت/healthcheck وب. گران‌تر (سه برنامهٔ داکر) ولی ایزوله‌تر.

ب) supercronic برای scheduler: اگر بخواهی scheduler را به‌جای مصرف‌کنندهٔ بلندمدت، به cron تبدیل کنی:

  • در Dockerfile: COPY --from=liaracloud/supercronic:v0.1.11 /usr/local/bin/supercronic /usr/local/bin/supercronic
  • فایل crontab در ریشه: * * * * * cd /app && php bin/console messenger:consume scheduler_default --limit=1
  • یک [program:supercronic] در supervisord با command=supercronic /app/crontab.
  • توجه: این الگو فقط برای کارهای دوره‌ای مناسب است؛ worker-async (SMS) باید مصرف‌کنندهٔ بلندمدت بماند. مگر نیاز خاص، گزینهٔ پیش‌فرض (Supervisor) را نگه دار.

نکات مهم

  • بدون docker-compose: هیچ depends_on/service_healthy روی لیارا نیست؛ منطق wait-for-DB در entrypoint.sh (تا ~۶۰s) دقیقاً برای همین لازم است — دست نخورد.
  • دیسک نه volume: اگر روزی VOLUME در Dockerfile اضافه شد، طبق مستند لیارا حذفش کن و از دیسک لیارا استفاده کن. الان Dockerfile دستور VOLUME ندارد — خوب است.
  • ماندگاری کلید JWT: دیسک jwt حتماً mount شود، وگرنه هر دیپلوی کلید نو می‌سازد و همهٔ توکن‌های صادرشده باطل می‌شوند (--skip-if-exists فقط وقتی دیسک ماندگار باشد کار می‌کند).
  • .env عمداً در image است (به‌خاطر Dotenv::bootEnv)؛ مقادیر واقعی از env لیارا می‌آیند و Dotenv متغیر ازقبل‌ست‌شده را بازنویسی نمی‌کند (clear_env=no در docker/php/zz-pool.conf). این رفتار را خراب نکن.
  • هماهنگی نسخهٔ DB: serverVersion در DATABASE_URL باید با نسخهٔ دیتابیس مدیریت‌شدهٔ لیارا یکی باشد (11.8).
  • پورت تک‌وب: لیارا فقط یک پورت HTTP بیرونی می‌دهد (8080)؛ MariaDB/Redis فقط روی شبکهٔ خصوصی در دسترس‌اند — درست با معماری ما می‌خواند.
  • CORS چنددامنه: بک‌اند برای ده‌ها دامنهٔ فرانت سرویس می‌دهد؛ بعد از تغییر docker/frontend-domains.json دوباره gen-cors-env.php بزن و env را به‌روزرسانی کن.
  • این تغییر فقط زیرساخت دیپلوی است و هیچ endpoint/route را عوض نمی‌کند، پس به‌روزرسانی docs/api/* لازم نیست.