کدنامهمرجع‌های مهندسی نرم‌افزار، به فارسی
داکر · فصل ۱Docker · Chapter 1

مسئله‌ای که داکر حل می‌کند

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.

۵۵دقیقه مطالعهminutes to read
۸تمرین با پاسخsolved exercises
۳نمودارdiagrams
۰پیش‌نیاز داکریDocker prerequisites

دردی که داکر درمان می‌کند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:

colleague@laptop
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:

colleague@laptop
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:

  1. نسخهٔ زبان و کتابخانه‌های آن Language runtime and its libraries

    تو Python 3.12 داری، او 3.9. کدی که با نحو جدید نوشته‌ای، آنجا خطای نحوی می‌دهد. یا Node 22 در برابر Node 18، که یک تابع در یکی هست و در دیگری نیست.

    You have Python 3.12, they have 3.9. Code written with newer syntax is a syntax error over there. Or Node 22 versus Node 18, where a function exists in one and not the other.

  2. کتابخانه‌های سیستمی 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.

  3. پیکربندی و مسیرها Configuration and paths

    متغیر محیطی که فقط در فایل .bashrc تو هست. مسیری که روی ویندوز C:\data است و روی لینوکس /var/data. سرویسی که روی پورت ۵۴۳۲ بالا است چون تو دستی بالا آورده‌ای.

    An environment variable that lives only in your .bashrc. A path that is C:\data on Windows and /var/data on 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.

اسم از کجا آمدهWhere the name comes from

قبل از کانتینرهای باربری، هر محموله را جداگانه در انبار کشتی می‌چیدند: بشکه، گونی، جعبه، هرکدام شکل خودش. بارگیری یک کشتی چند روز طول می‌کشید. کانتینر فولادی این را عوض کرد — نه چون هوشمند بود، بلکه چون استاندارد بود: جرثقیل، کامیون، قطار و کشتی همه با یک اندازه کار می‌کردند و دیگر مهم نبود داخلش چیست. داکر همین کار را با نرم‌افزار می‌کند. محتوای کانتینر تو می‌تواند پایتون باشد یا جاوا؛ ابزار بیرونی یکی است.

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:

bash
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.

مقایسهٔ ماشین مجازی و کانتینر ماشین مجازی Virtual machines سخت‌افزار میزبان Host hardware سیستم‌عامل میزبان Host operating system Hypervisor App Libs Guest OS + kernel App Libs Guest OS + kernel App Libs Guest OS + kernel کانتینر Containers سخت‌افزار میزبان Host hardware سیستم‌عامل میزبان — یک هستهٔ مشترک Host OS — one shared kernel namespaces + cgroups Docker Engine App Libs App Libs App Libs بدون Guest OS — همین‌جا صرفه‌جویی می‌شود No guest OS — this is where the saving is
در سمت ماشین مجازی، لایهٔ نارنجی سه بار تکرار شده است. در سمت کانتینر، یک بار. On the VM side the orange layer is repeated three times. On the container side, once.

آن لایهٔ تکراری همان چیزی است که هزینه دارد. یک سیستم‌عامل کامل یعنی صدها مگابایت فایل، یک هستهٔ جدا در حافظه، و یک فرایند بوت کامل هر بار که روشنش می‌کنی. سه ماشین مجازی یعنی سه بار همهٔ اینها — حتی اگر هر سه دقیقاً یک اوبونتوی یکسان باشند.

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
نتیجهٔ عملی این تفاوتThe practical consequence

چون هسته مشترک است، کانتینر لینوکسی فقط روی هستهٔ لینوکس اجرا می‌شود. اگر روی ویندوز یا مک کار می‌کنی، داکر پشت پرده یک ماشین مجازی سبک لینوکسی بالا آورده و کانتینرها داخل آن اجرا می‌شوند. این را بدان، چون بعداً وقتی دنبال پروسهٔ کانتینر روی سیستم خودت می‌گردی و پیدایش نمی‌کنی، جوابش همین است.

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.

از Dockerfile تا کانتینرهای در حال اجرا Registry انبار ایمیج‌ها image warehouse Dockerfile دستور ساخت build recipe build Image فقط‌خواندنی · لایه‌لایه read-only · layered push pull run container A پورت ۸۰۸۰ port 8080 container B پورت ۸۰۸۱ port 8081 container C پورت ۸۰۸۲ port 8082 volume دادهٔ ماندگار persistent یک ایمیج ← هر تعداد کانتینر one image → any number of containers ساخته می‌شود، عوض نمی‌شود built once, never modified
جریان کامل: از دستور ساخت تا چند کانتینر همزمان. فصل ۴ سمت چپ این نمودار را باز می‌کند. The full flow: from build recipe to several running containers. Chapter 4 opens up the left half.

لایه‌ها: چرا ایمیج شبیه کیک است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:

bash
# تاریخچهٔ لایه‌های یک ایمیج آماده را ببین
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.

عددها روی سیستم تو فرق خواهند کردThe numbers will differ on your machine

حجم‌ها و تاریخ‌ها به نسخهٔ ایمیج و معماری پردازنده‌ات بستگی دارند. آنچه باید یکسان باشد ساختار است: لایه‌های پایین بزرگ و لایه‌های تنظیماتی صفربایت. در همهٔ خروجی‌های این مرجع، به شکل خروجی نگاه کن نه به عددها.

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.

لایه‌های ایمیج و لایهٔ نوشتنی کانتینر writable layer با حذف کانتینر از بین می‌رود dies with the container COPY ./app → 4.1 MB RUN pip install → 86 MB RUN apt install python3 → 52 MB FROM ubuntu:24.04 → 78 MB ایمیج — فقط‌خواندنی image — read only کانتینر container هر فایلی که برنامه بنویسد، فقط در لایهٔ سبز ثبت می‌شود every file the program writes lands only in the green layer
لایه‌های آبی بین همهٔ کانتینرهای ساخته‌شده از این ایمیج مشترک‌اند؛ لایهٔ سبز مخصوص هر کانتینر است. The blue layers are shared by every container of this image; the green layer belongs to one container alone.
این جمله را حفظ کنMemorise this sentence

وقتی کانتینر را حذف می‌کنی، لایهٔ نوشتنی‌اش هم حذف می‌شود و هر چیزی که داخلش نوشته شده بود از بین می‌رود. اگر پایگاه‌داده‌ای را داخل کانتینر بالا آورده باشی و بدون 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:

bash
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

bash
docker run hello-world

بار اول خروجی چیزی شبیه این است:

The first time, the output looks something like this:

output
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:

  1. 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.

  2. Pulling from library/hello-world

    پس رفت سراغ رجیستری پیش‌فرض. library یعنی این ایمیج «رسمی» است و صاحب مشخصی ندارد؛ ایمیج‌های شخصی به شکل username/image هستند.

    So it went to the default registry. library means this is an “official” image with no individual owner; personal images look like username/image.

  3. c1ec31eb5944: Pull complete

    این عدد شناسهٔ یک لایه است. ایمیج‌های بزرگ‌تر اینجا چند خط دارند — هر خط یک لایه، و همه به‌صورت موازی دانلود می‌شوند.

    That identifier is a layer. Larger images show several lines here — one per layer, all downloading in parallel.

  4. Digest: sha256:…

    اثر انگشت رمزنگاری‌شدهٔ دقیقاً همین ایمیج. برچسب (tag) ممکن است فردا به ایمیج دیگری اشاره کند، اما این عدد هرگز عوض نمی‌شود. فصل ۱۱ رویش کار می‌کند.

    The cryptographic fingerprint of exactly this image. A tag may point somewhere else tomorrow; this number never changes. Chapter 11 works with it.

  5. و بعد برنامه اجرا شد و تمام شد 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:

bash
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:

bash
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:

alpine container
/ # 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:

bash
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 زنده است قفل می‌ماند.
  • -d or --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 always host:container.

ببینیم زنده است:

Let us check that it is alive:

bash
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:

bash
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
PowerShell
# همان کار روی ویندوز، بدون 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:

bash
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:

bash
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:

bash
# متوقف کن — کانتینر هست، فقط خاموش است
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:

bash
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”.

حالت‌های کانتینر و دستورهای انتقال created ساخته، نرفته made, idle start running پروسهٔ اصلی زنده main process alive در docker ps دیده می‌شود visible in docker ps paused pause unpause stop یا پایان پروسه or process ends exited خاموش ولی موجود stopped but present فقط با ps -a only via ps -a start rm removed داده رفت data gone ‏docker run یعنی create و start با هم · --rm یعنی موقع exit مستقیم به removed برو docker run is create + start in one · --rm jumps from exit straight to removed تا وقتی به removed نرسیده‌ای، نامش اشغال است until it reaches removed, its name stays 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:

bash
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:

bash
راه اول: کانتینر قبلی را روشن کن (همان دادهٔ قبلی سر جایش است)
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:

bash
# اول ببین چه چیزی قرار است حذف شود
docker ps -a --filter status=exited

# بعد حذف کن — تأیید می‌خواهد
docker container prune
دستوری که نباید بی‌فکر بزنیA command not to type casually

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:

bash — host
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:

bash
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.

اگر روی ویندوز یا مک هستیIf you are on Windows or macOS

دستور 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 web then run. For throwaway tests, use --rm from 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, then ss -ltnp | grep 8080 on Linux or netstat -ano | findstr 8080 on 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 -a and read STATUS; (2) you omitted -p, so nothing was published; (3) you reversed the order: -p 80:8080 instead 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 localhost inside 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 latest carries 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, not nginx.

مرجع سریع این فصلQuick reference

همهٔ دستورهایی که در این فصل دیدی، یک‌جا. این جدول را نگه دار؛ ۹۰ درصد کار روزمره همین‌هاست.

Every command from this chapter in one place. Keep this table; it covers 90% of daily work.

دستورهای فصل ۱Chapter 1 commands
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.

تمرین‌های حل‌شدهSolved ۰ / ۸
تمرین ۱٫۱Exercise 1.1 مبتدیBeginner ۵ دقیقه5 min

داکر را اجرا کن و ثابت کن که هم 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
bash
# ۱) سلامت 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.

تمرین ۱٫۲Exercise 1.2 مبتدیBeginner ۱۰ دقیقه10 min

بدون نصب هیچ‌چیزی روی سیستمت، وارد یک اوبونتو ۲۴٫۰۴ شو، نسخهٔ دقیق سیستم‌عاملش را پیدا کن، یک فایل بساز، و بیرون بیا. بعد ثابت کن که آن کانتینر دیگر وجود ندارد.

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
bash
docker run -it --rm ubuntu:24.04 bash
ubuntu container
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
bash
# هیچ کانتینر اوبونتویی باقی نمانده است
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.

تمرین ۱٫۳Exercise 1.3 مبتدیBeginner ۱۰ دقیقه10 min

یک وب‌سرور 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
bash
# ۱) بالا آوردن
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.

میان‌برShortcut

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.

تمرین ۱٫۴Exercise 1.4 متوسطIntermediate ۱۵ دقیقه15 min

از یک ایمیج، سه وب‌سرور مستقل روی پورت‌های ۸۰۸۱، ۸۰۸۲ و ۸۰۸۳ بالا بیاور. بعد با 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
bash
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
bash
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.

bash
# تمیزکاری — چند کانتینر را می‌شود با هم داد
docker rm -f w1 w2 w3
تمرین ۱٫۵Exercise 1.5 متوسطIntermediate ۲۰ دقیقه20 min

ثابت کن که دادهٔ نوشته‌شده داخل کانتینر با 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
bash
# ۱) کانتینری که زنده می‌ماند — بدون --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.

راه درست چیست؟What is the right way?

اینکه دادهٔ مهم را در لایهٔ نوشتنی بگذاری اشتباه است، نه یک ترفند. راه درست 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.

تمرین ۱٫۶Exercise 1.6 متوسطIntermediate ۱۵ دقیقه15 min

دو ایمیج 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
bash
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

↑ دقیقاً یکی — هر دو روی همان آلپاین ساخته شده‌اند
bash
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.

تمرین ۱٫۷Exercise 1.7 پیشرفتهAdvanced ۲۵ دقیقه25 min

ثابت کن که کانتینر واقعاً فقط یک پروسه روی میزبان است. یک کانتینر بالا بیاور، شناسهٔ پروسه‌اش روی میزبان را پیدا کن، و نشان بده همان پروسه داخل کانتینر شمارهٔ ۱ است. سپس با یک سقف حافظه، 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
bash — host
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.

bash
# بخش دوم: سقف حافظه ۲۰ مگابایت، و تلاش برای گرفتن ۲۰۰ مگابایت
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.

تمرین ۱٫۸Exercise 1.8 پیشرفتهAdvanced ۲۵ دقیقه25 min

بدون ساختن هیچ ایمیجی، کاری کن که وب‌سرور 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
bash
# ۱) صفحهٔ خودت را بساز
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:

bash
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.

  1. تکرارپذیر نیست. برای بالا آوردن همین سرویس روی سرور دیگر باید یادت باشد که بعد از run یک cp هم بزنی. این دقیقاً همان «راهنمای نصبی» است که در بخش اول گفتیم منسوخ می‌شود.
  2. It is not reproducible. To bring the same service up elsewhere you must remember to run a cp after run. That is exactly the “install README” that section one said always rots.
  3. فناپذیر است. اگر کانتینر را rm کنی، صفحه‌ات می‌رود. کانتینر باید دورانداختنی باشد؛ اگر چیز باارزشی فقط داخل آن باشد، دیگر نیست.
  4. It is fragile. rm the container and your page is gone. A container should be disposable; if something valuable exists only inside it, it no longer is.
  5. قابل بازبینی نیست. این تغییر در هیچ فایلی ثبت نشده، پس در Git نیست و کسی نمی‌تواند رویش review بزند.
  6. 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.

bash
docker rm -f myweb

پروژهٔ پایان فصلEnd-of-chapter project

پروژهٔ کوچک · حدود ۳۰ دقیقهMini project · about 30 minutes

دفترچهٔ اولین کانتینر

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

  1. صفحه را آماده کنPrepare the page

    فایل lab/ch01/site/index.html را باز کن و اسم خودت را در آن بگذار. می‌توانی صفحهٔ خودت را هم از صفر بنویسی.

    Open lab/ch01/site/index.html and put your own name in it. Writing your own page from scratch is fine too.

  2. سرویس را بالا بیاورBring the service up

    با نام ch01-site و روی پورت ۸۰۸۰. صفحه را با docker cp داخلش بگذار و در مرورگر ببین.

    Named ch01-site, on port 8080. Put the page in with docker cp and view it in a browser.

  3. رفتارش را ثبت کنRecord its behaviour

    سه بار صفحه را در مرورگر رفرش کن، بعد لاگ را بگیر. یک آدرس ناموجود مثل /nothing را هم صدا بزن و ببین در لاگ چه کد وضعیتی ثبت می‌شود.

    Refresh the page three times, then read the logs. Also request a missing path like /nothing and see which status code appears in the log.

  4. مقاومتش را بسنجTest its resilience

    کانتینر را stop و دوباره start کن. آیا صفحه‌ات هنوز هست؟ بعد rm کن و از نو بساز. حالا چه؟ جواب هر دو را بنویس.

    stop then start the container. Is your page still there? Then rm it and rebuild. Now what? Write down both answers.

  5. دفترچه را بنویسWrite the logbook

    در lab/ch01/logbook.md این‌ها را ثبت کن: تمام دستورهایی که زدی، خروجی هرکدام در یک خط، پاسخ سؤال مرحلهٔ ۴، و یک خط دربارهٔ اینکه اگر می‌خواستی این سرویس همیشه بالا بماند چه چیزی کم داری.

    In lab/ch01/logbook.md record: 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
bash
# ۲) بالا آوردن و قرار دادن صفحه
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).

در پایان این فصل باید بتوانیBy the end of this chapter you should be able to
  • به زبان خودت بگویی داکر چه مشکلی را حل می‌کند و چرا ماشین مجازی جواب کاملی نبود.
  • 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 image and a container without 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.