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

فایل .dockerignore

توی این درس یاد می‌گیری فایل .dockerignore چه می‌کند: فهرست فایل‌ها و پوشه‌هایی که از build context بیرون می‌مانند و در نتیجه نه به builder فرستاده می‌شوند و نه می‌توانند با COPY . . وارد image شوند. سه اثر مهم می‌بینی: سرعت (context کوچک‌تر)، کش بهتر (تغییر فایل‌های بی‌ربط کش را باطل نمی‌کند) و امنیت (.env و کلیدها وارد image نمی‌شوند). الگوها (*، **، !) را هم با مثال واقعی تمرین می‌کنی.

وقتی به سفر می‌روی، همه‌ی خانه را در چمدان نمی‌گذاری؛ فقط چیزهای لازم. .dockerignore فهرست «چیزهایی که هرگز در چمدان نمی‌گذارم» است: آشغال‌ها (node_modules که دوباره نصب می‌شود)، چیزهای خصوصی (کلید و رمز) و چیزهای بزرگ بی‌ربط (.git). چمدان سبک‌تر هم سریع‌تر جابه‌جا می‌شود و هم کسی با باز کردنش به رازهای خانه‌ات دست نمی‌یابد.

چه چیزی به builder می‌رسد؟

Section titled “چه چیزی به builder می‌رسد؟”
.dockerignore روی سیستم تو و قبل از ارسال context اعمال می‌شود؛ پس فایل‌های ignore‌شده نه ارسال می‌شوند نه قابل COPY هستند.

مثال ۱: پروژه‌ای با آشغال و راز

Section titled “مثال ۱: پروژه‌ای با آشغال و راز”

یک پروژه‌ی نمونه می‌سازیم که مثل پروژه‌ی واقعی چیزهای اضافه دارد:

Terminal window
mkdir -p proj/node_modules/big proj/.git/objects proj/src
echo "console.log('app')" > proj/src/app.js
echo "DB_PASSWORD=super-secret-123" > proj/.env
echo "debug line" > proj/debug.log
dd if=/dev/zero of=proj/node_modules/big/blob.bin bs=1m count=30 2>/dev/null
dd if=/dev/zero of=proj/.git/objects/pack.bin bs=1m count=10 2>/dev/null
find proj -type f | sort
خروجی
proj/.env
proj/.git/objects/pack.bin
proj/debug.log
proj/node_modules/big/blob.bin
proj/src/app.js
proj/Dockerfile
FROM alpine
WORKDIR /app
COPY . .
CMD ["sh", "-c", "ls -A /app"]

حالا بدون .dockerignore build می‌کنیم و ببین چقدر داده فرستاده می‌شود:

Terminal window
docker build --no-cache --progress=plain -t lx-ig1 ./proj 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done' | sed -E 's/ [0-9.]+s done//'
خروجی
#4 transferring context: 41.95MB

حدود ۴۰ مگابایت برای یک پروژه‌ی تقریباً خالی! node_modules و .git هم رفتند.

Terminal window
docker run --rm lx-ig1
echo "--- محتوای .env داخل image:"
docker run --rm lx-ig1 cat /app/.env
خروجی
.env
.git
Dockerfile
debug.log
node_modules
src
--- محتوای .env داخل image:
DB_PASSWORD=super-secret-123

فایل .env (و .git و node_modules و debug.log) داخل image است و هر کسی که image را بگیرد می‌تواند رمز را بخواند. image منتشر‌شده در registry عمومی یعنی رمز منتشر شده.

مثال ۳: اضافه کردن .dockerignore

Section titled “مثال ۳: اضافه کردن .dockerignore”
proj/.dockerignore
# وابستگی‌ها و تاریخچه
node_modules
.git
# رازها و فایل‌های محلی
.env
*.log
Terminal window
docker build --no-cache --progress=plain -t lx-ig2 ./proj 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done' | sed -E 's/ [0-9.]+s done//'
docker run --rm lx-ig2
echo "--- تلاش برای خواندن .env:"
docker run --rm lx-ig2 cat /app/.env 2>&1
خروجی
#3 transferring context: 155B
#4 transferring context: 236B
.dockerignore
Dockerfile
src
--- تلاش برای خواندن .env:
cat: can't open '/app/.env': No such file or directory

حالا context فقط چند بایت است، و داخل image فقط Dockerfile، .dockerignore و src هست؛ .env بیرون ماند و cat می‌گوید فایل وجود ندارد. قاعده‌ی فایل: هر خط یک الگو؛ خط‌های # توضیح‌اند؛ مسیرها نسبت به ریشه‌ی context هستند.

⚡ بررسی سریع

.dockerignore در چه مرحله‌ای اعمال می‌شود؟

مثال ۴: الگوها: *، ** و استثنا با !

Section titled “مثال ۴: الگوها: *، ** و استثنا با !”
Terminal window
mkdir -p pat/src/lib pat/docs
for f in pat/a.log pat/src/b.log pat/src/lib/c.log pat/notes.md pat/README.md pat/docs/guide.md pat/keep.log pat/main.txt; do echo x > $f; done
printf 'FROM alpine\nCOPY . /ctx\nCMD ["sh","-c","cd /ctx && find . -type f | sort"]\n' > pat/Dockerfile
cat > pat/.dockerignore <<'LXEOF'
*.log
**/*.log
*.md
!README.md
LXEOF
docker build -q -t lx-ig3 ./pat >/dev/null 2>&1
docker run --rm lx-ig3
خروجی
./.dockerignore
./Dockerfile
./README.md
./docs/guide.md
./main.txt
  • *.log فقط فایل‌های .log در ریشه را ignore می‌کند (a.log)؛ **/*.log در هر عمقی (src/b.log و src/lib/c.log).
  • *.md همه‌ی markdown های ریشه را حذف می‌کند ولی !README.md یک استثنا است: README.md می‌ماند.
  • در خروجی keep.log و a.log نیستند، ولی main.txt و README.md هستند. docs/guide.md هم ماند چون *.md فقط ریشه را می‌گیرد.

آخرین الگویی که با یک فایل تطبیق کند، برنده است:

Terminal window
printf '*.md\n!README.md\n' > pat/.dockerignore
docker build -q -t lx-ig3 ./pat >/dev/null 2>&1; echo "ترتیب A (استثنا بعد): $(docker run --rm lx-ig3 | tr '\n' ' ')"
printf '!README.md\n*.md\n' > pat/.dockerignore
docker build -q -t lx-ig3 ./pat >/dev/null 2>&1; echo "ترتیب B (استثنا قبل): $(docker run --rm lx-ig3 | tr '\n' ' ')"
خروجی
ترتیب A (استثنا بعد): ./.dockerignore ./Dockerfile ./README.md ./a.log ./docs/guide.md ./keep.log ./main.txt ./src/b.log ./src/lib/c.log
ترتیب B (استثنا قبل): ./.dockerignore ./Dockerfile ./a.log ./docs/guide.md ./keep.log ./main.txt ./src/b.log ./src/lib/c.log

اگر !README.md قبل از *.md بیاید، *.md آن را دوباره ignore می‌کند و README.md از دست می‌رود. استثنا باید بعد از الگوی کلی باشد.

مثال ۶: ببین دقیقاً چه چیزی وارد می‌شود

Section titled “مثال ۶: ببین دقیقاً چه چیزی وارد می‌شود”

راه عملی برای اطمینان از اینکه .dockerignore درست است: یک image موقت با COPY . /ctx بساز و محتوایش را بشمار (همان کاری که در مثال ۴ کردیم). یا اندازه‌ی context را در خروجی build ببین (مثال ۱ و ۳). بعد از نوشتن هر قاعده، این بررسی را انجام بده تا چیز لازمی را ignore نکرده باشی.

.dockerignore توسط CLI (کلاینت build) روی سیستم تو خوانده و اعمال می‌شود؛ قبل از اینکه چیزی به builder برسد. پس:

  • فایل‌های ignore‌شده هیچ‌وقت به daemon فرستاده نمی‌شوند؛ هم سرعت می‌گیری و هم رازها نشت نمی‌کنند.
  • COPY فقط به فایل‌های باقی‌مانده دسترسی دارد؛ تلاش برای کپی یک فایل ignore‌شده با not found شکست می‌خورد (اشتباه ۱).
  • BuildKit می‌تواند برای هر Dockerfile یک فایل ignore جدا بپذیرد (Dockerfile.dockerignore کنار Dockerfile)؛ اگر وجود داشته باشد، بر .dockerignore عمومی مقدم است.

الگوها سبک .gitignore هستند ولی همه‌ی ویژگی‌هایشان یکی نیست: مسیرها نسبت به ریشه‌ی context و بر پایه‌ی Match در Go (filepath.Match) تعریف می‌شوند، و ** برای هر تعداد پوشه است.

الگو معنی
node_modules پوشه‌ی node_modules در ریشه
**/node_modules node_modules در هر عمقی
*.log فایل‌های .log در ریشه
**/*.log فایل‌های .log در هر عمقی
.env فایل .env در ریشه
!keep.txt استثنا: این را نگه دار (باید بعد از الگوی کلی باشد)
# متن توضیح
پروژه الگوهای معمول
Node node_modules، npm-debug.log، .env، .git، dist (اگر در build ساخته می‌شود)
Python __pycache__، *.pyc، .venv، .env، .git
عمومی .git، .gitignore، Dockerfile، docker-compose*.yml، README.md (اختیاری)، *.log

۱) ignore کردن چیزی که COPY لازم دارد

Section titled “۱) ignore کردن چیزی که COPY لازم دارد”
Terminal window
mkdir -p m1
echo '{}' > m1/package.json
printf 'FROM alpine\nCOPY package.json /app/\n' > m1/Dockerfile
echo "package.json" > m1/.dockerignore
docker build -t lx-ig4 ./m1 2>&1 | grep -E 'ERROR' | head -1 | sed -E 's/ref [^ ]+/ref .../' | cut -c1-120
خروجی
#6 ERROR: failed to calculate checksum of ref ... "/package.json": not found

فایل ignore شد پس COPY آن را نمی‌بیند (not found). راه‌حل: فایل‌های لازم را ignore نکن؛ اگر خطای عجیب not found گرفتی، اول .dockerignore را بررسی کن.

۲) node_modules فقط در ریشه ignore می‌شود

Section titled “۲) node_modules فقط در ریشه ignore می‌شود”
Terminal window
mkdir -p m2/src/node_modules
echo x > m2/src/node_modules/leak.txt
echo node_modules > m2/.dockerignore
printf 'FROM alpine\nCOPY . /c\nCMD ["sh","-c","find /c -name leak.txt"]\n' > m2/Dockerfile
docker build -q -t lx-ig4 ./m2 >/dev/null 2>&1
echo "با node_modules:"; docker run --rm lx-ig4
echo "**/node_modules" > m2/.dockerignore
docker build -q -t lx-ig4 ./m2 >/dev/null 2>&1
echo "با **/node_modules:"; docker run --rm lx-ig4 | wc -l | tr -d ' '
خروجی
با node_modules:
/c/src/node_modules/leak.txt
با **/node_modules:
0

الگوی node_modules فقط پوشه‌ی ریشه را گرفت و src/node_modules نشت کرد. راه‌حل: **/node_modules برای هر عمق.

فایل باید دقیقاً .dockerignore باشد و در ریشه‌ی context (کنار آنچه به docker build می‌دهی). dockerignore بدون نقطه یا .dockerignore.txt نادیده گرفته می‌شود و هیچ خطایی هم نمی‌دهد. راه‌حل: اندازه‌ی context در خروجی build را بعد از ساختن فایل بررسی کن.

مثال ۵: !README.md قبل از *.md اثر ندارد. راه‌حل: استثنا بعد از الگوی کلی.

۵) فکر کردن «.gitignore کافی است»

Section titled “۵) فکر کردن «.gitignore کافی است»”

.gitignore برای git است؛ docker build آن را نمی‌خواند. فایل‌هایی که در git ignore‌اند (مثل .env) اگر در .dockerignore نباشند، COPY . . آن‌ها را برمی‌دارد. راه‌حل: هر دو فایل را جدا نگه دار.

✎ تمرینآسان

یک .dockerignore بنویس که فقط فایل‌های .log ریشه را ignore کند و ثابت کن debug.log وارد image نمی‌شود.

دیدن جواب
Terminal window
mkdir -p e1 && echo x > e1/debug.log && echo y > e1/keep.txt
printf 'FROM alpine\nCOPY . /c\nCMD ["ls","/c"]\n' > e1/Dockerfile
echo "*.log" > e1/.dockerignore
docker build -q -t lx-ig4 ./e1 >/dev/null 2>&1
docker run --rm lx-ig4
خروجی
Dockerfile
keep.txt
✎ تمرینمتوسط

تمرین اصلی: context یک پروژه را قبل و بعد از .dockerignore مقایسه کن. پوشه‌ی big با یک فایل ۲۰ مگابایتی و یک app.txt بساز، با COPY . /x build کن، اندازه‌ی context را بخوان، big را ignore کن و دوباره بخوان.

دیدن جواب
Terminal window
mkdir -p e2/big && dd if=/dev/zero of=e2/big/f.bin bs=1m count=20 2>/dev/null && echo a > e2/app.txt
printf 'FROM alpine\nCOPY . /x\n' > e2/Dockerfile
echo "قبل:"; docker build --no-cache --progress=plain ./e2 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done' | sed -E 's/ [0-9.]+s done//'
echo "big" > e2/.dockerignore
echo "بعد:"; docker build --no-cache --progress=plain ./e2 2>&1 | grep -E 'transferring context: [0-9.]+(MB|kB|B) [0-9.]+s done' | sed -E 's/ [0-9.]+s done//'
خروجی
قبل:
#3 transferring context: 2B
#4 transferring context: 20.98MB
بعد:
#3 transferring context: 44B
✎ تمرینسخت

یک .dockerignore بنویس که همه‌ی فایل‌های .md را ignore کند ولی README.md و docs/API.md را نگه دارد. با یک image موقت ثابت کن دقیقاً این‌ها وارد شدند.

دیدن جواب
Terminal window
mkdir -p e3/docs && for f in e3/README.md e3/notes.md e3/docs/API.md e3/docs/internal.md; do echo x > $f; done
printf 'FROM alpine\nCOPY . /c\nCMD ["sh","-c","cd /c && find . -name \\"*.md\\" | sort"]\n' > e3/Dockerfile
printf '**/*.md\n!README.md\n!docs/API.md\n' > e3/.dockerignore
docker build -q -t lx-ig4 ./e3 >/dev/null 2>&1
docker run --rm lx-ig4
خروجی
./README.md
./docs/API.md

استثناها (!) بعد از الگوی کلی آمده‌اند؛ پس فقط آن دو فایل ماندند.

؟ آزمونک
  1. .dockerignore چه کاری می‌کند؟

  2. مهم‌ترین خطر COPY . . بدون .dockerignore؟

  3. کدام الگو node_modules را در هر عمقی ignore می‌کند؟

  4. ترتیب درست برای ignore کردن همه‌ی md به‌جز README.md؟

  5. آیا docker build فایل .gitignore را می‌خواند؟

  • .dockerignore (در ریشه‌ی context) فایل‌هایی را که نباید به builder برسند مشخص می‌کند: node_modules، .git، .env، لاگ‌ها.
  • اثرها: context کوچک و build سریع‌تر، کش پایدارتر، و جلوگیری از نشت رازها به image.
  • الگوها: *.log (ریشه)، **/*.log (هر عمق)، !استثنا (بعد از الگوی کلی)، # توضیح.
  • .gitignore جداست؛ فایل باید دقیقاً .dockerignore باشد.
  • اندازه‌ی context را در خروجی docker build --progress=plain بخوان و با یک image موقت محتوا را بررسی کن.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
node_modulesپوشه‌ی node_modules در ریشه
**/node_modulesnode_modules در هر عمق
.envفایل رازها
.gitتاریخچه‌ی git
*.logلاگ‌های ریشه
**/*.logلاگ‌ها در هر عمق
!keep.txtاستثنا (بعد از الگوی کلی)
docker build --progress=plain .دیدن اندازه‌ی context
COPY . /ctx + find /ctxبررسی اینکه چه چیزی وارد شد