Mohammad Jawad Barati
Mohammad Jawad Barati
خواندن ۴ دقیقه·۴ سال پیش

نوشتن Dockerfile

راه های زیادی هست که یه ایمیج جدید بسازی (یه لایه روی یه ایمیج دیگه بکشی)، یکی از اون راه ها که کارت رو ساده میکنه داکر فایل هست.

برای اینکه یه ایمیج بسازی دستور `docker build` رو داریم. این دستور یه Dockerfile میخواد و یه context. منظور از context محلی هست که فایل هات روش قرار داره. مقدار context میتونه یه PATH (آدرس local) یا یه URL (یه ریپوزیتوری روی git) باشه.

نکته context اینه که تو وقتی میگی که داکرفایل فلان جا (یا همین جایی که الان ترمینال توشه) هست و PATH هم فلانه، کل اون دایرکتوری با زیر دایرکتوری هاش به عنوان context در نظر گرفته میشه. توی مثال زیر context اون تیکه اخر کامند هست.

docker build -f /media/home/node.js.developers.kh/Dockerfile -t imageName:version1 /media/home/node.js.developers.kh/projects/p1

نکات مهم:

  • دستور `docker build` توسط daemon داکر اجرا میشه نه CLI داکر. یعنی کل context رو کپی میکنه و به daemon داکر میده
  • اگه خواستی یه دایرکتوری یا فایل رو از context در بیاری میتونی اونا رو توی فایل .dockerignore بزاری.
  • حواست باشه که یه وقتی / رو به عنوان context ندی، فکر کنم بتونی حدس بزنی چرا.

مثلا توی این تصویر به خوبی میتونی ببینی که من test.md رو با این فرض که /. به دایرکتوری mmm اشاره داره توی workdir کپی کردم.

همونطوری که توی تصویر مشخصه تو میتونی با فلگ f بگی Dockerfile کجاست.

docker build -f /path/to/a/docker-file .

فلگ t هم برای مشخص کردن اسم ایمیج، ریپوزیتوریش و ورژنش استفاده میشه.

docker build -t 9109679196/myapp .

خوبی داکرفایل اینه که قبل از اینکه اجرا بشه daemon داکر یه سری validation روش میره و اگه اروری بود اونو بر میگردونه. همون طوری که توی تصویر بالا به وضوح مشخصه کاری که داکر میکنه اینه که به ازای هر instruction، اگه لازم باشه یه ایمیج میسازه و تغییرات رو توش commit میکنه.


نکته کنکوری

پس دستور `RUN cd /app` هیچ تاثیری روی instruction های بعدی نداره. مثلا من خودم میومدم یه همچین چیزی مینوشتم:

# ... WORKDIR /usr/src/app RUN cd /usr/share/nginx/html RUN echo &quot<h1>HI</h1>&quot > index.html # ...

و انتظار داشتم که فایل index.html توی مسیر /usr/share/nginx/html/ ایجاد بشه. و خب طبیعتا ایجاد نمیشه و این فایل توی مسیر WORKDIR ایجاد میشه.

استفاده از cache

داکر برای سرعت دادن به فرآیند build ایمیج میاد از ایمیج های intermediate ای که دفعه قبلی ساخته بود استفاده میکنه ولی این در صورتی اتفاق میفته که شما توی instruction ها دست نبرده باشی. به عبارت ساده تر تا اونجایی از داکرفایلت که تغییر نکرده از cache استفاده میکنه وگرنه از نو ایمیج های intermediate رو میسازه.

ابزار BuildKit

این ابزار بهت اجازه میده که build رو خیلی راحت تر انجام بدی. برای اطلاعات بیشتر این ریپوزیتوری گیت هاب شون.


فرمت نوشتن instruction های Dockerfile اینه که اول خود دستور میاد و بعدش آرگومان هایی که دریافت میکنه.

# comment INSTRUCTION arguments

قرارداد اینه که instruction ها رو UPPERCASE بنویسیم ولی در کل داکر case-sensitive نیست.

مشخص کردن ایمیج

هر داکرفایلی باید با `FROM` شروع بشه. این دستور ایمیج parent رو مشخص میکنه. البته یه سری instruction میتونه قبلش بیاد:

ARG NODE_VERSION FROM node:${NODE_VERSION}-alpine

و وقتی میخوای اجراش بکنی اینجوری میتونی اون آرگومان رو به داکرفایل پاس بدی:

docker build -e NODE_VERSION=14.15.0 .

در ضمن شما میتونی برای build گرفتن ایمیج چند تا stage داشته باشی و به هر ایمیج یه اسم مشخص بدی:

FROM alpine:latest as builder RUN apk --no-cache add build-base FROM builder as build1 COPY source1.cpp source.cpp RUN g++ -o /binary source.cpp FROM builder as build2 COPY source2.cpp source.cpp RUN g++ -o /binary source.cpp

برای مطالعه بیشتر در مورد اینکه چجوری چند تا stage داشته باشی این داکیومنت رو بخون.

کپی کردن

برای کپی کردن فایل توی ایمیجی که میخوای بسازی باید اون فایل کنار Dockerfile ای که نوشتی باشه. مثلا:

COPY file.zip /usr/src/app

اجرای دستور

برای اجرای دستور روی ایمیجی که میخوای بسازی توی Dockerfile میتونی اینجوری کار بکنی:

RUN apt-get update

افزودن CMD

برای اینکه یه دستوری رو به ایمیج ساخته شده معرفی بکنی که همیشه اونو اجرا بکنه، و جتی اگه بنا به هر دلیلی اون دستور رو نتونست بالا نگه داره (یعنی در حال اجرا نگهش داره) بیاد کانتینر رو پاک کنه و دوباره کانتینر بسازه.

CMD [ &quotnpm&quot, &quotstart&quot ]



بررسی best practice های Dockerfile نوشتن

  • تا حد ممکن هر کانتینری که توسط Dockerfile ایجاد میشه رو ephemeral نگه دار. یعنی کمترین وابستگی بین مراحل رو داشته باش. البته تا حد ممکن.
  • برای کاهش دادن حجم ایمیج نهایی و همین طور کوتاه تر کردن زمان build شده ایمیج Dockerfile تون رو multi stage بنویسید.

این داستان کاهش دادن زمان build شده رو این طوری ببینید که دارید اون بخشی رو که کمتر تغییر میکنه تو یه stage جدا build میکنید و همین طور که به سمت انتهای Dockerfile میریم بخش هایی که تغییر زیاد میکنن رو بنویسیم. مثلا:

  • ابزار هایی که برای build اپلیکیشن تون لازم دارید تو یه stage نصب کنید.
  • حالا تو یه stage دیگه lib/bin هایی که لازم دارید رو نصب/اپدیت کنید
  • حالا تو stage دیگه بیاید اپلیکیشنتون رو build کنید.

مثلا این یه Dockerfile برای یه پروژه NestJS ای:

FROM node:14.15.0 AS builder ENV NODE_ENV build ENV APP_PORT=3000 WORKDIR /usr/src/app # If you have troubles with node-gyp use should install these dependencies # ref: https://gist.github.com/nzvtrk/cba2970b1df9091b520811e521d9bd44 # RUN apk add g++ make python COPY package*.json ./ RUN npm ci COPY . ./ COPY .* ./ RUN npm run build && npm prune --production FROM node:14.15.0 ENV NODE_ENV production WORKDIR /usr/src/app COPY --from=builder /usr/src/app/node_modules ./ COPY --from=builder /usr/src/app/package*.json ./ COPY --from=builder /usr/src/app/dist/ ./ ENTRYPOINT [ &quotnpm&quot, &quotrun&quot ] EXPOSE ${APP_PORT} CMD [ &quotstart:prod&quot ]

https://github.com/nodejsdeveloperskh/project-schema/tree/nestjs-mongoose

رفرنس

داکیومنت خود داکر برای داکر فایل

رفرنس (برای COPY)

dockerdockerfilenodejsdeveloperskhnestjsنود جی‌اس
برنانه نویس، مدرس، محقق. عاشق انیمه هستم و دنبال چالش ها جدید.
شاید از این پست‌ها خوشتان بیاید