مثال استفاده از docker compose - نمونه فایل .yml یه پروژه واقعی
منبع: https://rasanika.com
داکر کامپوز برای تعریف و اجرای اپلیکیشن های داکریه که از چندین کانتینر تشکیل شده اند. یعنی اینکه مثلا تصور کنید برای اجرای یه سایت به این سرویس ها نیاز داریم: وب سرور، دیتابیس، بک اند، سیستم ارسال ایمیل و … با استفاده از docker compose می تونیم همه ی این سرویس ها رو دقیقا با ورژن و تنظیماتی که لازم داریم تعریف کنیم و بدیم به داکر که همه رو با ورژن های دقیق، برای ما راه اندازی کنه.
خیلی وقتا اتفاق میوفته که کد و پروژه ای که می سازیم روی لپ تاپ یا سیستم لوکالمون درست کار میکنه ولی وقتی آپلودش می کنیم روی سرور خطا میده. اکثر مواقع دلیلش اینه که مثلا نسخه دیتابیسی که رو سرور وجود داره با نسخه لوکالمون فرق داره. یکی از مزایای docker compose حل این مشکله یعنی دقیقا همون ورژنی از نرم افزار که انتظارشو داریم ایجاد میشه و باگ های غیرمنتظره کمتر پیش میاد. یه مزیت دیگش هم راحت تر کردن و تسریع پروسه راه اندازیه.
قبل از ادامه اول آخرین ورژن داکر رو نصب کنید. (کامپوز خودش با داکر نصب میشه)
نمونه فایل docker-compose.yml
سایت رسانیکا هم با استفاده از داکر کامپوز راه اندازی شده و فایل docker-compose.yml
اون تقریبا به این شکله:
version: '3.8'
networks:
rt-network:
driver: bridge
secrets:
postgres_db:
file: ./.secrets/postgres_db
postgres_usr:
file: ./.secrets/postgres_usr
postgres_pass:
file: ./.secrets/postgres_pass
jwt_secret:
file: ./.secrets/jwt_secret
rfs_secret:
file: ./.secrets/rfs_secret
x-service-defaults:
&service-defaults
ulimits:
core:
soft: 0
hard: 0
restart: unless-stopped
tty: true
networks:
- rt-network
x-pg-secrets:
&pg-secrets
- postgres_db
- postgres_usr
- postgres_pass
x-pg-secrets-env:
&pg-secrets-env
POSTGRES_DB_FILE: /run/secrets/postgres_db
POSTGRES_USER_FILE: /run/secrets/postgres_usr
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_pass
services:
postgres:
<<: *service-defaults
image: postgres:13.2-alpine
volumes:
- ./storage/postgres:/var/lib/postgresql/data
expose:
- '5432'
environment:
<<: *pg-secrets-env
TZ: UTC
LANG: en_US.utf8
secrets: *pg-secrets
redis:
<<: *service-defaults
image: redis:6.0.5-alpine
expose:
- '6379'
environment:
TZ: UTC
NODE_ENV: ${APP_ENV}
elastic:
<<: *service-defaults
image: elasticsearch:8.6.2
volumes:
- ./storage/elastic:/usr/share/elasticsearch/data
expose:
- '9200'
environment:
TZ: UTC
xpack.security.enabled: 'false'
ingest.geoip.downloader.enabled: 'false'
bootstrap.memory_lock: 'true'
discovery.type: single-node
ES_JAVA_OPTS: '-Xms512m -Xmx512m'
api:
&api
<<: *service-defaults
image: node:18-alpine
user: node:node
command: ['npm', 'run', '${API_CMD}']
working_dir: /main
volumes:
- ./storage/api:/main/storage
- ./shared-types:/shared-types
- ./api:/main
environment:
TZ: UTC
APP_TZ: ${APP_TZ}
APP_CALENDAR: ${APP_CALENDAR}
EXPOSED_PORT: 4500
APP_DOMAIN: ${APP_DOMAIN}
APP_PROTOCOL: ${APP_PROTOCOL}
APP_TITLE: ${APP_TITLE}
NODE_ENV: ${APP_ENV}
SMS_SECRET: ${SMS_SECRET}
SMS_TEMPLATE_VERIFY: ${SMS_TEMPLATE_VERIFY}
SMS_TEMPLATE_NOTIFS: ${SMS_TEMPLATE_NOTIFS}
SMS_FROM: ${SMS_FROM}
SMS_USE_TEXT: ${SMS_USE_TEXT}
ALLOW_EMAIL_AUTH: ${ALLOW_EMAIL_AUTH}
GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID}
expose:
- '4500'
depends_on:
- postgres
- elastic
secrets:
- postgres_db
- postgres_usr
- postgres_pass
- jwt_secret
- rfs_secret
api_cron:
<<: *api
command: ['npm', 'run', 'cron']
expose: []
rfs:
&rfs
<<: *service-defaults
image: node:18-alpine
user: node:node
command: ['npm', 'run', '${RFS_CMD}']
working_dir: /main
volumes:
- ./storage/rfs:/main/storage
- ./shared-types:/shared-types
- ./rfs:/main
environment:
TZ: UTC
APP_TZ: ${APP_TZ}
EXPOSED_PORT: 3500
APP_DOMAIN: ${APP_DOMAIN}
APP_PROTOCOL: ${APP_PROTOCOL}
APP_TITLE: ${APP_TITLE}
NODE_ENV: ${APP_ENV}
expose:
- '3500'
depends_on:
- postgres
secrets:
- rfs_secret
rfs_cron:
<<: *rfs
command: ['npm', 'run', 'cron']
expose: []
next:
<<: *service-defaults
image: node:18-alpine
user: node:node
command: ['npm', 'run', '${NEXT_CMD}']
working_dir: /main
volumes:
- ./next:/main
- ./shared-types:/shared-types
environment:
TZ: UTC
EXPOSED_PORT: 3000
NEXT_PUBLIC_APP_TZ: ${APP_TZ}
NEXT_PUBLIC_APP_TITLE: ${APP_TITLE}
NEXT_PUBLIC_APP_DOMAIN: ${APP_DOMAIN}
NEXT_PUBLIC_APP_PROTOCOL: ${APP_PROTOCOL}
NEXT_PUBLIC_ENV: ${APP_ENV}
NODE_ENV: ${APP_ENV}
NEXT_PUBLIC_TRACKING_ID: ${APP_TRACKING_ID}
NEXT_PUBLIC_ALLOW_EMAIL_AUTH: ${ALLOW_EMAIL_AUTH}
NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID}
expose:
- '3000'
secrets: *pg-secrets
nginx:
<<: *service-defaults
image: nginx:1.18-alpine
volumes:
- ./storage/errors:/etc/nginx/service-errors
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./storage/rfs/public:/rfs/storage
environment:
TZ: UTC
ports:
- '127.0.0.1:${APP_PORT}:80'
depends_on:
- api
- rfs
- next
توضیح قسمت های مختلف فایل yml
فایل های yml معمولا برای تنظیمات و کانفیگ نرم افزار استفاده میشن و یه فرمت خاصی دارن به اسم YAML که داکر کامپوز هم از این فرمت استفاده میکنه. توی کانفیگ فایل موردنظر ما، هفت بخش اصطلاحا روت داریم:
version: '3.8'
networks:
secrets:
x-service-defaults:
x-pg-secrets:
x-pg-secrets-env:
services:
version همیشه باید اولین بخش فایل docker-compose.yml باشه و نسخه ی اون رو مشخص کنه چون توی نسخه های مختلف ویژگی های مختلفی وجود داره.
networks یه سری شبکه تعریف میکنه که سرویس هامون از طریق این شبکه ها باهم در ارتباط هستند. مثلا دیتابیس و بک اند رو میذاریم توی یه شبکه مشترک که بتونن به هم دیگه دسترسی داشته باشن. اینجا من یه شبکه با اسم rt-network و نوع bridge تعریف کردم. اطلاعات بیشتر
secrets کلید ها و رمز های استفاده شده توسط سرویس ها رو تعریف میکنه. در واقع بجای اینکه رمز ها رو مستقیما توی سورس کد بنویسیم، از طریق داکر سکرت تعریفشون میکنیم و موقع اجرا به سرویس ها میدیم. با این روش رمز ها از سورس کد جدا میشن که امنیت بیشتری داره.
اینجا این کد یعنی یه سکرت با اسمpostgres_pass
تعریف کن و مقدارش رو از فایل./.secrets/postgres_pass
بردار (فایل رو خودمون ایجاد میکنم تو سرور یا سیستم لوکال):
postgres_pass:
file: ./.secrets/postgres_pass
بخش هایی که با
x-
شروع میشن بهشون میگن extension fields و برای جلوگیری از تکرار هستند. مثلا این تنظیمات بین همه ی سرویس ها مشترکه که با اسم service-defaults تعریف کردم:
ulimits:
core:
soft: 0
hard: 0
restart: unless-stopped
tty: true
networks:
- rt-network
services مهم ترین بخش این فایله و همه سرویس های این اپلیکیشن رو تعریف میکنه. اینجا ما 9 تا سرویس داریم که به ترتیب توضیح میدم:
postgres دیتایبسه (توی عنوان سرویس یعنی بجای postgres هر اسمی میشه استفاده کرد).
<<: *service-defaults
یعنی کانفیگ هایی که باx-service-defaults
تعریف کردیم رو روی این سرویس اعمال کن.image: postgres:13.2-alpine
این خط مشخص میکنه دیتابیس postgres ورژن 13 رو راه اندازی کن. به صورت پیش فرض داکر این ایمیج ها رو از داکر هاب برمیداره، میتونین برین اونجا و ایمیج مورد نظرتون رو پیدا کنید.
نکته مهم: قسمت volumes اینجا تعریف میکنه که دقیقا فولدر/var/lib/postgresql/data
دیتابیس رو روی فولدر./storage/postgres
سیستم قرار بده. ⚠ اگه اینکارو نکنیم با هر بار ریستارت این سرویس کل اطلاعات دیتابیس پاک میشه. هر فولدری توی هر سرویسی خواستیم اطلاعاتش ریست و پاک نشه باید براش volume تعریف کنیم.
توی expose پورت هایی که برای این سرویس باید باز بشه رو تعریف میکنم. این نکته رو هم بگم که هر اسمی واسه سرویس بدین به صورت پیشفرض هاست نیم سرویس میشه. یعنی اینجا postgres:5432 آدرس اتصال به دیتابیسه. توی environment متغیر های محیطی این سرویس رو تعیین میکنیم و توی secrets تعریف میکنیم به کدوم یک از سکرت ها دسترسی داشته باشهredis یه دیتابیس از نوع in-memory با سرعت بالاست که برای کش استفاده میکنیم. این سرویس چون صرفا برای کش استفاده میشه هیچ ولومی نداره و موقع ریستارت اطلاعاتش پاک میشه.
elastic یه سیستم جستجو هست. اگه الان یچیزی داخل رسانیکا سرچ کنید، این سرویس نتایج رو براتون پیدا میکنه.
api در واقع بک اند سایته که بر پایه ایمیج nodejs ساخته شده. اینجا چندتا آپشن جدید داریم. user همون یوزر و گروهیه که تو لینوکس تعریف میشه. command دستور پیشفرضی هست که موقع بالا اومدن سرویس اجرا میشه. و working_dir دایرکتوری یا فولدر پیشفرض موقع اجرای دستوراته. توی depends_on میتونیم لیست سرویس های پیش نیاز رو مشخص کنیم تا داکر همیشه قبل از بالا آوردن این سرویس، اونا رو بالا بیاره. مثلا دیتابیس برای شروع به کار بک اند لازمه و قبل از اون باید بالا بیاد.
api_cron سرویس کار های پس زمینه و زمان بندی شده هست. دقیقا مشخصات api رو داره
<<: *api
فقط دستور پیشفرضش فرق میکنه و هیچ پورتی لازم نداره واسه همین expose خالی دادم.rfs بک اند پردازش فایل هاست. تصاویر و ویدیو هایی که توی سایت آپلود میشه توسط این سرویس بهینه و اعتبار سنجی میشه.
rfs_cron هم کار های پس زمینه و زمان بندی شده ی مربوط به فایل هاست. مثلا هر روز فایل های استفاده نشده رو پاک میکنه.
next سرویس UI و فرانت رسانیکاه.
nginx وب سرور اصلیه و درخواست های HTTP رو به سرویس های مربوطه ارجاع میده. اینجا آپشن ports پورت مپینگ هست. شبکه rt-network رو که تعریف کردیم و گفتیم این سرویس ها ازش استفاده کنن یه شبکه ایزوله و امن هست و داکر اجازه نمیده هیچ درخواستی از خارج از این شبکه به دیتابیس و … وارد بشه. با استفاده از پورت مپینگ به داکر میگیم برای این سرویس و این پورت اجازه دسترسی بده. یعنی مثلا وقتی یه درخواست به
rasanika.com/api/
ارسال میشه اول میره به سرویس nginx که پورت مپینگ داره و دامنه روی اون تنظیمه، بعدش nginx تصمیم میگیره این درخواست رو هدایت میکنه به سرویس api یا هر سرویس دیگه ای.
برای دیدن همه ی ویژگی ها میتونید به رفرنس فایل docker-compose.yml مراجعه کنید.
استفاده از متغیر های .env
حتما توی این فایل یه سری عبارت های اینطوری ${…}
مثل ${APP_DOMAIN}
دیدین، اینا در واقع اسم متغیر های محیطی یا environment variable هستن که داکر مقدارشون رو از داخل فایل .env
کنار فایل docker-compose.yml
میخونه. بخش از فایل .env
رسانیکا در سرور:
APP_DOMAIN="rasanika.com"
API_CMD="start"
NEXT_CMD="start"
RFS_CMD="start"
و همچنین روی سیستم لوکال (محیط کدنویسی):
APP_DOMAIN="localhost"
API_CMD="dev"
NEXT_CMD="dev"
RFS_CMD="dev"
همونطور که میبینید این روش به ما اجازه میده خیلی راحت کانفیگ داکر رو بر اساس محیط پروداکشن سرور یا محیط کدنویسی تغییر بدیم. البته میشه از docker-compose.prop.yaml
هم استفاده کرد ولی من env رو ترجیح میدم.
بررسی وضعیت سرویس ها
بعد اینکه با دستور docker compose up -d
پروژه رو بالا آوردیم میتونیم وضعیت سرویس ها رو با دستور docker compose ps
بررسی کنیم. نمونش به این صورته:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
rasanika_elastic_1 elasticsearch:8.6.2 "/bin/tini -- /usr/l…" elastic 2 days ago Up 2 days 9200/tcp, 9300/tcp
rasanika_nginx_1 nginx:1.18-alpine "/docker-entrypoint.…" nginx 20 hours ago Up 20 hours 127.0.0.1:10903->80/tcp
rasanika_postgres_1 postgres:13.2-alpine "docker-entrypoint.s…" postgres 2 days ago Up 2 days 5432/tcp
rasanika_api_1 node:18-alpine "docker-entrypoint.s…" api 20 hours ago Up 20 hours 4500/tcp
rasanika_api_cron_1 node:18-alpine "docker-entrypoint.s…" api_cron 20 hours ago Up 1 second
rasanika_redis_1 redis:6.0.5-alpine "docker-entrypoint.s…" redis 2 days ago Up 2 days 6379/tcp
rasanika_next_1 node:18-alpine "docker-entrypoint.s…" next 20 hours ago Up 20 hours 3000/tcp
rasanika_rfs_1 node:18-alpine "docker-entrypoint.s…" rfs 2 days ago Up 20 hours 3500/tcp
rasanika_rfs_cron_1 node:18-alpine "docker-entrypoint.s…" rfs_cron 2 days ago Up 20 hours
راستی اگه علاقه داشتین مراحل راه اندازی (کدنویسی، سرور، دامنه و …) یه سایت کامل رو توی این ویدیو توضیح دادم:
آموزش React NextJS و TypeScript با ساخت یک پروژه کامل
rasanika.comدر این پست صفر تا صد ساخت و راه اندازی یک وبسایت را با ریاکت و تایپ اسکریپت یاد خواهیم گرفت. این یک پروژه متوسط رو به بالا است که طی آن با کدنویسی، طراحی دیتابیس و ریلیشنها، احراز هویت کاربر، ارتباط
در نهایت امیدوارم این پست برای شما مفید واقع شده باشه و خیلی ممنونم. موفق و پیروز باشید. 🌹💪