رفتن به محتوا
LoopX

اولین Dockerfile

توی این درس یاد می‌گیری تا حالا image ها را از Docker Hub می‌گرفتی؛ حالا خودت image می‌سازی. می‌فهمی Dockerfile چیست (یک فایل متنی که دستور ساخت image را می‌نویسد)، build context یعنی چه و چرا نقطه‌ی آخر docker build -t myapp . مهم است، چطور به image اسم و tag می‌دهی (-t) و آن را با docker run اجرا می‌کنی. در انتها یک image می‌سازی که یک صفحه‌ی HTML سرو می‌کند.

Dockerfile مثل دستور پخت است: فهرست مواد (پایه‌ی کار) و مراحل کار به ترتیب. وقتی آن را به آشپز می‌دهی (docker build)، یک غذای آماده‌ی بسته‌بندی‌شده (image) تحویل می‌گیری که هر وقت خواستی گرمش کنی (docker run). فرق مهم: Dockerfile متن است و می‌توانی آن را در git نگه داری و با همکارت به اشتراک بگذاری؛ هر کس از رویش build کند، همان image را می‌گیرد.

Dockerfile و فایل‌های پروژه (context) به docker build داده می‌شوند و یک image می‌سازند؛ docker run از آن کانتینر می‌سازد.

مثال ۱: کوچک‌ترین Dockerfile

Section titled “مثال ۱: کوچک‌ترین Dockerfile”

یک پوشه‌ی خالی با یک فایل به اسم دقیق Dockerfile (بدون پسوند):

Dockerfile
FROM alpine
CMD ["echo", "سلام از داخل image من"]

حالا ساختش (-t اسم می‌دهد؛ نقطه‌ی آخر context است، یعنی «پوشه‌ی فعلی»):

Terminal window
docker build -t lx-hello . 2>&1 | grep -E '^#[0-9]+ (\[|DONE)|naming to'
docker run --rm lx-hello
خروجی
#1 [internal] load build definition from Dockerfile
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/alpine:latest
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 DONE 0.0s
#4 [1/1] FROM docker.io/library/alpine:latest@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
#5 naming to docker.io/library/lx-hello:latest done
#5 DONE 0.0s
سلام از داخل image من
  • FROM alpine: شروع از image پایه‌ی alpine. هر Dockerfile با FROM شروع می‌شود.
  • CMD [...]: دستوری که وقتی کانتینر شروع شد اجرا شود.

خروجی docker build مرحله‌به‌مرحله است: هر #N یک گام است (load build definition خواندن Dockerfile، load metadata گرفتن اطلاعات image پایه، …). در پایان naming to ... اسم image را می‌گوید.

مثال ۲: image که یک صفحه‌ی HTML سرو می‌کند

Section titled “مثال ۲: image که یک صفحه‌ی HTML سرو می‌کند”

تمرین اصلی درس. به جای alpine از nginx:alpine شروع می‌کنیم و صفحه‌ی خودمان را داخلش می‌گذاریم:

site/index.html
<!doctype html>
<html lang="fa" dir="rtl">
<head><meta charset="utf-8"><title>سایت من</title></head>
<body><h1>سلام از image خودم!</h1></body>
</html>
site/Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
Terminal window
docker build -t lx-site:1.0 ./site 2>&1 | grep -E '^#[0-9]+ \[|naming to'
docker run -d --name lx-fd1 -p 8350:80 lx-site:1.0 >/dev/null
sleep 2
curl -s localhost:8350 | grep h1
خروجی
#1 [internal] load build definition from Dockerfile
#2 [internal] load metadata for docker.io/library/nginx:alpine
#3 [internal] load .dockerignore
#4 [internal] load build context
#5 [1/2] FROM docker.io/library/nginx:alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
#6 [2/2] COPY index.html /usr/share/nginx/html/index.html
#7 naming to docker.io/library/lx-site:1.0 done
<body><h1>سلام از image خودم!</h1></body>

اینجا به‌جای . مسیر ./site را داده‌ایم: context می‌تواند هر پوشه‌ای باشد، و Dockerfile داخل آن پوشه خودکار پیدا می‌شود. دستور COPY مبدأ مقصد فایل را از context (پوشه‌ی پروژه) به داخل image کپی می‌کند. image نهایی همه‌چیز لازم برای اجرا را دارد: nginx، تنظیماتش و صفحه‌ی ما. حالا این image را می‌توانی به هر جایی ببری (درس tag و push).

⚡ بررسی سریع

در docker build -t myapp . نقطه‌ی آخر یعنی چه؟

وقتی docker build می‌زنی، داکر کل محتوای context را برای builder می‌فرستد. اگر پوشه‌ات پر از فایل‌های بی‌ربط باشد، build کند می‌شود:

Terminal window
mkdir -p ctx-big
printf 'FROM alpine\nCOPY . /app\nCMD ["ls","/app"]\n' > ctx-big/Dockerfile
dd if=/dev/zero of=ctx-big/big.bin bs=1m count=40 2>/dev/null
echo "با فایل ۴۰ مگابایتی:"
docker build --no-cache -t lx-ctx ./ctx-big 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done'
rm ctx-big/big.bin
echo "بعد از پاک کردنش:"
docker build --no-cache -t lx-ctx ./ctx-big 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done'
docker rmi lx-ctx >/dev/null
خروجی
با فایل ۴۰ مگابایتی:
#5 transferring context: 41.95MB 0.6s done
بعد از پاک کردنش:
#4 transferring context: 31B 0.0s done

این Dockerfile با COPY . /app کل context را لازم دارد. دفعه‌ی اول یک فایل ۴۰ مگابایتی در context بود و داکر حدود ۴۰ مگابایت را فرستاد؛ بعد از پاک کردنش فقط چند بایت. (BuildKit هوشمند است و فقط فایل‌هایی را می‌فرستد که واقعاً لازم‌اند؛ ولی با COPY . . یعنی همه.) (درس .dockerignore نشان می‌دهد چطور فایل‌های اضافه را از context بیرون نگه داری.) اگر Dockerfile در پوشه‌ی دیگری است، با -f مسیر معرفی‌اش می‌کنی: docker build -f deploy/Dockerfile ..

-t اسم:tag به image برچسب می‌دهد؛ می‌توانی چند -t بدهی:

Terminal window
docker build -t lx-site:1.0 -t lx-site:latest ./site 2>&1 | grep -c "naming to"
docker images lx-site --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
خروجی
2
REPOSITORY TAG IMAGE ID
lx-site 1.0 5f080625ebb7
lx-site latest 5f080625ebb7

هر دو tag به یک image (یک ID) اشاره می‌کنند. اگر -t ندهی، image فقط با شناسه‌ی هش ساخته می‌شود و اسم ندارد (dangling)؛ پس همیشه -t بگذار.

مثال ۵: بازسازی بعد از تغییر

Section titled “مثال ۵: بازسازی بعد از تغییر”
Terminal window
cat > site/index.html <<'LXEOF'
<!doctype html>
<html lang="fa" dir="rtl">
<head><meta charset="utf-8"><title>سایت من</title></head>
<body><h1>نسخه‌ی دوم</h1></body>
</html>
LXEOF
docker build -t lx-site:2.0 ./site 2>&1 | grep -E '^#[0-9]+ \[|CACHED'
docker run -d --name lx-fd2 -p 8351:80 lx-site:2.0 >/dev/null; sleep 2
curl -s localhost:8351 | grep h1
curl -s localhost:8350 | grep h1
خروجی
#1 [internal] load build definition from Dockerfile
#2 [internal] load metadata for docker.io/library/nginx:alpine
#3 [internal] load .dockerignore
#4 [internal] load build context
#5 [1/2] FROM docker.io/library/nginx:alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
#6 [2/2] COPY index.html /usr/share/nginx/html/index.html
#6 CACHED
<body><h1>نسخه‌ی دوم</h1></body>
<body><h1>سلام از image خودم!</h1></body>

بعد از عوض کردن فایل، image جدید ساختیم و کانتینر جدید تغییر را نشان داد؛ ولی کانتینر قبلی (روی ۸۳۵۰) هنوز نسخه‌ی قدیمی را دارد. image ثابت است: عوض کردن فایل روی دیسک، image موجود را عوض نمی‌کند؛ باید دوباره build کنی و کانتینر را از image تازه بسازی.

مثال ۶: ببین داخل image چیست

Section titled “مثال ۶: ببین داخل image چیست”
Terminal window
docker history lx-site:2.0 --format 'table {{.CreatedBy}}\t{{.Size}}' | head -4
docker image inspect lx-site:2.0 --format 'تعداد لایه‌ها: {{len .RootFS.Layers}} | دستور پیش‌فرض: {{.Config.Cmd}}'
خروجی
CREATED BY SIZE
COPY index.html /usr/share/nginx/html/index.… 24.6kB
RUN /bin/sh -c set -x && apkArch="$(cat … 51.4MB
ENV ACME_VERSION=0.4.1 0B
تعداد لایه‌ها: 9 | دستور پیش‌فرض: [nginx -g daemon off;]

بالاترین ردیف‌ها لایه‌ی COPY ما (چند بایت) روی لایه‌های nginx:alpine است. image ما = image پایه + یک لایه‌ی کوچک.

پشت پرده: وقتی docker build اجرا می‌شود

Section titled “پشت پرده: وقتی docker build اجرا می‌شود”

۱) CLI محتوای context (و Dockerfile) را برای builder می‌فرستد (BuildKit، که داخل daemon است). ۲) builder دستورهای Dockerfile را یکی‌یکی اجرا می‌کند؛ هر دستور مؤثر (مثل RUN و COPY) یک لایه‌ی جدید می‌سازد. ۳) نتیجه یک image (فهرست لایه‌ها + تنظیمات) است که با هش مشخص می‌شود و نام و tag به آن چسبانده می‌شود. چون هر مرحله را می‌شود کش کرد، build دوم معمولاً خیلی سریع‌تر است (درس لایه‌ها و کش).

دو نکته‌ی مهم: Dockerfile فقط یک دستور ساخت است، نه محیط اجرا: دستورهایش هنگام build اجرا می‌شوند، نه هنگام run (به‌جز CMD که فقط ثبت می‌شود). و context محدوده‌ی دید build است: COPY فقط به فایل‌های داخل context دسترسی دارد (اشتباه ۲ را ببین).

دستور Dockerfile کار
FROM image image پایه (اولین دستور)
COPY src dst کپی از context به image
CMD [...] دستور پیش‌فرض اجرای کانتینر
دستور / گزینه کار
docker build -t N:T . ساخت با اسم و tag از context فعلی
docker build -f FILE . Dockerfile با اسم یا مسیر دیگر
docker build -t a:1 -t a:latest . چند tag برای یک image
docker build --no-cache . بدون استفاده از کش
docker run IMAGE اجرای کانتینر از image ساخته‌شده
Terminal window
mkdir -p empty-dir
docker build -t lx-none ./empty-dir 2>&1 | grep -E 'ERROR'
خروجی
ERROR: failed to build: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory

در پوشه‌ی فعلی فایلی به اسم دقیق Dockerfile (با D بزرگ، بدون پسوند) نیست. راه‌حل: پوشه‌ی درست را بررسی کن یا با -f مسیر را بده.

۲) COPY فایلی که خارج از context است

Section titled “۲) COPY فایلی که خارج از context است”
Terminal window
mkdir -p ctx-bad
printf 'FROM alpine\nCOPY ../outside.txt /x\n' > ctx-bad/Dockerfile
echo hi > outside.txt
docker build -t lx-bad ./ctx-bad 2>&1 | grep -E 'ERROR'
خروجی
#5 ERROR: failed to calculate checksum of ref 3uko94hpx8qfnep9m1ut84163::rrlhl6xio3ek92jg27hvkfh3f: "/outside.txt": not found
ERROR: failed to build: failed to solve: failed to compute cache key: failed to calculate checksum of ref 3uko94hpx8qfnep9m1ut84163::rrlhl6xio3ek92jg27hvkfh3f: "/outside.txt": not found

COPY فقط به داخل context دسترسی دارد؛ .. از آن بیرون می‌زند. راه‌حل: فایل را داخل پوشه‌ی پروژه بگذار، یا context را عوض کن (docker build -f proj/Dockerfile .).

۳) فراموش کردن context (نقطه‌ی آخر)

Section titled “۳) فراموش کردن context (نقطه‌ی آخر)”
Terminal window
docker build -t lx-x 2>&1 | head -2
خروجی
ERROR: docker: 'docker buildx build' requires 1 argument

docker build یک مسیر context می‌خواهد. راه‌حل: . را بنویس.

بدون -t، image بی‌اسم می‌شود و پیدا کردنش سخت. راه‌حل: همیشه -t اسم:نسخه.

۵) تغییر فایل و انتظار تغییر کانتینر قدیمی

Section titled “۵) تغییر فایل و انتظار تغییر کانتینر قدیمی”

مثال ۵: image و کانتینر موجود عوض نمی‌شوند. راه‌حل: دوباره build و کانتینر را از image جدید بساز.

✎ تمرینآسان

یک Dockerfile با FROM alpine و CMD ["echo","hello"] بساز، با اسم lx-t1 build کن و اجرایش کن.

دیدن جواب
Terminal window
mkdir -p ex1
printf 'FROM alpine\nCMD ["echo","hello"]\n' > ex1/Dockerfile
docker build -t lx-t1 ./ex1 2>&1 | grep -c "naming to"
docker run --rm lx-t1
docker rmi lx-t1 >/dev/null
خروجی
2
hello
✎ تمرینمتوسط

تمرین اصلی: یک image بساز که صفحه‌ی HTML تو (با متن خوش آمدی) را سرو کند، روی پورت 8352 اجرا کن و با curl ثابت کن متن را می‌بینی.

دیدن جواب
Terminal window
mkdir -p ex2
echo '<h1>خوش آمدی</h1>' > ex2/index.html
printf 'FROM nginx:alpine\nCOPY index.html /usr/share/nginx/html/index.html\n' > ex2/Dockerfile
docker build -t lx-t2 ./ex2 2>&1 | grep -c "naming to"
docker run -d --name lx-ex2 -p 8352:80 lx-t2 >/dev/null; sleep 2
curl -s localhost:8352
docker rm -f lx-ex2 >/dev/null; docker rmi lx-t2 >/dev/null
خروجی
2
<h1>خوش آمدی</h1>
✎ تمرینسخت

دو tag (1.0 و latest) برای یک image بساز، ثابت کن ID یکی است، بعد فایل را عوض کن و فقط 1.1 را build کن؛ ثابت کن حالا 1.0 و 1.1 ID متفاوت دارند و latest هنوز به نسخه‌ی قدیمی اشاره می‌کند (tag قابل جابه‌جایی است).

دیدن جواب
Terminal window
mkdir -p ex3
echo '<p>v1</p>' > ex3/index.html
printf 'FROM nginx:alpine\nCOPY index.html /usr/share/nginx/html/\n' > ex3/Dockerfile
docker build -t lx-t3:1.0 -t lx-t3:latest ./ex3 >/dev/null 2>&1
echo '<p>v2</p>' > ex3/index.html
docker build -t lx-t3:1.1 ./ex3 >/dev/null 2>&1
docker images lx-t3 --format '{{.Tag}} {{.ID}}' | sort
docker rmi lx-t3:1.0 lx-t3:latest lx-t3:1.1 >/dev/null
خروجی
1.0 82b51a1b686d
1.1 0015d43a8489
latest 82b51a1b686d

1.0 و latest هم‌ID هستند؛ 1.1 ID جدید دارد. latest خودکار به آخرین build نمی‌پرد؛ باید خودت tag بزنی.

؟ آزمونک
  1. Dockerfile چیست؟

  2. اولین دستور هر Dockerfile معمولاً چیست؟

  3. build context چیست؟

  4. اگر index.html را عوض کنی، کانتینر قبلاً در حال اجرا چه می‌شود؟

  5. با -t lx-site:1.0 چه می‌کنی؟

  • Dockerfile دستور ساخت image است؛ docker build -t اسم:tag . از رویش image می‌سازد و docker run اجرایش می‌کند.
  • FROM (پایه)، COPY (کپی از context)، CMD (دستور پیش‌فرض) سه دستور اولیه‌اند.
  • context پوشه‌ایست که برای build فرستاده می‌شود؛ COPY فقط به آن دسترسی دارد و حجمش روی سرعت اثر دارد.
  • image ثابت است؛ تغییر فایل یعنی build مجدد و کانتینر جدید.
  • همیشه -t بگذار؛ یک image می‌تواند چند tag داشته باشد.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
docker build -t myapp .ساخت image با اسم از context فعلی
docker build -t myapp:1.0 -t myapp:latest .چند tag
docker build -f path/Dockerfile .Dockerfile دیگر
docker run --rm myappاجرای image ساخته‌شده
docker images myappفهرست image های یک اسم
docker history myappلایه‌های image
FROM imageimage پایه
COPY src dstکپی فایل از context
CMD ["cmd","arg"]دستور پیش‌فرض