Docker برای فینچ
فینچ طوری طراحی شده که بهخوبی درون Docker اجرا شود. ایمیج رسمی uproid/finch شامل Dart SDK، ابزار خط فرمان finch، و هر چیزی است که برای build و سرویسدهی یک اپلیکیشن فینچ لازم است. پروژه example/ در این ریپازیتوری یک docker-compose.yaml کامل ارائه میدهد که میتوانید آن را بهعنوان نقطه شروع برای MongoDB، MySQL، SQLite، یک Nginx reverse proxy، و build کردن assetهای Tailwind — همه در یک stack — کپی کنید.
ساختار پروژه
یک پروژه معمولی فینچ مبتنی بر Docker از این فایلها استفاده میکند:
my-app/
Dockerfile # ایمیج container فینچ را میسازد
docker-compose.yaml # تمام سرویسها را orchestrate میکند
docker/
nginx.conf # پیکربندی Nginx reverse proxy
lib/ # کد Dart اپلیکیشن
public/ # فایلهای استاتیک سرویسدهیشده توسط Nginx
sqlite/ # فایلهای دیتابیس SQLite (volume-mounted)
mongodb/ # فایلهای داده MongoDB (volume-mounted)
mysql/ # فایلهای داده MySQL (volume-mounted)
Dockerfile
فایل Dockerfile نمونه از ایمیج پایه رسمی فینچ استفاده میکند و در زمان build وابستگیها را نصب میکند:
FROM uproid/finch:latest AS build
WORKDIR /www
# مقادیر پیشفرض مسیر/رفتار — برای هر محیط در docker-compose.yaml بازنویسی کنید
ENV WIDGETS_PATH=./lib/widgets
ENV WIDGETS_TYPE=j2.html
ENV LANGUAGE_PATH=./lib/languages
ENV PUBLIC_DIR=./public
ENV LOCAL_DEBUG=true
ENV ENABLE_DATABASE=true
COPY pubspec.yaml ./
RUN dart pub get --no-offline
COPY lib/ ./lib/
# Build: باینری اپلیکیشن فینچ را تولید کرده و مسیرها/widgetها را از پیش کامپایل میکند
RUN finch -u
# پورت اپلیکیشن (8085) و پورت Dart VM service/DevTools (8181)
EXPOSE 8085 8181
# در زمان شروع: اپلیکیشن را اجرا کرده و migrationهای در انتظار MySQL و SQLite را اعمال میکند
CMD ["finch", "serve", "-p", "/www/lib/watcher.dart", "--args=\"migrate --init --and migrate_sqlite --init\""]
finch -u (به Finch CLI مراجعه کنید) خودِ CLI سراسرینصبشدهی finch را دوباره فعال میکند — معادل اجرای دوبارهی dart pub global activate finch — تا ابزار build/serve داخل ایمیج با آخرین نسخهی منتشرشدهی Finch همراستا باشد، نه نسخهای که بهصورت اتفاقی در image پایهی uproid/finch:latest جاسازی شده است. CMD اپلیکیشن را از طریق finch serve اجرا میکند، lib/watcher.dart را نظارت میکند (به Run App مراجعه کنید) و migrate --init / migrate_sqlite --init را پاس میدهد تا migrationهای دیتابیس (به Database Migration مراجعه کنید) بهصورت خودکار در هر شروع container اعمال شوند — اجرای مکرر آن امن است، چون migrationهای از قبل اعمالشده رد میشوند.
اگر local debugger (ENABLE_LOCAL_DEBUGGER=true) را درون یک container فعال میکنید، EXPOSE 8282 را هم اضافه کرده و آن را در docker-compose.yaml منتشر کنید — این پورت ترمینال تعاملی debugger را روی یک WebSocket حمل میکند و بهصورت پیشفرض expose نمیشود.
نکته: فایلهای
Dockerfileوdocker-compose-live.yamlدر ریشه این ریپازیتوری، خودِ framework فینچ را برای نمونه live خودش build میکنند و قرار نیست در یک پروژه اپلیکیشن کپی شوند — همیشه ازexample/Dockerfileوexample/docker-compose.yamlشروع کنید.
docker-compose.yaml
استک نمونه کامل دارای پنج سرویس است: finch (اپلیکیشن شما)، nginx (reverse proxy)، mongodb، mysql، و nodejs (برای build کردن assetهای Tailwind CSS):
services:
finch:
hostname: finch
container_name: finch
image: uproid/finch:latest
restart: always
ports:
- "8085:8085" # پورت اپلیکیشن
- "8181:8181" # پورت Dart VM service / DevTools
environment:
SQLITE_PATH: /www/sqlite/example_database.sqlite
ENABLE_DATABASE: true
MONGODB_CONNECTION: mongodb # از hostname سرویس استفاده کنید، نه localhost
MONGODB_PORT: 27017 # پورت داخلی container (نه پورت 27018 که به host نگاشت شده)
MONGODB_NAME: my_database
MONGODB_AUTH: admin
MONGODB_PASSWORD: change-me
MONGODB_USER: root
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_DATABASE: my_database
MYSQL_USER: app_user
MYSQL_PASSWORD: change-me
build:
context: .
volumes:
- ./lib/:/www/lib/ # hot-reload کردن فایلهای سورس بدون rebuild کردن ایمیج
- ./public/:/www/public/
- ./sqlite/:/www/sqlite/ # نگهداشتن دادههای SQLite بین راهاندازیهای مجدد container
nodejs:
image: node:20-alpine
container_name: nodejs
working_dir: /app
tty: true
stdin_open: true
volumes:
- ./:/app
command: sh -c "npm install && npm run tailwind:watch"
restart: always
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8080:8080" # پورت رو به بیرون (public)
volumes:
- ./public/:/var/www/html/public
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
mongodb:
image: mongo:latest
container_name: mongodb
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: change-me
ports:
- "27018:27017" # پورت 27018 روی host، برای جلوگیری از تداخل با یک نصب محلی MongoDB
volumes:
- ./mongodb:/data/db
mysql:
image: mysql:latest
container_name: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: change-me-root
MYSQL_DATABASE: my_database
MYSQL_USER: app_user
MYSQL_PASSWORD: change-me
ports:
- "3306:3306"
volumes:
- ./mysql:/var/lib/mysql
نکات کلیدی:
nodejsفقط زمانی اهمیت دارد که پروژه شما از Tailwind استفاده کند (بهpackage.jsonوtailwind.config.jsنمونه مراجعه کنید)؛ اگر assetهای frontend را build نمیکنید، این سرویس را کاملاً حذف کنید.mongodbوmysqlمستقل از یکدیگرند — فقط دیتابیسهایی را که اپلیکیشن شما واقعاً استفاده میکند فعال کنید، با تنظیمenable: falseرویFinchDBConfig/FinchMysqlConfig/FinchSqliteConfigمربوطه درconfigs.dart(به Configuration مراجعه کنید)، و بلاک سرویس استفادهنشده را در اینجا حذف کنید.- درون شبکه Docker، containerها یکدیگر را با نام سرویس (
mongodb،mysql،nginx) پیدا میکنند، هرگز باlocalhost— به همین دلیلMONGODB_CONNECTIONوMYSQL_HOSTبه نامهای سرویس اشاره میکنند، نهlocalhost، حتی اگر همان فایل.envمورد استفاده برای توسعه محلی (بدون Docker) مقدارlocalhostرا داشته باشد. - پورت MongoDB که به host نگاشت شده (
27018) با پورتی که فینچ بهصورت داخلی به آن متصل میشود (27017) متفاوت است — اپلیکیشن روی شبکه Docker با پورت native خودش با Mongo صحبت میکند، در حالی که27018فقط برای این وجود دارد که بتوانید یک کلاینت محلی MongoDB را از دستگاه host خود بهlocalhost:27018برای دیباگ متصل کنید.
پراکسی معکوس Nginx
docker/nginx.conf هم درخواستهای معمولی HTTP و هم endpointهای مبتنی بر WebSocket یعنی /ws و /debugger (که توسط WebSockets و local debugger استفاده میشوند) را به container به نام finch پراکسی میکند:
upstream finch {
server finch:8085;
keepalive 32;
}
server {
listen 8080;
server_name localhost:8080;
client_max_body_size 20M;
location / {
root /var/www/html;
try_files /public$uri @dart;
}
# پشتیبانی WebSocket برای مسیرهای /ws و /debugger
location ~ ^/(ws|debugger) {
proxy_pass http://finch;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
}
location @dart {
proxy_pass http://finch;
proxy_pass_request_body on;
proxy_pass_request_headers on;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
try_files /public$uri @dart یعنی Nginx فایلهای استاتیک را مستقیماً از دایرکتوری mountشده public/ سرویسدهی میکند اگر وجود داشته باشند، و در غیر این صورت درخواست را فقط به فینچ (@dart) فوروارد میکند — این همان پیکربندی Nginx for Finch است که خارج از Docker استفاده میشود، فقط بهجای یک process محلی به container finch اشاره میکند. proxy_buffering off و timeoutهای طولانی روی بلاک location مربوط به WebSocket ضروری هستند — بدون آنها Nginx اتصالات طولانیمدت WebSocket را buffer میکند یا زودهنگام میبندد.
راهاندازی Stack
# ساخت (build) و راهاندازی همه سرویسها
docker-compose up --build
# اجرا در پسزمینه (detached)
docker-compose up -d --build
# دنبال کردن لاگهای یک سرویس
docker-compose logs -f finch
# rebuild کردن فقط سرویس finch پس از تغییر Dockerfile
docker-compose up -d --build finch
# توقف همهچیز (فقط containerها — volumeها نگه داشته میشوند)
docker-compose down
# توقف همهچیز و حذف volumeها (تمام دادههای دیتابیس از بین میرود)
docker-compose down -v
پس از اجرا، اپلیکیشن از طریق http://localhost:8080 (از طریق Nginx) یا مستقیماً از http://localhost:8085 (با دور زدن Nginx، مفید هنگام دیباگ اینکه مشکل در فینچ است یا در پیکربندی پراکسی) در دسترس است.
از آنجا که ./lib/ و ./public/ بهصورت bind-mount درون container قرار میگیرند، ویرایش این فایلها روی host بلافاصله توسط file watcher فینچ تشخیص داده میشود — برای تغییرات روزمره کد Dart نیازی به rebuild کردن ایمیج ندارید، فقط زمانی که وابستگیهای pubspec.yaml تغییر میکنند.
متغیرهای محیطی
هر مقدار از FinchConfigs که از env.get(...) (یا env.getInt(...) / env.getBool(...)) خوانده میشود، میتواند بهعنوان یک متغیر محیطی Docker یا از طریق یک فایل .env که توسط اپلیکیشن بارگذاری میشود تنظیم شود. یک .env مینیمال منطبق با فایل compose بالا:
MONGODB_CONNECTION=mongodb
MONGODB_PORT=27017
MONGODB_NAME=my_database
MONGODB_AUTH=admin
MONGODB_USER=root
MONGODB_PASSWORD=change-me
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_DATABASE=my_database
MYSQL_USER=app_user
MYSQL_PASSWORD=change-me
SQLITE_PATH=/www/sqlite/example_database.sqlite
ENABLE_LOCAL_DEBUGGER=false
LOCAL_DEBUG=false
برای فهرست کامل متغیرهای محیطی پشتیبانیشده، شامل تنظیمات سرور، قالب، و ایمیل که در اینجا نشان داده نشدهاند، به Configuration مراجعه کنید.
هرگز رمزهای عبور واقعی را commit نکنید. در خودِ docker-compose.yaml، بهجای hardcode کردن مستقیم اطلاعات محرمانه در YAML (همانطور که در بالا برای اختصار نشان داده شد)، درونیابی (interpolation) ${VAR_NAME} را از یک فایل .env که از طریق .gitignore مستثنا شده ترجیح دهید.
ملاحظات محیط Production
- در محیط production،
ENABLE_LOCAL_DEBUGGER=falseوLOCAL_DEBUG=falseرا تنظیم کنید — local debugger یک ترمینال WebSocket به داخل container در حال اجرا expose میکند و فقط برای توسعه در نظر گرفته شده است. به Debugging مراجعه کنید. - پورت Dart VM service (
8181) را خارج از شبکه خودتان منتشر نکنید؛ این پورت دسترسی کامل خواندن/نوشتن به isolate در حال اجرا میدهد. - یک reverse proxy واقعی با پایاندهی TLS (Nginx بههمراه یک certificate، یا یک load balancer مدیریتشده) جلوی stack قرار دهید —
nginx.confنمونه در اینجا فقط HTTP است و بهعنوان نقطه شروع در نظر گرفته شده. - برای دادههای
mongodb/mysqlدر production، بهجای bind-mount کردن یک دایرکتوری host، از named volume (یا یک دیتابیس مدیریتشده) استفاده کنید، تا backupها و permissionها بهطور یکسان در همه hostها مدیریت شوند. - اگر فراتر از تعداد کمی کاربر همزمان مقیاس میگیرید،
maxConnectionsرا رویFinchDBConfig/FinchMysqlConfigافزایش دهید — به MongoDB و MySQL مراجعه کنید.