مسئلهای که داکر حل میکند
The problem Docker solves
قبل از اینکه یک دستور بزنیم، باید بدانیم داریم چه دردی را درمان میکنیم. این فصل ابزار یاد نمیدهد؛ مدل ذهنی میسازد. اگر این فصل را درست بفهمی، چهارده فصل بعدی فقط جزئیات است.
Before typing a single command, we need to know which pain we are treating. This chapter does not teach a tool; it builds a mental model. Get this chapter right and the next fourteen are just details.
دردی که داکر درمان میکندThe pain Docker treats
داستان همیشه یکی است. برنامهات روی لپتاپ خودت اجرا میشود. آن را به یک نفر دیگر میدهی و اجرا نمیشود. این جمله را میگویی: «روی سیستم من که کار میکرد.» و راست هم میگویی.
The story is always the same. Your program runs on your laptop. You hand it to someone else and it does not run. You say the sentence: “it worked on my machine.” And you are telling the truth.
فرض کن یک برنامهٔ کوچک پایتون نوشتهای که از یک پایگاهدادهٔ PostgreSQL میخواند و یک گزارش میسازد. روی لپتاپ خودت بیعیب کار میکند. فایل را برای همکارت میفرستی. ده دقیقه بعد این عکس صفحه را میفرستد:
Say you wrote a small Python program that reads from a PostgreSQL database and produces a report. It works flawlessly on your laptop. You send the file to a colleague. Ten minutes later they send you this screenshot:
python report.py Traceback (most recent call last): File "report.py", line 3, in <module> import psycopg2 ModuleNotFoundError: No module named 'psycopg2'
میگویی: «نصبش کن.» نصب میکند. حالا این میآید:
You say: “install it.” They do. Now this shows up:
pip install psycopg2 Error: pg_config executable not found. pg_config is required to build psycopg2 from source.
حالا معلوم میشود که کتابخانهٔ پایتون به یک کتابخانهٔ سیستمی نیاز دارد که روی ماشین تو
سالها پیش موقع یک کار دیگر نصب شده بود و اصلاً یادت نیست. این دقیقاً همان چیزی است که
آدمها به آن میگویند «درد محیط» (environment drift): تفاوت بیسروصدا میان
ماشینها که تا لحظهٔ خراب شدن دیده نمیشود.
It turns out the Python library needs a system library that was installed on your machine
years ago for something unrelated and that you have long forgotten. This is exactly what people
mean by environment drift: the silent difference between machines that stays invisible
until it breaks.
سه لایهای که فرق میکنند
The three layers that differ
وقتی دو ماشین «یکسان» نیستند، تفاوت تقریباً همیشه در یکی از این سه لایه است:
When two machines are not “the same”, the difference is almost always in one of three layers:
-
نسخهٔ زبان و کتابخانههای آن
Language runtime and its libraries
تو
Python 3.12داری، او3.9. کدی که با نحو جدید نوشتهای، آنجا خطای نحوی میدهد. یاNode 22در برابرNode 18، که یک تابع در یکی هست و در دیگری نیست.You have
Python 3.12, they have3.9. Code written with newer syntax is a syntax error over there. OrNode 22versusNode 18, where a function exists in one and not the other. -
کتابخانههای سیستمی
System libraries
همان
libpq،libssl،ImageMagickو امثالش. اینها بخشی از پروژهٔ تو نیستند، پس در فایلهای پروژه هم ثبت نشدهاند. نامرئیاند تا وقتی که غایب باشند.Things like
libpq,libssl,ImageMagick. They are not part of your project, so they are not recorded in your project files. Invisible until absent. -
پیکربندی و مسیرها
Configuration and paths
متغیر محیطی که فقط در فایل
.bashrcتو هست. مسیری که روی ویندوزC:\dataاست و روی لینوکس/var/data. سرویسی که روی پورت ۵۴۳۲ بالا است چون تو دستی بالا آوردهای.An environment variable that lives only in your
.bashrc. A path that isC:\dataon Windows and/var/dataon Linux. A service listening on port 5432 because you started it by hand.
راهحلهای قبلی و اینکه چرا کافی نبودند
Earlier answers, and why they fell short
این مسئله جدید نیست و آدمها قبلاً سه جواب برایش داشتند. هر سه جواب کار میکنند، فقط هزینهٔ متفاوتی دارند.
This is not a new problem, and people had three answers for it. All three work; they just cost different amounts.
فایل «راهنمای نصب»
An install README
بیست خط دستور که باید به ترتیب اجرا شوند. مشکل: این فایل هیچوقت بهروز نمیماند. سه ماه بعد یک نسخه عوض میشود و راهنما دروغ میگوید — بدون اینکه کسی بفهمد.
Twenty commands to run in order. The problem: it never stays current. Three months later a version changes and the README is lying — and nobody notices.
ماشین مجازی
A virtual machine
واقعاً جواب میدهد: یک سیستمعامل کامل با همهچیزش. اما فایلش چند گیگابایت است، بالا آمدنش دهها ثانیه طول میکشد، و برای هر سرویس یکی لازم داری. برای ۵ سرویس، ۵ سیستمعامل.
It genuinely works: a whole operating system with everything in it. But the file is gigabytes, booting takes tens of seconds, and you need one per service. Five services, five operating systems.
ابزار پیکربندی
Configuration management
ابزارهایی مثل Ansible که نصب را خودکار میکنند. بهتر از راهنمای دستی است، اما
هنوز روی سیستمعاملِ موجود کار میکند؛ پس تفاوتهای آن سیستمعامل سر جایش میماند.
Tools like Ansible that automate the install. Better than a manual README, but they
still act on the operating system that is already there, so its differences remain.
جواب داکر از جنس دیگری است: بهجای اینکه توضیح بدهی برنامه به چه چیزهایی نیاز دارد، آن چیزها را کنار خود برنامه بستهبندی کن و همه را با هم تحویل بده. نه یک راهنما، نه یک اسکریپت، بلکه یک بستهٔ آماده که همهجا یکسان باز میشود.
Docker's answer is of a different kind: instead of describing what your program needs, package those things next to the program and ship them together. Not a README, not a script — a ready-made bundle that opens the same way everywhere.
قبل از کانتینرهای باربری، هر محموله را جداگانه در انبار کشتی میچیدند: بشکه، گونی، جعبه، هرکدام شکل خودش. بارگیری یک کشتی چند روز طول میکشید. کانتینر فولادی این را عوض کرد — نه چون هوشمند بود، بلکه چون استاندارد بود: جرثقیل، کامیون، قطار و کشتی همه با یک اندازه کار میکردند و دیگر مهم نبود داخلش چیست. داکر همین کار را با نرمافزار میکند. محتوای کانتینر تو میتواند پایتون باشد یا جاوا؛ ابزار بیرونی یکی است.
Before shipping containers, cargo was loaded piece by piece: barrels, sacks, crates, each its own shape. Loading one ship took days. The steel container changed that — not because it was clever but because it was standard: cranes, trucks, trains and ships all agreed on one size and stopped caring what was inside. Docker does the same for software. Your container may hold Python or Java; the tooling outside is identical.
داکر دقیقاً چیستWhat Docker actually is
دو جمله، بدون تعارف: داکر ابزاری است که برنامهات را همراه با تمام وابستگیهایش در یک بستهٔ
استاندارد و تغییرناپذیر به نام image (ایمیج) بستهبندی میکند. سپس آن بسته را
بهصورت یک پروسهٔ ایزوله روی هستهٔ لینوکس ماشین میزبان اجرا میکند؛ به آن پروسهٔ در حال اجرا
میگوییم container (کانتینر).
Two sentences, no ceremony: Docker packages your program together with every dependency into a
standard, immutable bundle called an image. It then runs that bundle as an isolated
process on the host machine's Linux kernel; that running process is a container.
این تعریف دو کلمهٔ مهم دارد که باید رویشان مکث کنی.
That definition has two words worth pausing on.
تغییرناپذیر (immutable) یعنی ایمیج بعد از ساختهشدن عوض نمیشود.
اگر بخواهی چیزی را تغییر بدهی، ایمیج جدید میسازی. این محدودیت به نظر آزاردهنده میرسد، اما
دقیقاً همان چیزی است که تضمین میکند ایمیجی که امروز تست کردهای، فردا روی سرور همان رفتار
را دارد. هر چیزی که بتواند بیسروصدا عوض شود، بالاخره بیسروصدا عوض میشود.
Immutable means an image does not change after it is built. To change something, you build a new image. The constraint sounds annoying, but it is exactly what guarantees that the image you tested today behaves the same on the server tomorrow. Anything that can change quietly eventually does.
روی هستهٔ ماشین میزبان یعنی کانتینر سیستمعامل خودش را ندارد. با ماشین میزبان
یک هسته (kernel) مشترک دارد و فقط فایلسیستم و دیدش جدا است. این تفاوت اصلی با
ماشین مجازی است و در بخش بعد کاملاً بازش میکنیم.
On the host's kernel means a container has no operating system of its own. It
shares one kernel with the host and only its filesystem and its view are separate.
That is the key difference from a virtual machine, and the next section unpacks it fully.
داکر چه چیزی نیست
What Docker is not
این فهرست به اندازهٔ تعریف مهم است، چون بیشتر سردرگمیهای ماه اول از همینها میآید.
This list matters as much as the definition; most first-month confusion comes from these.
ماشین مجازی نیست
Not a virtual machine
هستهٔ جداگانه ندارد. برای همین چند میلیثانیهای بالا میآید و برای همین هم ایزولاسیونش از ماشین مجازی ضعیفتر است.
It has no separate kernel. That is why it starts in milliseconds — and also why its isolation is weaker than a VM's.
برنامهات را سریعتر نمیکند
Not a speed-up
کد کند، داخل کانتینر هم کند است. داکر سربار محاسباتی تقریباً صفر دارد، اما جادو هم نمیکند.
Slow code is slow inside a container too. Docker's compute overhead is near zero, but it performs no magic.
جای معماری خوب را نمیگیرد
Not a substitute for design
یک برنامهٔ بههمریخته، داخل کانتینر میشود یک برنامهٔ بههمریختهٔ قابلحمل. قابلحمل بودن ارزش دارد، ولی مسئلهٔ دیگری را حل میکند.
A tangled application becomes a portable tangled application. Portability is worth something, but it solves a different problem.
بهتنهایی امن نیست
Not secure by itself
پیشفرضهای داکر برای راحتی تنظیم شدهاند، نه برای امنیت. فصل ۱۳ کامل به این میپردازد.
Docker's defaults are tuned for convenience, not safety. Chapter 13 is devoted to this.
سه قطعهای که باید از هم تشخیص بدهی
Three pieces you must tell apart
وقتی میگوییم «داکر»، در واقع سه چیز جدا را با یک اسم صدا میزنیم. تشخیص ندادنشان باعث میشود خطاها بیمعنی به نظر برسند.
When we say “Docker” we are calling three separate things by one name. Not telling them apart is what makes error messages look meaningless.
| قطعهPiece | چیستWhat it is | کجا میبینیاشWhere you meet it |
|---|---|---|
docker (CLI) |
دستوری که تو تایپ میکنی. خودش کاری نمیکند؛ فقط درخواست را میفرستد. The command you type. It does nothing itself; it forwards the request. | ترمینالYour terminal |
dockerd (daemon) |
سرویسی که در پسزمینه اجرا است و واقعاً کانتینرها را میسازد و میگرداند. The background service that actually creates and runs containers. | خطای «daemon در دسترس نیست» از اینجا میآید The “cannot connect to the daemon” error comes from here |
| رجیستریRegistry | انبار ایمیجها. پیشفرض Docker Hub است.
The image warehouse. Docker Hub by default. |
وقتی Pulling from… میبینی
When you see Pulling from… |
این معماری یعنی docker فقط یک پیامرسان است. وقتی این خطا را میبینی:
This architecture means docker is only a messenger. When you see this error:
docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
مشکل «داکر نصب نیست» نیست. دستور docker کاملاً سالم اجرا شده و پیامش را
فرستاده؛ کسی آن طرف خط جواب نداده. یعنی سرویس پسزمینه بالا نیست. درمانش در فصل ۲ است.
The problem is not “Docker is not installed”. The docker command ran perfectly and sent
its message; nobody answered on the other end. The background service is not up. The cure is in chapter 2.
ماشین مجازی یا کانتینر؟Virtual machine or container?
این مقایسه مهمترین نمودار این فصل است. تفاوت در یک چیز خلاصه میشود: کانتینر هستهٔ سیستمعامل را قرض میگیرد، ماشین مجازی هستهٔ خودش را میآورد.
This comparison is the most important diagram in the chapter. The difference reduces to one thing: a container borrows the operating system kernel; a virtual machine brings its own.
آن لایهٔ تکراری همان چیزی است که هزینه دارد. یک سیستمعامل کامل یعنی صدها مگابایت فایل، یک هستهٔ جدا در حافظه، و یک فرایند بوت کامل هر بار که روشنش میکنی. سه ماشین مجازی یعنی سه بار همهٔ اینها — حتی اگر هر سه دقیقاً یک اوبونتوی یکسان باشند.
That repeated layer is what costs. A full operating system means hundreds of megabytes of files, a separate kernel in memory, and a complete boot sequence every time you start it. Three VMs mean three of all that — even if all three are the identical Ubuntu.
| ماشین مجازیVirtual machine | کانتینرContainer | |
|---|---|---|
| هستهKernel | مال خودشIts own | مشترک با میزبانShared with the host |
| زمان بالا آمدنStart time | دهها ثانیه (بوت کامل)Tens of seconds (full boot) | کسری از ثانیه (فقط اجرای یک پروسه)A fraction of a second (just a process) |
| حجم روی دیسکDisk footprint | معمولاً گیگابایتTypically gigabytes | معمولاً دهها تا صدها مگابایتTypically tens to hundreds of MB |
| قدرت ایزولاسیونIsolation strength | قوی — مرز سختافزاریStrong — hardware boundary | ضعیفتر — مرز نرمافزاری هستهWeaker — kernel software boundary |
| سیستمعامل متفاوتDifferent OS | میشود (ویندوز روی لینوکس)Possible (Windows on Linux) | نمیشود — هسته یکی استNot possible — one kernel |
| مناسب برایGood for | اجرای کد نامطمئن، سیستمعامل متفاوت، جداسازی سخت Untrusted code, a different OS, hard separation | بستهبندی و اجرای سرویسهای خودت Packaging and running your own services |
چون هسته مشترک است، کانتینر لینوکسی فقط روی هستهٔ لینوکس اجرا میشود. اگر روی ویندوز یا مک کار میکنی، داکر پشت پرده یک ماشین مجازی سبک لینوکسی بالا آورده و کانتینرها داخل آن اجرا میشوند. این را بدان، چون بعداً وقتی دنبال پروسهٔ کانتینر روی سیستم خودت میگردی و پیدایش نمیکنی، جوابش همین است.
Because the kernel is shared, a Linux container only runs on a Linux kernel. On Windows or macOS, Docker quietly runs a lightweight Linux VM and your containers live inside it. Remember this — later, when you go looking for the container's process on your own system and cannot find it, this is the answer.
چهار قطعهای که همهچیز از آنها ساخته میشودThe four pieces everything is built from
اگر فقط چهار کلمه از این فصل یادت بماند، همینها باشند: image،
container، volume، registry. هر خطایی که در
شش ماه آینده میبینی، دربارهٔ یکی از این چهارتاست.
If only four words survive this chapter, make them these: image, container,
volume, registry. Every error you meet in the next six months is about one of them.
ایمیجImage image
یک فایلسیستم آمادهٔ فقطخواندنی، بستهبندیشده و تغییرناپذیر. همهٔ فایلهایی که برنامهات برای اجرا لازم دارد داخلش هست: خود برنامه، کتابخانهها، و حتی ابزارهای پایهٔ لینوکس.
A ready-made, read-only, immutable filesystem bundle. Everything your program needs to run is inside: the program, its libraries, even the base Linux tools.
کانتینرContainer container
یک ایمیج که در حال اجراست، بهاضافهٔ یک لایهٔ نوشتنی نازک روی آن. فناپذیر است: میسازیاش، کار میکند، حذفش میکنی — و آن لایهٔ نوشتنی با آن میرود.
An image that is running, plus a thin writable layer on top. Disposable by design: you create it, it works, you delete it — and the writable layer goes with it.
والیومVolume volume
فضای ذخیرهسازی که بیرون از کانتینر زندگی میکند و به آن وصل میشود. کانتینر را حذف کن، والیوم سر جایش میماند. اینجا جای دادهٔ پایگاهداده و فایلهای آپلودی است.
Storage that lives outside the container and is attached to it. Delete the container and the volume stays. This is where database data and uploaded files belong.
رجیستریRegistry registry
انباری که ایمیجها در آن نگهداری و به اشتراک گذاشته میشوند. پیشفرض Docker Hub
است. رابطهاش با ایمیج، مثل رابطهٔ یک مخزن Git با پروژهٔ توست.
The warehouse where images are stored and shared. Docker Hub by default. Its relation
to an image is like a Git remote's relation to your project.
رابطهٔ ایمیج و کانتینر
How image and container relate
این مهمترین رابطه در کل داکر است و بیشتر آدمها اولش قاطیاش میکنند. سادهترین تشبیهی که درست از آب درمیآید این است:
This is the most important relationship in all of Docker, and most people confuse it at first. The simplest analogy that actually holds up:
ایمیج مثل فایل نصب یک برنامه است؛ کانتینر مثل آن برنامه وقتی بازش کردهای. یک فایل نصب داری، اما میتوانی برنامه را سه بار همزمان باز کنی. هر پنجره وضعیت خودش را دارد و بستن یکی به دو تای دیگر کاری ندارد. فایل نصب هم با باز و بسته کردن پنجرهها تغییر نمیکند.
An image is like an installer; a container is like the program once you have opened it. You have one installer, but you can open the program three times at once. Each window has its own state, and closing one does nothing to the others. The installer itself never changes.
برای کسانی که برنامهنویسی شیءگرا بلدند، تشبیه دقیقتر این است: ایمیج class است
و کانتینر instance. از یک کلاس، هر تعداد شیء که بخواهی میسازی.
If you know object-oriented programming, the tighter analogy is: the image is a class
and the container is an instance. One class, as many instances as you like.
لایهها: چرا ایمیج شبیه کیک استLayers: why an image is like a cake
ایمیج یک فایل یکپارچه نیست. مجموعهای از لایههای رویهمچیدهشده است که هرکدام فقط تفاوت خودش با لایهٔ زیرین را نگه میدارد. این جزئیات پیادهسازی به نظر میرسد، اما نیست: نصف رفتارهای عجیبی که در ماه اول میبینی از همین میآید.
An image is not one solid file. It is a stack of layers, each holding only its difference from the one below. This sounds like an implementation detail. It is not: half the odd behaviour you meet in your first month comes from it.
فرض کن ایمیجی میسازی که بر پایهٔ اوبونتو است، بعد پایتون نصب میکند، بعد کتابخانههای پروژه را نصب میکند، و آخر کد خودت را کپی میکند. نتیجه چهار لایه است که روی هم چیده شدهاند. داکر این را به تو نشان میدهد:
Say you build an image based on Ubuntu, then install Python, then install project libraries, then copy in your own code. The result is four stacked layers. Docker will show them to you:
# تاریخچهٔ لایههای یک ایمیج آماده را ببین docker image history nginx:1.27-alpine IMAGE CREATED CREATED BY SIZE <image id> 3 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B <missing> 3 weeks ago STOPSIGNAL SIGQUIT 0B <missing> 3 weeks ago EXPOSE map[80/tcp:{}] 0B <missing> 3 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B <missing> 3 weeks ago COPY docker-entrypoint.sh / # buildkit 1.62kB <missing> 3 weeks ago RUN /bin/sh -c set -x && addgroup ... 41.2MB <missing> 3 weeks ago ENV NGINX_VERSION=1.27.3 0B <missing> 4 weeks ago ADD alpine-minirootfs.tar.gz / # buildkit 8.42MB
از پایین به بالا بخوانش. پایینترین لایه فایلسیستم پایهٔ آلپاین است. رویش لایهای که خود Nginx را نصب کرده. بالاتر لایههایی که فقط تنظیماتاند و صفر بایت حجم دارند، چون هیچ فایلی اضافه نکردهاند — فقط ابردادهاند.
Read it bottom-up. The bottom layer is the Alpine base filesystem. Above it, the layer that installed Nginx itself. Higher up, layers that are pure configuration and take zero bytes, because they add no files — they are only metadata.
حجمها و تاریخها به نسخهٔ ایمیج و معماری پردازندهات بستگی دارند. آنچه باید یکسان باشد ساختار است: لایههای پایین بزرگ و لایههای تنظیماتی صفربایت. در همهٔ خروجیهای این مرجع، به شکل خروجی نگاه کن نه به عددها.
Sizes and dates depend on the image version and your CPU architecture. What should match is the shape: large layers at the bottom, zero-byte configuration layers on top. Throughout this reference, read outputs for their shape, not their numbers.
چرا این طراحی؟ دو دلیل محکم
Why this design? Two solid reasons
یک: اشتراکگذاری روی دیسک. اگر ده ایمیج مختلف همه بر پایهٔ همان آلپاین باشند، آن لایهٔ پایه فقط یک بار روی دیسک ذخیره میشود. ده ایمیج ولی یک نسخه از پایه. برای همین است که ایمیج دوم و سوم خیلی سریعتر دانلود میشوند از اولی.
One: sharing on disk. If ten different images all sit on that same Alpine base, the base layer is stored on disk exactly once. Ten images, one copy of the base. That is why the second and third image download far faster than the first.
دو: کش هنگام ساخت. وقتی ایمیجت را دوباره میسازی و فقط کد خودت عوض شده، داکر لایههای پایینی را دستنخورده برمیدارد و فقط لایهٔ آخر را از نو میسازد. تفاوتش بین بیلد دو ثانیهای و بیلد دو دقیقهای است. فصل ۴ کاملاً روی همین سوار است.
Two: build cache. When you rebuild and only your own code changed, Docker reuses the lower layers untouched and rebuilds only the last one. That is the difference between a two-second and a two-minute build. Chapter 4 is built entirely on this.
لایهٔ نوشتنی — و اولین دام بزرگ
The writable layer — and the first big trap
لایههای ایمیج فقطخواندنیاند. پس وقتی کانتینر اجرا میشود و برنامه میخواهد فایلی بنویسد، چه اتفاقی میافتد؟ داکر یک لایهٔ نازک و نوشتنی روی همهٔ لایههای ایمیج اضافه میکند. هر تغییری — فایل جدید، ویرایش، حذف — فقط در آن لایه ثبت میشود.
Image layers are read-only. So when a container runs and the program wants to write a file, what happens? Docker adds a thin writable layer on top of all the image layers. Every change — a new file, an edit, a deletion — is recorded only in that layer.
وقتی کانتینر را حذف میکنی، لایهٔ نوشتنیاش هم حذف میشود و هر چیزی که داخلش
نوشته شده بود از بین میرود. اگر پایگاهدادهای را داخل کانتینر بالا آورده باشی
و بدون volume کانتینر را rm کنی، تمام رکوردها رفتهاند. برگشتی هم
ندارد. راهحل درست در فصل ۶ است؛ فعلاً فقط بدان که این خطر وجود دارد.
When you delete a container, its writable layer is deleted with it, and everything written
there is gone. If you ran a database inside a container without a volume and
then rm the container, every record is gone. There is no undo. The correct solution is
chapter 6; for now, just know the danger exists.
اولین اجراYour first run
تا اینجا حرف زدیم. حالا دست به کار شویم. برای این بخش داکر باید نصب باشد؛ اگر نیست، فصل ۲ را ببین و برگرد. هیچکدام از این دستورها به سیستم تو آسیب نمیزنند.
Enough talk. Hands on the keyboard. This section needs Docker installed; if it is not, see chapter 2 and come back. None of these commands can hurt your system.
اول مطمئن شو که همهچیز سر جایش است:
First, confirm everything is in place:
docker --version Docker version 27.3.1, build ce12230 # این یکی مهمتر است: نشان میدهد سرویس پسزمینه هم جواب میدهد docker info --format '{{.ServerVersion}}' 27.3.1
اگر دستور دوم خطای Cannot connect to the Docker daemon داد، یعنی CLI سالم است
ولی سرویس بالا نیست — برگرد به جدول بخش دوم.
If the second command gives Cannot connect to the Docker daemon, the CLI is fine but the
service is down — go back to the table in section two.
اجرای اول: hello-world
First run: hello-world
docker run hello-world
بار اول خروجی چیزی شبیه این است:
The first time, the output looks something like this:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:305243c73…
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
این پنج خط اول، تمام معماری داکر را در خودش دارد. بیایید خطبهخط بخوانیمش:
Those first five lines contain the whole architecture. Let us read them line by line:
-
Unable to find image … locallyداکر اول انبار محلی خودت را نگاه کرد. ایمیج آنجا نبود. توجه کن که این خطا نیست، فقط گزارش است.
Docker looked in your local store first. The image was not there. Note that this is not an error, only a report.
-
Pulling from library/hello-worldپس رفت سراغ رجیستری پیشفرض.
libraryیعنی این ایمیج «رسمی» است و صاحب مشخصی ندارد؛ ایمیجهای شخصی به شکلusername/imageهستند.So it went to the default registry.
librarymeans this is an “official” image with no individual owner; personal images look likeusername/image. -
c1ec31eb5944: Pull completeاین عدد شناسهٔ یک لایه است. ایمیجهای بزرگتر اینجا چند خط دارند — هر خط یک لایه، و همه بهصورت موازی دانلود میشوند.
That identifier is a layer. Larger images show several lines here — one per layer, all downloading in parallel.
-
Digest: sha256:…اثر انگشت رمزنگاریشدهٔ دقیقاً همین ایمیج. برچسب (
tag) ممکن است فردا به ایمیج دیگری اشاره کند، اما این عدد هرگز عوض نمیشود. فصل ۱۱ رویش کار میکند.The cryptographic fingerprint of exactly this image. A
tagmay point somewhere else tomorrow; this number never changes. Chapter 11 works with it. -
و بعد برنامه اجرا شد و تمام شد
and then the program ran and finished
کانتینر پیامش را چاپ کرد و پروسهاش تمام شد. کانتینر تا وقتی زنده است که پروسهٔ اصلیاش زنده باشد. این جمله را هم حفظ کن.
The container printed its message and its process ended. A container lives exactly as long as its main process. Memorise this one too.
حالا همان دستور را دوباره بزن. این بار پنج خط اول نیست، چون ایمیج دیگر محلی است:
Now run the same command again. This time the first five lines are gone, because the image is local:
docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly.
اجرای دوم: وارد یک لینوکس کامل شو
Second run: step inside a whole Linux
این تمرین بیشتر از هر توضیحی مفهوم کانتینر را جا میاندازد. با یک دستور، وارد یک سیستم لینوکسی مستقل میشوی:
This exercise lands the concept better than any explanation. One command drops you inside a separate Linux system:
docker run -it --rm alpine:3.20 sh
سه سوئیچ اینجا هست که باید بشناسیشان:
Three flags here are worth knowing:
| سوئیچFlag | کاملLong form | چه میکندWhat it does |
|---|---|---|
-i | --interactive |
ورودی استاندارد را باز نگه میدارد تا بتوانی تایپ کنی. Keeps standard input open so you can type. |
-t | --tty |
یک ترمینال مجازی میسازد؛ بدون این، پرامپت و رنگ نداری. Allocates a pseudo-terminal; without it you get no prompt and no colours. |
--rm | — | بهمحض خروج، کانتینر را حذف میکند. برای آزمایشهای موقت حتماً بگذارش. Deletes the container as soon as it exits. Always use it for throwaway experiments. |
حالا داخل کانتینری. اینها را امتحان کن:
You are inside the container now. Try these:
/ # cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.20.3 PRETTY_NAME="Alpine Linux v3.20" / # ls / bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var / # ps aux PID USER TIME COMMAND 1 root 0:00 sh 7 root 0:00 ps aux / # whoami root / # exit
سه چیز در این خروجی باید توجهت را جلب کند.
Three things in that output deserve your attention.
اول: یک فایلسیستم لینوکسی کامل داری، در حالی که شاید سیستمعامل خودت اصلاً اوبونتو یا آلپاین نباشد. اگر روی ویندوز هستی، همین الان داخل یک آلپاین لینوکس ایستادهای.
First: you have a complete Linux filesystem, even though your own OS may be neither Ubuntu nor Alpine. If you are on Windows, you are standing inside an Alpine Linux right now.
دوم: خروجی ps aux فقط دو پروسه دارد. روی سیستم واقعی تو
الان صدها پروسه در حال اجراست، اما این کانتینر آنها را نمیبیند. این همان
namespace است — بخش هشتم بازش میکند.
Second: ps aux shows just two processes. Your real system is running
hundreds right now, but this container cannot see them. That is a namespace — section
eight unpacks it.
سوم: تو root هستی. داخل کانتینر، پیشفرض داکر کاربر روت است.
این راحت است و خطرناک — دقیقاً موضوع فصل ۱۳.
Third: you are root. Inside a container, Docker's default user is root.
That is convenient and dangerous — precisely the subject of chapter 13.
اجرای سوم: یک وبسرور واقعی
Third run: a real web server
حالا کاری کنیم که واقعاً به درد بخورد. یک وبسرور Nginx بالا میآوریم، بدون اینکه چیزی روی سیستم نصب کنیم:
Now something genuinely useful. We will start an Nginx web server without installing anything on your system:
docker run -d --name web -p 8080:80 nginx:1.27-alpine 4f2a9c1b7e3d5a8f0c6b2d4e9a1f3c7b5d8e0a2c4f6b9d1e3a5c7f9b1d3e5a7c
آن رشتهٔ طولانی شناسهٔ کانتینر است. دو سوئیچ جدید:
That long string is the container ID. Two new flags:
-dیا--detach: کانتینر را در پسزمینه اجرا کن و ترمینال را به من برگردان. بدون این، ترمینالت تا وقتی Nginx زنده است قفل میماند.-dor--detach: run in the background and give me my terminal back. Without it, your terminal stays occupied for as long as Nginx runs.-p 8080:80: پورت ۸۰۸۰ روی ماشین خودم را به پورت ۸۰ داخل کانتینر وصل کن. ترتیب مهم است و همیشهمیزبان:کانتینراست.-p 8080:80: connect port 8080 on my machine to port 80 inside the container. The order matters and is alwayshost:container.
ببینیم زنده است:
Let us check that it is alive:
docker ps CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES 4f2a9c1b7e3d nginx:1.27-alpine "/docker-entrypoint.…" Up 12 seconds 0.0.0.0:8080->80/tcp web
ستون PORTS دقیقاً همان نگاشتی است که خواستیم. حالا از سیستم خودت صدایش کن:
The PORTS column is exactly the mapping we asked for. Now call it from your own system:
curl -I http://localhost:8080 HTTP/1.1 200 OK Server: nginx/1.27.3 Content-Type: text/html Content-Length: 615 Connection: keep-alive
# همان کار روی ویندوز، بدون curl Invoke-WebRequest -Uri http://localhost:8080 -Method Head | Select-Object StatusCode
مرورگر را هم باز کن و http://localhost:8080 را ببین. صفحهٔ خوشآمد Nginx
آنجاست. حالا لاگها را نگاه کن — درخواست خودت را میبینی:
Open a browser at http://localhost:8080 too. The Nginx welcome page is there. Now look at
the logs — your own request is in them:
docker logs web 172.17.0.1 - - [08/Sep/2026:09:12:44 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0" "-" 172.17.0.1 - - [08/Sep/2026:09:13:02 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 …" "-" # دنبال کردن زنده — مثل tail -f ؛ با Ctrl+C خارج شو docker logs -f web
و برای اینکه داخل همین کانتینرِ در حال اجرا یک شل باز کنی:
And to open a shell inside that same running container:
docker exec -it web sh / # nginx -v nginx version: nginx/1.27.3 / # ls /usr/share/nginx/html 50x.html index.html / # exit
run و exec
run versus exec
docker run کانتینر جدید میسازد. docker exec در کانتینری که
همین الان اجرا است یک دستور اضافه اجرا میکند. اگر میخواهی ببینی داخل سرویسِ در حال
کار چه خبر است، همیشه exec است، نه run.
docker run creates a new container. docker exec runs an extra
command in one that is already running. To look inside a working service, it is always
exec, never run.
و در پایان، تمیزکاری. توجه کن که stop و rm دو کار متفاوتاند:
Finally, cleaning up. Note that stop and rm are two different things:
# متوقف کن — کانتینر هست، فقط خاموش است docker stop web web # ps خالی است، اما ps -a هنوز نشانش میدهد docker ps docker ps -a CONTAINER ID IMAGE STATUS NAMES 4f2a9c1b7e3d nginx:1.27-alpine Exited (0) 8 seconds ago web # دوباره روشنش کن — همان کانتینر، نه یکی جدید docker start web # حالا واقعاً حذفش کن docker stop web docker rm web web
و ایمیجهایی که تا الان دانلود شدهاند هنوز روی دیسکاند:
And the images downloaded so far are still on disk:
docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx 1.27-alpine 1ff4bb4faebc 3 weeks ago 52.5MB alpine 3.20 63b790fccc9f 2 months ago 7.83MB hello-world latest d2c94e258dcb 8 months ago 13.3kB # داکر روی دیسک چقدر جا گرفته؟ docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 3 0 60.4MB 60.4MB (100%) Containers 0 0 0B 0B Local Volumes 0 0 0B 0B Build Cache 0 0 0B 0B
به حجمها نگاه کن: کل Nginx با سیستمعامل پایهاش حدود ۵۰ مگابایت است. یک ماشین مجازی اوبونتو برای همین کار، چند گیگابایت میشد. این همان صرفهجویی نمودار بخش سوم است، به عدد.
Look at the sizes: all of Nginx with its base OS is around 50 MB. A Ubuntu VM for the same job would be several gigabytes. That is the saving from the section-three diagram, in numbers.
چرخهٔ عمر یک کانتینرA container's life cycle
کانتینر همیشه در یکی از چند حالت مشخص است. اگر این نمودار را در ذهن داشته باشی، دیگر
گیج نمیشوی که چرا docker ps کانتینرت را نشان نمیدهد ولی اسمش «اشغال» است.
A container is always in one of a few defined states. Keep this diagram in mind and you will stop
being puzzled by why docker ps shows nothing yet the name is “taken”.
exited و removed میآید.
Most day-one confusion lives in the gap between exited and removed.
نکتهٔ کلیدی این است: کانتینر متوقفشده هنوز وجود دارد. فضای دیسکش را گرفته، اسمش رزرو است، و لایهٔ نوشتنیاش دستنخورده باقی مانده. برای همین است که این دستور خطا میدهد:
The key point: a stopped container still exists. It occupies disk, reserves its name, and its writable layer is intact. That is why this command fails:
docker run -d --name web -p 8080:80 nginx:1.27-alpine docker stop web docker run -d --name web -p 8080:80 nginx:1.27-alpine docker: Error response from daemon: Conflict. The container name "/web" is already in use by container "4f2a9c1b7e3d". You have to remove (or rename) that container to be able to reuse that name.
داکر دقیقاً میگوید چه کار کن. یا کانتینر قدیمی را حذف کن، یا دوباره روشنش کن:
Docker tells you exactly what to do. Either delete the old container, or start it again:
راه اول: کانتینر قبلی را روشن کن (همان دادهٔ قبلی سر جایش است) docker start web راه دوم: قبلی را حذف کن و از نو بساز (لایهٔ نوشتنی قبلی از بین میرود) docker rm web docker run -d --name web -p 8080:80 nginx:1.27-alpine
و برای پاک کردن انبوه کانتینرهای متوقفشدهای که در طول تمرین جمع شدهاند:
And to clear out the pile of stopped containers you accumulate while practising:
# اول ببین چه چیزی قرار است حذف شود docker ps -a --filter status=exited # بعد حذف کن — تأیید میخواهد docker container prune
docker system prune -a --volumes همهچیز را پاک میکند: تمام کانتینرهای متوقف،
تمام ایمیجهایی که کانتینر فعالی ندارند، تمام شبکههای بلااستفاده، و تمام والیومها.
آن سوئیچ آخر یعنی دادهٔ پایگاهدادهات هم میرود. روی ماشین توسعه بیخطر است، روی سروری که
چیزی رویش زنده است فاجعه. اگر شک داری، بدون --volumes بزنش.
docker system prune -a --volumes wipes everything: all stopped containers, every image
without a running container, all unused networks, and all volumes. That last flag means your
database data goes too. Harmless on a dev machine, a catastrophe on a server with anything live on it.
When in doubt, run it without --volumes.
زیر کاپوت: کانتینر فقط یک پروسه استUnder the hood: a container is just a process
این بخش برای عیبیابی است، نه برای حفظ کردن. اگر بدانی کانتینر واقعاً چیست، خطاهای عجیب دیگر عجیب نیستند.
This section is for debugging, not memorisation. Once you know what a container really is, strange errors stop being strange.
حقیقت ساده این است: کانتینر یک پروسهٔ معمولی روی ماشین میزبان است که هسته چند محدودیت رویش گذاشته. نه بیشتر. اگر روی لینوکس کار میکنی، همین الان میتوانی ببینیاش:
The plain truth: a container is an ordinary process on the host machine with a few kernel restrictions applied to it. Nothing more. If you are on Linux, you can see it right now:
docker run -d --name web -p 8080:80 nginx:1.27-alpine # روی خودِ ماشین میزبان، نه داخل کانتینر ps -ef | grep -m2 nginx root 284915 284893 0 09:31 ? 00:00:00 nginx: master process nginx -g daemon off; 101 284962 284915 0 09:31 ? 00:00:00 nginx: worker process
آنجاست. یک پروسهٔ کاملاً عادی با یک PID عادی، در فهرست پروسههای سیستم خودت.
اما داخل کانتینر، همان پروسه خودش را با شمارهٔ 1 میبیند:
There it is. A perfectly ordinary process with an ordinary PID, in your own system's
process list. But from inside the container, that same process sees itself as number 1:
docker exec web ps -ef PID USER TIME COMMAND 1 root 0:00 nginx: master process nginx -g daemon off; 30 nginx 0:00 nginx: worker process 38 root 0:00 ps -ef
یک پروسه، دو شماره، دو دیدگاه. این هستهٔ کل ماجراست و اسمش namespace
(فضای نام) است.
One process, two numbers, two points of view. This is the heart of the whole thing, and it is called
a namespace.
دو مکانیزمی که همهچیز را میسازند
The two mechanisms behind everything
| مکانیزمMechanism | چه میکندWhat it does | اثری که میبینیThe effect you see |
|---|---|---|
namespaces |
دید پروسه را محدود میکند. جدا برای پروسهها، شبکه، فایلسیستم، نام میزبان، کاربر. Restricts what a process can see: separately for processes, network, filesystem, hostname, users. | ps فقط دو خط · localhost داخل کانتینر خودِ کانتینر است
ps shows two lines · localhost inside means the container itself |
cgroups |
مصرف منابع را محدود میکند: حافظه، پردازنده، ورودی/خروجی دیسک. Caps resource use: memory, CPU, disk I/O. | کانتینری که از سقف حافظه رد شود، با کد ۱۳۷ کشته میشود A container over its memory cap is killed with code 137 |
union filesystem |
لایههای فقطخواندنی را با لایهٔ نوشتنی ترکیب میکند و یکی نشان میدهد. Merges the read-only layers with the writable one into a single view. | اشتراک لایهها روی دیسک · از بین رفتن داده با rm
Layer sharing on disk · data loss on rm |
هر سهٔ اینها قابلیتهای هستهٔ لینوکس هستند، نه اختراع داکر. داکر آنها را بستهبندی کرده و پشت چند دستور ساده گذاشته. برای همین است که کانتینر سربار محاسباتی ندارد: چیزی شبیهسازی نمیشود، فقط هسته چند قانون اضافه اعمال میکند.
All three are Linux kernel features, not Docker inventions. Docker packaged them behind a few simple commands. That is why containers carry no compute overhead: nothing is emulated, the kernel simply applies a few extra rules.
و از همینجا یک نتیجهٔ امنیتی مهم درمیآید: چون هسته مشترک است، اگر کسی از داخل کانتینر بتواند از یک آسیبپذیری هسته استفاده کند، به کل ماشین دسترسی دارد. ماشین مجازی این مرز را در سختافزار میکشد و شکستنش سختتر است. پس کد نامطمئن دیگران را داخل کانتینر ساده اجرا نکن و فرض نکن ایزوله است.
From which follows an important security consequence: because the kernel is shared, anyone who can exploit a kernel vulnerability from inside a container reaches the whole machine. A VM draws that boundary in hardware and it is harder to break. So do not run other people's untrusted code in a plain container and assume it is isolated.
دستور ps -ef | grep nginx روی ترمینال ویندوز چیزی پیدا نمیکند و این طبیعی
است. پروسهٔ کانتینر داخل آن ماشین مجازی لینوکسی است که داکر بالا آورده. برای دیدنش باید
وارد همان محیط شوی — روی WSL2 کافی است ترمینال WSL را باز کنی و همان دستور را آنجا بزنی.
Running ps -ef | grep nginx in a Windows terminal finds nothing, and that is correct.
The container process lives inside the Linux VM that Docker runs. To see it you must be in that
environment — on WSL2, just open a WSL terminal and run the same command there.
کجا خراب میشودWhere it breaks
پنج خطایی که تقریباً همه در هفتهٔ اول میبینند. پیام خطا را دقیق آوردهایم تا وقتی همان را دیدی، بشناسیاش.
The five errors nearly everyone meets in their first week. The exact messages are here so you recognise them when they show up.
docker: Error response from daemon: Conflict. The container name "/web" is already in use by container "4f2a9c1b7e3d". You have to remove (or rename) that container to be able to reuse that name.
- علت
- Cause
- فکر میکنی
docker runکانتینر را «دوباره راه میاندازد». نمیاندازد؛ هر بار یک کانتینر جدید میسازد. کانتینر قبلی هنوز وجود دارد، فقط خاموش است، و اسم را گرفته. - You assume
docker run“restarts” the container. It does not; it creates a new one every time. The old one still exists, merely stopped, and holds the name. - درمان
- Fix
- اگر دادهٔ قبلی را میخواهی:
docker start web. اگر از نو میخواهی:docker rm webو بعدrun. برای آزمایشهای موقت از اول--rmبگذار. - To keep the old state:
docker start web. To start clean:docker rm webthenrun. For throwaway tests, use--rmfrom the start.
docker: Error response from daemon: driver failed programming external connectivity on endpoint web: Bind for 0.0.0.0:8080 failed: port is already allocated.
- علت
- Cause
- پورت ۸۰۸۰ روی ماشین میزبان قبلاً اشغال شده — یا با کانتینر دیگری، یا با برنامهای که خودت بالا آوردهای. یک پورت میزبان فقط یک صاحب دارد.
- Host port 8080 is already taken — by another container, or by something you started yourself. A host port has exactly one owner.
- درمان
- Fix
- ببین چه کسی گرفتتش:
docker psو بعدss -ltnp | grep 8080روی لینوکس یاnetstat -ano | findstr 8080روی ویندوز. یا فقط پورت میزبان را عوض کن:-p 8081:80. پورت داخل کانتینر همیشه میتواند ۸۰ بماند. - Find the owner:
docker ps, thenss -ltnp | grep 8080on Linux ornetstat -ano | findstr 8080on Windows. Or simply change the host port:-p 8081:80. The port inside the container can always stay 80.
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Connection refused
- علت
- Cause
- سه احتمال، به همین ترتیب بررسیشان کن: (۱) کانتینر اصلاً بالا نیست —
docker ps -aبزن و ستونSTATUSرا ببین؛ (۲) سوئیچ-pرا جا انداختهای، پس پورت اصلاً منتشر نشده؛ (۳) ترتیب را برعکس نوشتهای:-p 80:8080بهجای-p 8080:80. - Three possibilities, check in this order: (1) the container is not up —
run
docker ps -aand readSTATUS; (2) you omitted-p, so nothing was published; (3) you reversed the order:-p 80:8080instead of-p 8080:80. - درمان
- Fix
- همیشه
میزبان:کانتینر. عدد سمت راست همان پورتی است که سرویس داخل ایمیج رویش گوش میدهد و تو انتخابش نکردهای — مستندات ایمیج گفته. - Always
host:container. The right-hand number is the port the service inside the image listens on; you do not choose it, the image documents it.
psql: error: connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused
- علت
- Cause
- این را از داخل یک کانتینر زدهای و انتظار داری به پایگاهدادهٔ کانتینر
دیگر وصل شود. اما
localhostداخل هر کانتینر یعنی خودِ همان کانتینر، نه ماشین تو و نه کانتینر کناری. این مستقیماً نتیجهٔ network namespace است. - You ran this from inside a container expecting to reach a database in another
one. But
localhostinside any container means that container itself — not your machine and not the neighbouring container. A direct consequence of the network namespace. - درمان
- Fix
- کانتینرها باید در یک شبکهٔ مشترک باشند و همدیگر را با نام صدا بزنند،
نه با
localhost. فصل ۷ کاملاً به این میپردازد. - Containers must share a network and address each other by name, not by
localhost. Chapter 7 is devoted to this.
Error response from daemon: pull access denied for myapp, repository does not exist or may require 'docker login'
- علت
- Cause
- ایمیجی که خواستهای در رجیستری پیشفرض وجود ندارد. معمولاً غلط املایی در اسم
یا برچسب است. نکتهٔ ظریف: اگر برچسب ننویسی، داکر
:latestفرض میکند — و خیلی ایمیجها اصلاً برچسبی به این نام ندارند. - The image you asked for does not exist in the default registry. Usually a typo in the
name or tag. The subtle part: with no tag, Docker assumes
:latest— and plenty of images have no such tag at all. - درمان
- Fix
- اسم دقیق و برچسب را بررسی کن. و بدان که
latestهیچ معنای «جدیدترین» ندارد؛ فقط یک نام پیشفرض است که ممکن است ماهها قدیمی باشد. در کار جدی همیشه نسخهٔ صریح بنویس:nginx:1.27-alpineنهnginx. - Check the exact name and tag. And know that
latestcarries no meaning of “newest”; it is just a default name that may be months old. In serious work always pin a version:nginx:1.27-alpine, notnginx.
مرجع سریع این فصلQuick reference
همهٔ دستورهایی که در این فصل دیدی، یکجا. این جدول را نگه دار؛ ۹۰ درصد کار روزمره همینهاست.
Every command from this chapter in one place. Keep this table; it covers 90% of daily work.
docker --version |
نسخهٔ CLICLI version |
docker info |
وضعیت کامل — اگر جواب داد یعنی سرویس پسزمینه سالم است Full status — a reply means the daemon is healthy |
docker run IMAGE |
کانتینر جدید بساز و اجرا کنCreate and start a new container |
docker run -it --rm IMAGE sh |
شل تعاملی موقت؛ با خروج حذف میشود A throwaway interactive shell; deleted on exit |
docker run -d --name N -p H:C IMAGE |
اجرای پسزمینه با نام و نگاشت پورت میزبان:کانتینر
Background run with a name and host:container port mapping |
docker ps |
کانتینرهای در حال اجراRunning containers |
docker ps -a |
همه، شامل متوقفشدههاAll of them, including stopped |
docker logs -f NAME |
لاگ کانتینر، بهصورت زندهContainer logs, followed live |
docker exec -it NAME sh |
شل داخل کانتینری که الان اجراست A shell inside an already running container |
docker stop NAME |
توقف نرم — کانتینر باقی میماندGraceful stop — the container remains |
docker start NAME |
روشن کردن دوبارهٔ همان کانتینرStart that same container again |
docker rm NAME |
حذف کانتینر و لایهٔ نوشتنیاشDelete the container and its writable layer |
docker images |
ایمیجهای محلیLocal images |
docker image history IMAGE |
لایههای ایمیج و حجم هرکدامAn image's layers and their sizes |
docker pull IMAGE:TAG |
دانلود بدون اجراDownload without running |
docker system df |
مصرف دیسک به تفکیکDisk usage, broken down |
docker container prune |
حذف همهٔ کانتینرهای متوقفشدهRemove all stopped containers |
docker inspect NAME |
همهٔ جزئیات بهصورت JSONEvery detail, as JSON |
تمرینهاExercises
هشت تمرین، از ساده به سخت. همه پاسخ کامل دارند — اما اول خودت انجامش بده. بعد از هر تمرین، تیک «انجام شد» را بزن تا نوار پیشرفت جلو برود.
Eight exercises, easy to hard. All have full solutions — but do them yourself first. Tick “Done” after each one to move the progress bar.
داکر را اجرا کن و ثابت کن که هم CLI و هم سرویس پسزمینه سالماند. سپس ایمیج
hello-world را اجرا کن و بگو چند لایه دانلود شد.
Run Docker and prove that both the CLI and the daemon are healthy. Then run the
hello-world image and say how many layers were downloaded.
معیار موفقیت: عبارت Hello from Docker! در خروجی دیده شود.
Success criterion: Hello from Docker! appears in the output.
راهنماییHint
برای بخش دوم، خطهایی که به Pull complete ختم میشوند را بشمار.
هر خط یک لایه است. اگر ایمیج را قبلاً دانلود کردهای، اول docker rmi hello-world
بزن تا خروجی کامل را ببینی.
For the second part, count the lines ending in Pull complete. Each line is one
layer. If you already have the image, run docker rmi hello-world first to see the full output.
پاسخ و توضیحSolution
# ۱) سلامت CLI docker --version # ۲) سلامت سرویس پسزمینه — این مهمترین بخش است docker info --format '{{.ServerVersion}}' # ۳) اگر ایمیج را از قبل داری، حذفش کن تا خروجی کامل را ببینی docker rmi hello-world # ۴) اجرا docker run hello-world
چرا اینطور: docker --version فقط نسخهٔ برنامهٔ خط فرمان را چاپ
میکند و حتی وقتی سرویس پسزمینه خاموش است هم جواب میدهد. برای همین بهتنهایی سلامت را ثابت
نمیکند. اما docker info مجبور است با dockerd حرف بزند، پس اگر
ServerVersion برگشت، هر دو طرف سالماند.
Why this way: docker --version only prints the CLI's own version and
answers even when the daemon is down, so on its own it proves nothing. docker info, by
contrast, must talk to dockerd; if ServerVersion comes back, both halves are healthy.
تعداد لایه: یک لایه. hello-world کوچکترین ایمیج ممکن است —
فقط یک فایل اجرایی که یک متن چاپ میکند و تمام. برای همین در خروجی فقط یک خط
Pull complete میبینی.
Layer count: one. hello-world is about the smallest possible image —
a single executable that prints a message. Hence exactly one Pull complete line.
بدون نصب هیچچیزی روی سیستمت، وارد یک اوبونتو ۲۴٫۰۴ شو، نسخهٔ دقیق سیستمعاملش را پیدا کن، یک فایل بساز، و بیرون بیا. بعد ثابت کن که آن کانتینر دیگر وجود ندارد.
Without installing anything on your system, step into an Ubuntu 24.04, find its exact OS version, create a file, and exit. Then prove that container no longer exists.
معیار موفقیت: VERSION_ID="24.04" را ببینی، و بعد از خروج
docker ps -a آن کانتینر را نشان ندهد.
Success criterion: you see VERSION_ID="24.04", and after exiting,
docker ps -a does not list that container.
راهنماییHint
به سه سوئیچ نیاز داری: یکی برای باز نگهداشتن ورودی، یکی برای ساختن ترمینال،
و یکی برای اینکه بعد از خروج خودش پاک شود. اوبونتو شل bash دارد، برخلاف
آلپاین که فقط sh دارد.
You need three flags: one to keep input open, one to allocate a terminal, and one to make
it clean up after itself. Ubuntu ships bash, unlike Alpine which only has sh.
پاسخ و توضیحSolution
docker run -it --rm ubuntu:24.04 bash
root@8f21c4d90b7e:/# cat /etc/os-release PRETTY_NAME="Ubuntu 24.04.1 LTS" NAME="Ubuntu" VERSION_ID="24.04" VERSION="24.04.1 LTS (Noble Numbat)" root@8f21c4d90b7e:/# echo "سلام از داخل کانتینر" > /tmp/note.txt root@8f21c4d90b7e:/# cat /tmp/note.txt سلام از داخل کانتینر root@8f21c4d90b7e:/# exit
# هیچ کانتینر اوبونتویی باقی نمانده است docker ps -a --filter ancestor=ubuntu:24.04 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
چرا اینطور: آن رشتهٔ 8f21c4d90b7e در پرامپت، نام میزبان کانتینر است
و برابر با دوازده رقم اول شناسهٔ آن. این خودش نمونهای از UTS namespace است:
کانتینر نام میزبان جداگانهای دارد.
Why this way: the 8f21c4d90b7e in the prompt is the container's hostname,
equal to the first twelve characters of its ID. That is itself an example of the UTS namespace:
the container gets its own hostname.
سرنوشت فایل: /tmp/note.txt در لایهٔ نوشتنی همان کانتینر ساخته شد.
سوئیچ --rm باعث شد کانتینر و آن لایه با هم حذف شوند. فایل دیگر هیچجا نیست —
نه در ایمیج، نه روی سیستم تو. این همان چیزی است که بخش پنجم دربارهٔ آن هشدار داد.
The file's fate: /tmp/note.txt was created in that container's writable
layer. --rm deleted the container and that layer together. The file is nowhere now —
not in the image, not on your system. Exactly what section five warned about.
یک وبسرور Nginx با نام lab-web روی پورت ۹۰۹۰ بالا بیاور. با یک دستور
ثابت کن که کد وضعیت ۲۰۰ برمیگرداند. لاگها را ببین و درخواست خودت را در آن پیدا کن.
در پایان، کاملاً تمیزش کن.
Start an Nginx web server named lab-web on port 9090. Prove with one command that it
returns status 200. Look at the logs and find your own request in them. Then clean it up completely.
معیار موفقیت: خروجی شامل 200 OK باشد، و در پایان
docker ps -a هیچ کانتینری با نام lab-web نداشته باشد.
Success criterion: the output contains 200 OK, and at the end
docker ps -a lists no container named lab-web.
راهنماییHint
«تمیز کردن کامل» یعنی دو دستور، نه یکی. کانتینر متوقفشده هنوز وجود دارد.
اگر curl نداری، مرورگر هم قبول است — لاگ همان را نشان میدهد.
“Completely” means two commands, not one. A stopped container still exists. If you have no
curl, a browser works too — the log will show it.
پاسخ و توضیحSolution
# ۱) بالا آوردن docker run -d --name lab-web -p 9090:80 nginx:1.27-alpine # ۲) بررسی — سوئیچ -I فقط هدر را میگیرد curl -I http://localhost:9090 HTTP/1.1 200 OK Server: nginx/1.27.3 # ۳) لاگ docker logs lab-web 172.17.0.1 - - [08/Sep/2026:09:44:10 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0" "-" # ۴) تمیزکاری — هر دو دستور لازم است docker stop lab-web docker rm lab-web # ۵) تأیید docker ps -a --filter name=lab-web
چرا دو دستور برای تمیزکاری: stop فقط پروسهٔ داخل کانتینر را
میبندد و کانتینر را به حالت exited میبرد. rm است که خود کانتینر و
لایهٔ نوشتنیاش را حذف میکند. اگر فقط stop بزنی، نام lab-web همچنان
اشغال میماند.
Why two commands: stop only ends the process inside and moves the
container to exited. rm is what deletes the container and its writable layer.
With stop alone, the name lab-web stays taken.
docker rm -f lab-web هر دو کار را با هم انجام میدهد. اما -f
یعنی «بیمقدمه بکش»؛ برنامه فرصت بستن تمیز فایلها و اتصالها را نمیگیرد. روی وبسرور
آزمایشی اشکالی ندارد، روی پایگاهداده میتواند داده را خراب کند.
docker rm -f lab-web does both at once. But -f means “kill it now”; the
program gets no chance to close files and connections cleanly. Fine for a test web server, risky
for a database.
از یک ایمیج، سه وبسرور مستقل روی پورتهای ۸۰۸۱، ۸۰۸۲ و ۸۰۸۳ بالا بیاور.
بعد با docker system df نشان بده که این سه کانتینر تقریباً هیچ فضای اضافهای
روی دیسک نگرفتهاند و توضیح بده چرا.
From one image, start three independent web servers on ports 8081, 8082 and 8083. Then use
docker system df to show that these three containers took almost no extra disk space,
and explain why.
معیار موفقیت: هر سه پورت جواب ۲۰۰ بدهند، و ستون SIZE
برای Containers در حد چند کیلوبایت باشد، نه سه برابر حجم ایمیج.
Success criterion: all three ports answer 200, and the SIZE
column for Containers is a few kilobytes, not three times the image size.
راهنماییHint
هر کانتینر باید نام یکتا و پورت میزبان یکتا داشته باشد، ولی پورت داخل کانتینر هر سه بار همان ۸۰ میماند. جواب «چرا» را در بخش لایهها پیدا میکنی.
Each container needs a unique name and a unique host port, but the container-side port stays 80 all three times. The answer to “why” is in the layers section.
پاسخ و توضیحSolution
docker run -d --name w1 -p 8081:80 nginx:1.27-alpine docker run -d --name w2 -p 8082:80 nginx:1.27-alpine docker run -d --name w3 -p 8083:80 nginx:1.27-alpine curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8081 curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8082 curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8083 200 200 200
docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 1 1 52.5MB 0B (0%) Containers 3 3 3.27kB 0B (0%) Local Volumes 0 0 0B 0B
چرا: هر سه کانتینر همان لایههای فقطخواندنیِ یک ایمیج را میبینند. آن ۵۲ مگابایت یک بار روی دیسک است و بین هر سه به اشتراک گذاشته شده. چیزی که برای هر کانتینر جداگانه ذخیره میشود فقط لایهٔ نوشتنی نازک آن است — و چون Nginx تازه بالا آمده و هنوز چیز زیادی ننوشته، آن لایه در حد چند کیلوبایت است.
Why: all three containers see the same read-only layers of one image. Those 52 MB exist on disk once and are shared between all three. What is stored per container is only its thin writable layer — and since Nginx has just started and written little, that layer is a few kilobytes.
همین ویژگی است که باعث میشود روی یک سرور متوسط بتوانی دهها کانتینر اجرا کنی، در حالی که با ماشین مجازی به چند تا محدود بودی.
This is precisely what lets you run dozens of containers on a modest server where virtual machines would have limited you to a handful.
# تمیزکاری — چند کانتینر را میشود با هم داد docker rm -f w1 w2 w3
ثابت کن که دادهٔ نوشتهشده داخل کانتینر با docker rm از بین میرود، ولی با
docker stop نه. یک کانتینر آلپاین بساز (بدون --rm)، فایلی در آن
بنویس، متوقفش کن، دوباره روشنش کن و ببین فایل هست. بعد حذفش کن و کانتینر جدیدی از همان
ایمیج بساز و نشان بده فایل نیست.
Prove that data written inside a container is destroyed by docker rm but survives
docker stop. Create an Alpine container (without --rm), write a file in it,
stop it, start it again and see the file is there. Then delete it, create a new container from the
same image and show the file is gone.
معیار موفقیت: بعد از start محتوای فایل چاپ شود؛ در
کانتینر جدید خطای No such file or directory بگیری.
Success criterion: after start the file's contents print; in the
new container you get No such file or directory.
راهنماییHint
کانتینری که پروسهٔ اصلیاش تمام شود، میمیرد. برای اینکه آلپاین زنده بماند
به آن کاری بده که تمام نشود — مثلاً sleep با عددی بزرگ، یا
tail -f /dev/null. بعد با exec واردش شو.
A container whose main process ends, dies. To keep Alpine alive, give it work that does not
finish — a long sleep, or tail -f /dev/null. Then get in with exec.
پاسخ و توضیحSolution
# ۱) کانتینری که زنده میماند — بدون --rm docker run -d --name box alpine:3.20 sleep infinity # ۲) فایل بنویس docker exec box sh -c 'echo "داده مهم" > /data.txt' docker exec box cat /data.txt داده مهم # ۳) توقف و روشن کردن دوباره docker stop box docker start box docker exec box cat /data.txt داده مهم ← فایل سر جایش است # ۴) حالا حذف کامل و ساخت یک کانتینر تازه از همان ایمیج docker rm -f box docker run -d --name box alpine:3.20 sleep infinity docker exec box cat /data.txt cat: can't open '/data.txt': No such file or directory docker rm -f box
چرا اینطور: فایل /data.txt هرگز وارد ایمیج نشد. فقط در لایهٔ
نوشتنیِ کانتینر اول ثبت شد. stop آن لایه را دست نمیزند — پروسه میمیرد،
فایلسیستم میماند. اما rm کل لایه را با کانتینر پاک میکند. کانتینر دوم لایهٔ
نوشتنیِ خالی و تازهای دارد، پس فایل را نمیبیند.
Why this way: /data.txt never entered the image. It was recorded only in
the first container's writable layer. stop does not touch that layer — the process
dies, the filesystem stays. But rm erases the whole layer along with the container. The
second container gets a fresh, empty writable layer and cannot see the file.
چرا sleep infinity: اگر مینوشتی docker run -d alpine،
کانتینر بلافاصله میمرد. چون ایمیج آلپاین دستور پیشفرضش /bin/sh است و شلی که
ورودی تعاملی نداشته باشد فوراً تمام میشود — و با تمام شدن پروسهٔ اصلی، کانتینر هم تمام میشود.
Why sleep infinity: had you written docker run -d alpine, the
container would have died at once. The Alpine image's default command is /bin/sh, and a
shell with no interactive input exits immediately — and when the main process ends, so does the container.
اینکه دادهٔ مهم را در لایهٔ نوشتنی بگذاری اشتباه است، نه یک ترفند. راه درست
volume است که کل فصل ۶ به آن اختصاص دارد.
Putting important data in the writable layer is a mistake, not a trick. The correct answer is
a volume, and all of chapter 6 is about it.
دو ایمیج python:3.12-alpine و node:22-alpine را دانلود کن.
نشان بده که این دو، لایهٔ پایهٔ مشترکی دارند و بگو این اشتراک چقدر فضا صرفهجویی کرده است.
Pull the images python:3.12-alpine and node:22-alpine. Show that the two
share a base layer, and say how much space that sharing saved.
معیار موفقیت: شناسهٔ لایهٔ پایهٔ هر دو ایمیج یکسان باشد، و بتوانی تفاوت «مجموع حجم ایمیجها» با «فضای واقعی روی دیسک» را نشان بدهی.
Success criterion: the base layer ID is identical for both images, and you can show the gap between “sum of image sizes” and “actual disk used”.
راهنماییHint
دستور docker image inspect با قالب {{.RootFS.Layers}}
فهرست شناسهٔ لایهها را میدهد. اولین عضو این فهرست، لایهٔ پایه است. برای بخش دوم،
عددهای docker images و docker system df را مقایسه کن.
docker image inspect with the format {{.RootFS.Layers}} lists layer
IDs; the first entry is the base layer. For the second part, compare the numbers from
docker images and docker system df.
پاسخ و توضیحSolution
docker pull python:3.12-alpine docker pull node:22-alpine # اولین لایهٔ هر ایمیج را جدا بگیر docker image inspect python:3.12-alpine --format '{{index .RootFS.Layers 0}}' sha256:4a2c4e35d0e5a2c8b6f9d1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3 docker image inspect node:22-alpine --format '{{index .RootFS.Layers 0}}' sha256:4a2c4e35d0e5a2c8b6f9d1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3 ↑ دقیقاً یکی — هر دو روی همان آلپاین ساخته شدهاند
docker images --filter reference=python --filter reference=node REPOSITORY TAG IMAGE ID SIZE python 3.12-alpine f4a1c9e02b73 58.1MB node 22-alpine 8b3d7f1c4e90 152MB # مجموع سادهٔ ستون SIZE ≈ ۲۱۰ مگابایت — اما روی دیسک: docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 2 0 202MB 202MB (100%)
چرا اینطور: شناسهٔ لایه در داکر از روی محتوای لایه محاسبه میشود
(به این میگویند content-addressable). دو لایه با محتوای یکسان، ناگزیر شناسهٔ
یکسان دارند و داکر فقط یک نسخه از آن را نگه میدارد. ستون SIZE در
docker images حجم کاملِ هر ایمیج را نشان میدهد و لایههای مشترک را
در هر دو ردیف حساب میکند؛ اما docker system df فضای واقعی دیسک را میگوید.
اختلاف این دو عدد، همان صرفهجویی است.
Why this way: a layer's ID is derived from the layer's content (this is called
content-addressable storage). Two layers with identical content necessarily have the same ID,
and Docker keeps exactly one copy. The SIZE column in docker images reports each
image's full size and counts shared layers in both rows; docker system df reports the
real disk usage. The gap between the two numbers is the saving.
روی سیستم تو عددها فرق میکنند و ممکن است اختلاف کوچک باشد — چون آلپاین بهخودیخود کوچک است.
همین آزمایش را با دو ایمیج مبتنی بر debian تکرار کن تا اختلاف چشمگیر شود.
Your numbers will differ and the gap may be small — Alpine is tiny to begin with. Repeat the experiment
with two debian-based images to see a dramatic difference.
ثابت کن که کانتینر واقعاً فقط یک پروسه روی میزبان است. یک کانتینر بالا بیاور، شناسهٔ
پروسهاش روی میزبان را پیدا کن، و نشان بده همان پروسه داخل کانتینر
شمارهٔ ۱ است. سپس با یک سقف حافظه، cgroups را در عمل ببین.
Prove that a container really is just a process on the host. Start a container, find its process ID
on the host, and show that the same process is number 1 inside the container. Then
set a memory cap and watch cgroups act.
معیار موفقیت: دو عدد متفاوت برای یک پروسه به دست بیاوری، و کانتینر
دوم با کد خروج 137 کشته شود.
Success criterion: you obtain two different numbers for one process, and the
second container is killed with exit code 137.
راهنماییHint
docker inspect با قالب {{.State.Pid}} مستقیماً شناسهٔ
پروسه روی میزبان را میدهد. برای سقف حافظه از --memory استفاده کن و برنامهای
اجرا کن که عمداً بیشتر از آن حافظه بگیرد. اگر روی ویندوز یا مکی، بخش اول را در ترمینال WSL
یا با docker run --pid=host امتحان کن.
docker inspect with {{.State.Pid}} gives the host-side PID directly.
For the memory cap use --memory and run something that deliberately allocates more than
that. On Windows or macOS, do the first part in a WSL terminal or via docker run --pid=host.
پاسخ و توضیحSolution
docker run -d --name probe alpine:3.20 sleep infinity # ۱) شناسهٔ پروسه از دید میزبان docker inspect probe --format '{{.State.Pid}}' 291744 # ۲) همان پروسه در فهرست پروسههای میزبان ps -p 291744 -o pid,ppid,cmd PID PPID CMD 291744 291721 sleep infinity # ۳) همان پروسه از دید داخل کانتینر docker exec probe ps -ef PID USER TIME COMMAND 1 root 0:00 sleep infinity 14 root 0:00 ps -ef docker rm -f probe
چه دیدیم: یک پروسهٔ sleep infinity وجود دارد، نه دوتا. میزبان
آن را 291744 میبیند و کانتینر 1. این تنها یک تغییر دید است
که PID namespace ایجاد کرده. اگر روی میزبان kill 291744 بزنی،
کانتینر میمیرد — چون واقعاً همان پروسه است.
What we saw: there is one sleep infinity process, not two. The host calls
it 291744, the container calls it 1. That is purely a change of view
created by the PID namespace. Run kill 291744 on the host and the container dies —
because it genuinely is the same process.
# بخش دوم: سقف حافظه ۲۰ مگابایت، و تلاش برای گرفتن ۲۰۰ مگابایت docker run --name hog --memory 20m alpine:3.20 \ sh -c 'dd if=/dev/zero of=/dev/shm/fill bs=1M count=200' # کد خروج را بخوان docker inspect hog --format '{{.State.ExitCode}} {{.State.OOMKilled}}' 137 true docker rm hog
کد ۱۳۷ یعنی چه: کد خروج ۱۳۷ برابر است با ۱۲۸ بهعلاوهٔ ۹، و ۹ شمارهٔ
سیگنال SIGKILL است. یعنی پروسه خودش تمام نشد؛ هسته آن را کشت. فیلد
OOMKilled: true علتش را تأیید میکند: از سقف حافظهای که cgroups
برایش گذاشته بود عبور کرد.
What 137 means: exit code 137 is 128 plus 9, and 9 is the SIGKILL signal
number. The process did not finish on its own; the kernel killed it. The OOMKilled: true
field confirms why: it exceeded the memory ceiling that cgroups had set.
این دقیقاً همان خطایی است که در محیط واقعی میبینی وقتی کانتینری بیدلیل «ناپدید» میشود. فصل ۱۲ روش سیستماتیک تشخیصش را میدهد.
This is exactly the failure you meet in production when a container “vanishes” for no visible reason. Chapter 12 gives the systematic way to diagnose it.
بدون ساختن هیچ ایمیجی، کاری کن که وبسرور Nginx بهجای صفحهٔ پیشفرض، صفحهٔ HTML خودت را نشان بدهد. بعد توضیح بده چرا این روش برای کار جدی نامناسب است و چه چیزی جایش را میگیرد.
Without building any image, make Nginx serve your own HTML page instead of the default one. Then explain why this approach is unsuitable for real work and what replaces it.
معیار موفقیت: خروجی curl http://localhost:8080 متن خودت
باشد، و بتوانی بگویی این تغییر کجا ذخیره شده است.
Success criterion: curl http://localhost:8080 returns your own text,
and you can say where that change is stored.
راهنماییHint
دستور docker cp فایل را بین میزبان و کانتینر جابهجا میکند و نحوش
شبیه cp معمولی است، با این تفاوت که یک طرف نامکانتینر:/مسیر است.
از تمرین ۶ میدانی Nginx فایلهایش را کجا نگه میدارد.
docker cp moves files between host and container with a syntax like ordinary
cp, except one side is container:/path. You know from earlier where Nginx
keeps its files.
پاسخ و توضیحSolution
# ۱) صفحهٔ خودت را بساز cat > index.html <<'HTML' <!doctype html> <html lang="fa" dir="rtl"><meta charset="utf-8"> <title>اولین کانتینر من</title> <h1>این صفحه از داخل یک کانتینر میآید</h1> HTML # ۲) کانتینر را بالا بیاور docker run -d --name myweb -p 8080:80 nginx:1.27-alpine # ۳) فایل را داخلش کپی کن docker cp index.html myweb:/usr/share/nginx/html/index.html # ۴) بررسی curl -s http://localhost:8080 | head -n 3 <!doctype html> <html lang="fa" dir="rtl"><meta charset="utf-8"> <title>اولین کانتینر من</title>
این تغییر کجا ذخیره شد: در لایهٔ نوشتنی کانتینر myweb. ایمیج
nginx:1.27-alpine ذرهای عوض نشده — میتوانی با docker diff دقیقاً
ببینی چه چیزی نسبت به ایمیج تغییر کرده:
Where the change lives: in myweb's writable layer. The image
nginx:1.27-alpine has not changed by a byte — docker diff shows exactly
what differs from the image:
docker diff myweb C /usr/share/nginx/html C /usr/share/nginx/html/index.html # C یعنی Changed ، A یعنی Added ، D یعنی Deleted
چرا این روش برای کار جدی بد است: سه دلیل، به ترتیب اهمیت.
Why this is bad for real work: three reasons, in order of importance.
- تکرارپذیر نیست. برای بالا آوردن همین سرویس روی سرور دیگر باید
یادت باشد که بعد از
runیکcpهم بزنی. این دقیقاً همان «راهنمای نصبی» است که در بخش اول گفتیم منسوخ میشود. - It is not reproducible. To bring the same service up elsewhere you must
remember to run a
cpafterrun. That is exactly the “install README” that section one said always rots. - فناپذیر است. اگر کانتینر را
rmکنی، صفحهات میرود. کانتینر باید دورانداختنی باشد؛ اگر چیز باارزشی فقط داخل آن باشد، دیگر نیست. - It is fragile.
rmthe container and your page is gone. A container should be disposable; if something valuable exists only inside it, it no longer is. - قابل بازبینی نیست. این تغییر در هیچ فایلی ثبت نشده، پس در Git نیست و کسی نمیتواند رویش review بزند.
- It is not reviewable. The change is recorded in no file, so it is not in Git and nobody can review it.
جایگزین درست: دو راه، بسته به اینکه محتوا بخشی از برنامه است یا داده.
اگر بخشی از برنامه است، یک Dockerfile بنویس که آن را داخل ایمیج
COPY کند — فصل ۴. اگر داده است و باید بعد از ساخت ایمیج هم قابل تغییر باشد،
از bind mount یا volume استفاده کن — فصل ۶.
The right replacement: two options, depending on whether the content is part of the
program or is data. If it is part of the program, write a Dockerfile that
COPYs it into the image — chapter 4. If it is data that must stay changeable after the
image is built, use a bind mount or a volume — chapter 6.
docker rm -f myweb
پروژهٔ پایان فصلEnd-of-chapter project
دفترچهٔ اولین کانتینر
Your first container logbook
هدف: یک وبسایت شخصی کوچک را با Nginx سرو کنی، رفتارش را زیر نظر بگیری، و در پایان
یک «دفترچه» بنویسی که هر کسی بتواند با آن دقیقاً همان کار را تکرار کند. فایلهای آماده
در پوشهٔ lab/ch01/ کنار همین مرجع هستند.
The goal: serve a small personal site with Nginx, observe how it behaves, and finish by writing a
“logbook” that lets anyone reproduce exactly what you did. Starter files live in
lab/ch01/ next to this reference.
مراحل
Steps
-
صفحه را آماده کنPrepare the page
فایل
lab/ch01/site/index.htmlرا باز کن و اسم خودت را در آن بگذار. میتوانی صفحهٔ خودت را هم از صفر بنویسی.Open
lab/ch01/site/index.htmland put your own name in it. Writing your own page from scratch is fine too. -
سرویس را بالا بیاورBring the service up
با نام
ch01-siteو روی پورت ۸۰۸۰. صفحه را باdocker cpداخلش بگذار و در مرورگر ببین.Named
ch01-site, on port 8080. Put the page in withdocker cpand view it in a browser. -
رفتارش را ثبت کنRecord its behaviour
سه بار صفحه را در مرورگر رفرش کن، بعد لاگ را بگیر. یک آدرس ناموجود مثل
/nothingرا هم صدا بزن و ببین در لاگ چه کد وضعیتی ثبت میشود.Refresh the page three times, then read the logs. Also request a missing path like
/nothingand see which status code appears in the log. -
مقاومتش را بسنجTest its resilience
کانتینر را
stopو دوبارهstartکن. آیا صفحهات هنوز هست؟ بعدrmکن و از نو بساز. حالا چه؟ جواب هر دو را بنویس.stopthenstartthe container. Is your page still there? Thenrmit and rebuild. Now what? Write down both answers. -
دفترچه را بنویسWrite the logbook
در
lab/ch01/logbook.mdاینها را ثبت کن: تمام دستورهایی که زدی، خروجی هرکدام در یک خط، پاسخ سؤال مرحلهٔ ۴، و یک خط دربارهٔ اینکه اگر میخواستی این سرویس همیشه بالا بماند چه چیزی کم داری.In
lab/ch01/logbook.mdrecord: every command you ran, a one-line summary of each output, your answers from step 4, and one line on what is missing if you wanted this service to stay up permanently.
پاسخ مرجع — بعد از انجام دادن خودت باز کن Reference answer — open after you have done it
# ۲) بالا آوردن و قرار دادن صفحه docker run -d --name ch01-site -p 8080:80 nginx:1.27-alpine docker cp site/index.html ch01-site:/usr/share/nginx/html/index.html # ۳) ثبت رفتار curl -s -o /dev/null http://localhost:8080 curl -s -o /dev/null http://localhost:8080/nothing docker logs ch01-site 172.17.0.1 - - [...] "GET / HTTP/1.1" 200 412 "-" "curl/8.5.0" "-" 172.17.0.1 - - [...] "GET /nothing HTTP/1.1" 404 153 "-" "curl/8.5.0" "-" 2026/09/08 10:02:31 [error] 30#30: *3 open() "/usr/share/nginx/html/nothing" failed (2: No such file or directory), client: 172.17.0.1, request: "GET /nothing HTTP/1.1" # ۴) آزمون مقاومت docker stop ch01-site docker start ch01-site curl -s http://localhost:8080 | head -n 1 <!doctype html> ← صفحه هست docker rm -f ch01-site docker run -d --name ch01-site -p 8080:80 nginx:1.27-alpine curl -s http://localhost:8080 | grep -o '<title>.*</title>' <title>Welcome to nginx!</title> ← صفحهٔ خودت رفت docker rm -f ch01-site
پاسخ مرحلهٔ ۴: بعد از stop و start صفحه سر جایش
است، چون لایهٔ نوشتنی همان کانتینر دستنخورده باقی مانده. بعد از rm و ساخت
دوباره، صفحهٔ پیشفرض Nginx برگشته، چون کانتینر جدید لایهٔ نوشتنی تازهای دارد و
docker cp هرگز چیزی به ایمیج اضافه نکرده بود.
Answer to step 4: after stop and start the page is still there,
because that container's writable layer is untouched. After rm and a rebuild, Nginx's default
page is back, because the new container has a fresh writable layer and docker cp never added
anything to the image.
چه چیزی کم داری: سه چیز. اول، راهی برای اینکه صفحهات بخشی از خودِ ایمیج
شود تا با ساخت دوبارهٔ کانتینر از بین نرود (فصل ۴). دوم، سیاست ریاستارت تا بعد از ریبوت
سرور خودش بالا بیاید (فصل ۱۰). سوم، اگر دادهای در کار باشد، یک volume (فصل ۶).
What is missing: three things. First, a way to make the page part of the image itself so
it survives rebuilding the container (chapter 4). Second, a restart policy so it comes back after a server
reboot (chapter 10). Third, if any data is involved, a volume (chapter 6).
- به زبان خودت بگویی داکر چه مشکلی را حل میکند و چرا ماشین مجازی جواب کاملی نبود.
- Say in your own words what problem Docker solves and why a VM was not a complete answer.
- تفاوت
imageوcontainerرا بدون مکث توضیح دهی. - Explain the difference between an
imageand acontainerwithout pausing. - یک سرویس آماده را بالا بیاوری، لاگش را ببینی، واردش شوی و تمیز حذفش کنی.
- Start a packaged service, read its logs, get inside it and remove it cleanly.
- بگویی وقتی کانتینر را حذف میکنی دقیقاً چه چیزی از بین میرود.
- Say exactly what is destroyed when you delete a container.
- وقتی خطای «نام اشغال است» یا «پورت اشغال است» دیدی، بدون جستجو حلش کنی.
- Fix “name already in use” or “port already allocated” without searching for it.