توی این درس یاد میگیری فایل .dockerignore چه میکند: فهرست فایلها و پوشههایی که از build context بیرون میمانند و در نتیجه نه به builder فرستاده میشوند و نه میتوانند با COPY . . وارد image شوند. سه اثر مهم میبینی: سرعت (context کوچکتر)، کش بهتر (تغییر فایلهای بیربط کش را باطل نمیکند) و امنیت (.env و کلیدها وارد image نمیشوند). الگوها (*، **، !) را هم با مثال واقعی تمرین میکنی.
تشبیه: چمدان سفر
Section titled “تشبیه: چمدان سفر”وقتی به سفر میروی، همهی خانه را در چمدان نمیگذاری؛ فقط چیزهای لازم. .dockerignore فهرست «چیزهایی که هرگز در چمدان نمیگذارم» است: آشغالها (node_modules که دوباره نصب میشود)، چیزهای خصوصی (کلید و رمز) و چیزهای بزرگ بیربط (.git). چمدان سبکتر هم سریعتر جابهجا میشود و هم کسی با باز کردنش به رازهای خانهات دست نمییابد.
چه چیزی به builder میرسد؟
Section titled “چه چیزی به builder میرسد؟”مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: پروژهای با آشغال و راز
Section titled “مثال ۱: پروژهای با آشغال و راز”یک پروژهی نمونه میسازیم که مثل پروژهی واقعی چیزهای اضافه دارد:
mkdir -p proj/node_modules/big proj/.git/objects proj/srcecho "console.log('app')" > proj/src/app.jsecho "DB_PASSWORD=super-secret-123" > proj/.envecho "debug line" > proj/debug.logdd if=/dev/zero of=proj/node_modules/big/blob.bin bs=1m count=30 2>/dev/nulldd if=/dev/zero of=proj/.git/objects/pack.bin bs=1m count=10 2>/dev/nullfind proj -type f | sortproj/.envproj/.git/objects/pack.binproj/debug.logproj/node_modules/big/blob.binproj/src/app.jsFROM alpineWORKDIR /appCOPY . .CMD ["sh", "-c", "ls -A /app"]حالا بدون .dockerignore build میکنیم و ببین چقدر داده فرستاده میشود:
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 هم رفتند.
مثال ۲: نشت راز
Section titled “مثال ۲: نشت راز”docker run --rm lx-ig1echo "--- محتوای .env داخل image:"docker run --rm lx-ig1 cat /app/.env.env.gitDockerfiledebug.lognode_modulessrc--- محتوای .env داخل image:DB_PASSWORD=super-secret-123فایل .env (و .git و node_modules و debug.log) داخل image است و هر کسی که image را بگیرد میتواند رمز را بخواند. image منتشرشده در registry عمومی یعنی رمز منتشر شده.
مثال ۳: اضافه کردن .dockerignore
Section titled “مثال ۳: اضافه کردن .dockerignore”# وابستگیها و تاریخچهnode_modules.git
# رازها و فایلهای محلی.env*.logdocker 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-ig2echo "--- تلاش برای خواندن .env:"docker run --rm lx-ig2 cat /app/.env 2>&1#3 transferring context: 155B#4 transferring context: 236B.dockerignoreDockerfilesrc--- تلاش برای خواندن .env:cat: can't open '/app/.env': No such file or directoryحالا context فقط چند بایت است، و داخل image فقط Dockerfile، .dockerignore و src هست؛ .env بیرون ماند و cat میگوید فایل وجود ندارد. قاعدهی فایل: هر خط یک الگو؛ خطهای # توضیحاند؛ مسیرها نسبت به ریشهی context هستند.
.dockerignore در چه مرحلهای اعمال میشود؟
فایلهای ignoreشده اصلاً به builder فرستاده نمیشوند؛ پس COPY هم نمیتواند آنها را ببیند.
مثال ۴: الگوها: *، ** و استثنا با !
Section titled “مثال ۴: الگوها: *، ** و استثنا با !”mkdir -p pat/src/lib pat/docsfor 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; doneprintf 'FROM alpine\nCOPY . /ctx\nCMD ["sh","-c","cd /ctx && find . -type f | sort"]\n' > pat/Dockerfilecat > pat/.dockerignore <<'LXEOF'*.log**/*.log*.md!README.mdLXEOFdocker build -q -t lx-ig3 ./pat >/dev/null 2>&1docker 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فقط ریشه را میگیرد.
مثال ۵: ترتیب مهم است
Section titled “مثال ۵: ترتیب مهم است”آخرین الگویی که با یک فایل تطبیق کند، برنده است:
printf '*.md\n!README.md\n' > pat/.dockerignoredocker 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/.dockerignoredocker 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 نکرده باشی.
پشت پرده
Section titled “پشت پرده”.dockerignore توسط CLI (کلاینت build) روی سیستم تو خوانده و اعمال میشود؛ قبل از اینکه چیزی به builder برسد. پس:
- فایلهای ignoreشده هیچوقت به daemon فرستاده نمیشوند؛ هم سرعت میگیری و هم رازها نشت نمیکنند.
COPYفقط به فایلهای باقیمانده دسترسی دارد؛ تلاش برای کپی یک فایل ignoreشده باnot foundشکست میخورد (اشتباه ۱).- BuildKit میتواند برای هر Dockerfile یک فایل ignore جدا بپذیرد (
Dockerfile.dockerignoreکنارDockerfile)؛ اگر وجود داشته باشد، بر.dockerignoreعمومی مقدم است.
الگوها سبک .gitignore هستند ولی همهی ویژگیهایشان یکی نیست: مسیرها نسبت به ریشهی context و بر پایهی Match در Go (filepath.Match) تعریف میشوند، و ** برای هر تعداد پوشه است.
جدولهای مرجع
Section titled “جدولهای مرجع”| الگو | معنی |
|---|---|
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 |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) ignore کردن چیزی که COPY لازم دارد
Section titled “۱) ignore کردن چیزی که COPY لازم دارد”mkdir -p m1echo '{}' > m1/package.jsonprintf 'FROM alpine\nCOPY package.json /app/\n' > m1/Dockerfileecho "package.json" > m1/.dockerignoredocker 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 میشود”mkdir -p m2/src/node_modulesecho x > m2/src/node_modules/leak.txtecho node_modules > m2/.dockerignoreprintf 'FROM alpine\nCOPY . /c\nCMD ["sh","-c","find /c -name leak.txt"]\n' > m2/Dockerfiledocker build -q -t lx-ig4 ./m2 >/dev/null 2>&1echo "با node_modules:"; docker run --rm lx-ig4echo "**/node_modules" > m2/.dockerignoredocker build -q -t lx-ig4 ./m2 >/dev/null 2>&1echo "با **/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 برای هر عمق.
۳) اسم غلط فایل
Section titled “۳) اسم غلط فایل”فایل باید دقیقاً .dockerignore باشد و در ریشهی context (کنار آنچه به docker build میدهی). dockerignore بدون نقطه یا .dockerignore.txt نادیده گرفته میشود و هیچ خطایی هم نمیدهد. راهحل: اندازهی context در خروجی build را بعد از ساختن فایل بررسی کن.
۴) ترتیب استثنا
Section titled “۴) ترتیب استثنا”مثال ۵: !README.md قبل از *.md اثر ندارد. راهحل: استثنا بعد از الگوی کلی.
۵) فکر کردن «.gitignore کافی است»
Section titled “۵) فکر کردن «.gitignore کافی است»”.gitignore برای git است؛ docker build آن را نمیخواند. فایلهایی که در git ignoreاند (مثل .env) اگر در .dockerignore نباشند، COPY . . آنها را برمیدارد. راهحل: هر دو فایل را جدا نگه دار.
یک .dockerignore بنویس که فقط فایلهای .log ریشه را ignore کند و ثابت کن debug.log وارد image نمیشود.
دیدن جواب
mkdir -p e1 && echo x > e1/debug.log && echo y > e1/keep.txtprintf 'FROM alpine\nCOPY . /c\nCMD ["ls","/c"]\n' > e1/Dockerfileecho "*.log" > e1/.dockerignoredocker build -q -t lx-ig4 ./e1 >/dev/null 2>&1docker run --rm lx-ig4Dockerfilekeep.txtتمرین اصلی: context یک پروژه را قبل و بعد از .dockerignore مقایسه کن. پوشهی big با یک فایل ۲۰ مگابایتی و یک app.txt بساز، با COPY . /x build کن، اندازهی context را بخوان، big را ignore کن و دوباره بخوان.
دیدن جواب
mkdir -p e2/big && dd if=/dev/zero of=e2/big/f.bin bs=1m count=20 2>/dev/null && echo a > e2/app.txtprintf 'FROM alpine\nCOPY . /x\n' > e2/Dockerfileecho "قبل:"; 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/.dockerignoreecho "بعد:"; 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 موقت ثابت کن دقیقاً اینها وارد شدند.
دیدن جواب
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; doneprintf 'FROM alpine\nCOPY . /c\nCMD ["sh","-c","cd /c && find . -name \\"*.md\\" | sort"]\n' > e3/Dockerfileprintf '**/*.md\n!README.md\n!docs/API.md\n' > e3/.dockerignoredocker build -q -t lx-ig4 ./e3 >/dev/null 2>&1docker run --rm lx-ig4./README.md./docs/API.mdاستثناها (!) بعد از الگوی کلی آمدهاند؛ پس فقط آن دو فایل ماندند.
آزمونک
Section titled “آزمونک”.dockerignore چه کاری میکند؟
قبل از ارسال context به builder اعمال میشود.
مهمترین خطر COPY . . بدون .dockerignore؟
هر کس image را بگیرد میتواند .env را بخواند.
کدام الگو node_modules را در هر عمقی ignore میکند؟
الگوی ساده فقط ریشه را میگیرد.
ترتیب درست برای ignore کردن همهی md بهجز README.md؟
آخرین الگوی منطبق برنده است؛ استثنا باید بعد بیاید.
آیا docker build فایل .gitignore را میخواند؟
دو فایل جدا هستند.
جمعبندی
Section titled “جمعبندی”.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_modules | node_modules در هر عمق |
.env | فایل رازها |
.git | تاریخچهی git |
*.log | لاگهای ریشه |
**/*.log | لاگها در هر عمق |
!keep.txt | استثنا (بعد از الگوی کلی) |
docker build --progress=plain . | دیدن اندازهی context |
COPY . /ctx + find /ctx | بررسی اینکه چه چیزی وارد شد |