14 KiB
دیپلوی 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-rootwww-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/warmup→doctrine: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 مدیریتشدهٔ لیارا
در کنسول لیارا:
- یک دیتابیس MariaDB 11.8 بساز (هماهنگ با
serverVersion=mariadb-11.8.0). شبکهٔ خصوصی را فعال کن. - یک Redis بساز، شبکهٔ خصوصی فعال.
- هاست داخلی هرکدام را از صفحهٔ سرویس بردار (روی شبکهٔ خصوصی، مثلاً
clinicpro-db/clinicpro-redis). - برنامهٔ داکر و دو سرویس را روی همان شبکهٔ خصوصی قرار بده.
۴. ستکردن متغیرهای محیطی روی لیارا
با 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>/health→200.- ورود ادمین در
/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/*لازم نیست.