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

شبکه: bridge، DNS داخلی و publish

Networking: bridge networks, internal DNS, and port publishing

دو تا container داریم و هر دو Running هستند. حالا چطور یکی باید دیگری را پیدا کند؟ جواب از localhost شروع نمی‌شود؛ باید caller، شبکه و port درست را پیدا کنیم.

We have two running containers. How does one find the other? The answer does not start with localhost; it starts by finding the caller, the network, and the right port.

۱۲۰دقیقهٔ مطالعه و اجراminutes
۱۸تمرین با پاسخsolved exercises
۴نمودار مفهومیdiagrams
۱آزمایشگاه کاملfull lab

داده ماند؛ حالا web چطور پایگاه‌داده را پیدا کند؟The data stayed; how does web find the database?

فصل قبل پایگاه‌داده‌مان را از عمر container جدا کردیم. حالا یک web داریم و یک پایگاه‌داده؛ هر دو هم Running هستند. سؤال بعدی خیلی طبیعی است: web آدرس پایگاه‌داده را چه بنویسد؟ اولین چیزی که خیلی‌ها امتحان می‌کنند localhost است. بیایید اول همین حدس را خراب کنیم.

In Chapter 6 we separated data from the container lifetime. Now take a more realistic step: one container runs the web app and another runs the database. Both are up, but web must reach the database. The natural first guess is: “Should I use localhost?”

Experiment A · localhost from the wrong place
$ docker network create demo-net
demo-net

$ docker run -d --name web --network demo-net nginx:alpine
...

$ docker run --rm --network demo-net curlimages/curl:latest \
  -fsS --max-time 2 http://localhost
curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server

$ docker run --rm --network demo-net curlimages/curl:latest \
  -fsS http://web | head -n 2
<!DOCTYPE html>
<html>

هر دو دستور از داخل یک container موقت اجرا می‌شوند. وقتی آنجا می‌نویسی localhost، در واقع داری به همان container موقت اشاره می‌کنی؛ Nginx داخل container دیگری به نام web است. وقتی به http://web می‌روی، این بار نامِ container مقصد را روی شبکهٔ مشترک استفاده کرده‌ای.

Both commands ran from a temporary client container. In the first command, localhost means the loopback of that client; nginx is not inside the client. In the second, web is the container name on demo-net, and Docker resolves it to web's internal address.

مدل ذهنی اصلیThe main mental model

قاعده‌ای که کل فصل روی آن می‌ایستد همین است: localhost یعنی «همین‌جایی که من هستم». از روی host یعنی خود host؛ داخل web یعنی خود web؛ داخل client یعنی خود client. localhost هیچ‌وقت اسم مستعار «بقیهٔ containerها» نیست.

localhost means “this network namespace where the command runs.” From the host it is the host; from web it is web; from client it is client.

یک localhost، چند «همین‌جا»Three localhosts: one word, several scopes

فعلاً لازم نیست وارد جزئیات kernel شویم. هر container را مثل یک اتاق کوچک شبکه تصور کن؛ loopback، interface و دید خودش را دارد. Docker می‌تواند بین این اتاق‌ها راه بسازد، اما localhost همیشه داخل همان اتاق می‌ماند. همین تصویر ساده بعداً خیلی از خطاهای شبکه را توضیح می‌دهد.

You do not need kernel details at the start. For this chapter, think of a network namespace as a network room: each container has its own loopback, interface, routes, and DNS view. Docker can connect those rooms with interfaces and a bridge, but localhost does not cross a room's wall.

The host, web container, and client container each have a separate localhost scope Docker host host localhost127.0.0.1:8080browser lives here web containerits own localhostnginx :80 client containerits own localhostcurl runs here host → webclient → web

کادرها سه محدوده مستقل را نشان می‌دهند، نه سه اسم برای یک آدرس. فلش host به web فقط مسیر publish شده را معنا می‌کند؛ فلش client به web مسیر شبکه داخلی را نشان می‌دهد. هیچ‌کدام localhost را به اتاق دیگری منتقل نمی‌کنند.

The boxes show three independent scopes, not three names for one address. The host-to-web arrow means a published path; the client-to-web arrow means an internal network path. Neither changes what localhost means inside another room.

پس قبل از اینکه port را عوض کنی، یک سؤال خیلی ساده بپرس: «این دستور دقیقاً از کجا اجرا شده؟» اگر از client اجرا شده، localhost:80 فقط دنبال سرویس داخل همان client می‌گردد. برای رسیدن به web باید واقعاً راهی بین دو container وجود داشته باشد و مقصد را هم درست صدا بزنی.

When you see a failure, ask where the command ran before changing ports. If it ran in client, localhost:80 needs a service inside that client. To reach web, you need a path between the containers and a destination name or address.

bridge چیست؟ یک شبکهٔ مشترک با درِ مشخصWhat is a bridge? A shared network with a clear boundary

حالا باید بین این «اتاق‌ها» یک راه مشترک بسازیم. bridge شبکه را می‌توانی مثل یک شبکهٔ محلی نرم‌افزاری ببینی: containerهایی که عضو آن هستند interface و آدرس می‌گیرند و می‌توانند به سرویس‌های هم دسترسی داشته باشند. container بیرون این شبکه، چنین مسیر داخلی‌ای را خودکار ندارد.

When containers join a bridge network, Docker gives each one an interface and an address from that network's subnet. A bridge is a software local network: members can reach one another's ports, while containers on other networks do not normally have that internal path.

Docker از اول یک شبکهٔ bridge پیش‌فرض دارد. برای آزمایش‌های ساده بد نیست، اما وقتی چند سرویس متعلق به یک برنامه داری بهتر است شبکهٔ خودت را نام‌گذاری کنی؛ مثلاً demo-net. آن‌وقت دقیقاً معلوم است چه containerهایی عضو این مجموعه‌اند و نام‌ها در کدام شبکه معنی دارند.

Separate two bridge cases. Docker Engine creates the default bridge network, and a container without --network normally joins it. A user-defined bridge is one you create with docker network create and name for an application stack.

ویژگیdefault bridgeuser-defined bridge
ورود containerپیش‌فرض، اگر network ندهیبا --network NAME یا network connect
پیداکردن با نامautomatic DNS برای container name ندارد؛ link قدیمی استautomatic DNS با name یا alias همان network
مرزبندی stackcontainerهای بی‌ربط هم در یک network مشترک‌اندفقط اعضای متصل به همان network در scope هستند
اتصال در طول عمرمدیریت محدودتر و legacyconnect و disconnect بدون recreate
انتخاب عملیبرای آزمایش ساده یا سازگاری قدیمیانتخاب معمول برای applicationهای چندcontainerی

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

“Better” does not mean automatically secure. A user-defined bridge gives a better scope and name-resolution model; you still choose published ports, credentials, and service access deliberately.

یک شبکه بساز و نام را به‌جای IP به‌کار ببرCreate a network and use the name instead of the IP

آدرس IP داخلی container چیزی نیست که بخواهی داخل تنظیمات برنامه حک کنی. ممکن است web را حذف و دوباره بسازی و IP عوض شود. اسم web روی شبکهٔ خودت قرارداد بهتری است: برنامه مقصد را با نام می‌شناسد و Docker آدرس فعلی را پیدا می‌کند.

When web is recreated, its internal IP may change. If the application config contains that IP, every recreate becomes a small incident. A container name on a user-defined network is a more stable contract for this purpose.

create · attach · resolve
$ docker network create --driver bridge demo-net
demo-net

$ docker network ls
NETWORK ID     NAME       DRIVER    SCOPE
...            bridge     bridge    local
...            demo-net   bridge    local

$ docker run -d --name web --network demo-net nginx:alpine
...

$ docker run --rm --network demo-net curlimages/curl:latest   -fsS -v http://web

# representative lines from curl
* Connected to web (...) port 80
< HTTP/1.1 200 OK

در خروجی curl -v خطی شبیه Connected to web را پیدا کن. نکته همین است: ما IP را ننوشته‌ایم. client نام web را در همان شبکه پیدا کرده و بعد درخواست HTTP را به آدرس فعلی آن فرستاده است.

In PowerShell, Select-String only shortens the displayed output; on Linux or macOS use grep -E 'Connected|HTTP/'. The useful line is Connected to web: client resolved web to an IP without us memorizing that IP.

A client resolves the name web through the user-defined network's internal DNS and requests web port 80 client containercurl http://webcaller demo-netembedded DNSweb → 172.19.0.2same network scope web containernginx :80destination name lookupHTTP request

در نمودار دو اتفاق جدا داریم. اول نام web به آدرس داخلی فعلی‌اش پیدا می‌شود؛ بعد درخواست HTTP به port 80 آن آدرس می‌رود. جدا دیدن این دو مرحله مهم است، چون «نام پیدا نشد» با «نام پیدا شد ولی اتصال رد شد» دو مشکل متفاوت‌اند.

The first arrow is only name resolution: web becomes an internal address within demo-net. The second arrow is the actual HTTP request to web's port 80; keep the two steps distinct.

این نام جادویی و global نیست. اگر client را از demo-net جدا کنی، دیگر همان DNS محدوده را ندارد و web پیدا نمی‌شود یا مسیرش در دسترس نیست. همین وابستگی به عضویت دلیل خوبی است که اسم شبکه را در کنار سرویس‌ها مدیریت کنی.

This name is not magical or global. Disconnect client from demo-net and it no longer has that DNS scope or path to web. That membership dependency is why the network name belongs in the service setup.

وقتی ارتباط قطع شد، خود شبکه را نگاه کنWhat does network inspect show?

وقتی client به web نمی‌رسد، حدس‌زدن IP آخرین کاری است که می‌خواهیم بکنیم. خود demo-net را inspect کن. آنجا می‌بینی شبکه چه اسمی دارد، driver چیست و کدام containerها واقعاً عضو آن هستند. خیلی وقت‌ها مشکل همین است که یکی از دو طرف اصلاً روی شبکه‌ای که فکر می‌کردی نیست.

When “client cannot reach web,” inspect the network instead of guessing an IP. The network name, driver, subnet, gateway, and connected containers appear together in this output.

representative network inspect · addresses vary
$ docker network inspect demo-net
[
  {
    "Name": "demo-net",
    "Driver": "bridge",
    "Scope": "local",
    "IPAM": {
      "Config": [{ "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" }]
    },
    "Containers": {
      "a1b2...": {
        "Name": "web",
        "IPv4Address": "172.19.0.2/16"
      }
    }
  }
]

خروجی را با سه سؤال بخوان: آیا شبکهٔ درست را نگاه می‌کنم؟ آیا web واقعاً در فهرست اعضاست؟ آیا آدرسی گرفته؟ IP برای عیب‌یابی خوب است، ولی آن را تبدیل به تنظیم دائمی برنامه نکن؛ اسم سرویس برای همین ساخته شده است.

Use the output to answer three questions: did I inspect the right network name? Is web actually listed under Containers? Do the address and subnet match the situation? The IP is useful for diagnosis, but should not become permanent application configuration.

برای دیدن اتصال‌های خود container هم docker inspect web --format '{{json .NetworkSettings.Networks}}' بزن. این دو inspect مکمل‌اند: یکی از دید شبکه حرف می‌زند، دیگری از دید container.

To see a container's own attachments, run docker inspect web --format '{{json .NetworkSettings.Networks}}'. The two inspections complement each other: one speaks from the network's view, the other from the container's view.

port داخلی با port روی host یکی نیستAn internal port is not a host port

Nginx داخل web روی port 80 گوش می‌دهد. اگر client روی همان شبکه باشد، می‌تواند مستقیم به web:80 برود؛ این مسیر اصلاً لازم نیست از portهای host عبور کند. این همان جایی است که مفهوم شبکهٔ داخلی با publish از هم جدا می‌شود.

nginx listens on port 80 inside web. That port belongs to web's world. When client on the same network requests http://web:80, it reaches the internal port directly and needs no -p.

مرورگر تو روی host عضو demo-net نیست. اگر می‌خواهی از localhost:8080 وارد Nginx شوی، باید یک در روی host باز کنی: -p 8080:80. سمت چپ دری است که روی host می‌زنی؛ سمت راست port سرویس داخل container است.

A browser on the host is not a member of demo-net. To let the host enter through localhost:8080, you need a mapping: -p 8080:80. The left number is the host port; the right number is the container port.

callerآدرس مقصد-p لازم است؟مسیر request
client روی demo-nethttp://web:80نهclient → bridge → web:80
browser روی hosthttp://localhost:8080بله، 8080:80host:8080 → mapping → web:80
container روی network دیگرhttp://web:80برای internal path کافی نیستباید network مشترک یا مسیر دیگری داشته باشد
host port collision · same host port twice
$ docker run -d --name web-copy --network demo-net \
  -p 127.0.0.1:8080:80 nginx:alpine
docker: Error response from daemon: driver failed programming external connectivity
on endpoint web-copy (...): Bind for 127.0.0.1:8080 failed: port is already allocated.

این خطا دربارهٔ port داخلی nginx نیست؛ host port ۸۰۸۰ قبلاً مالک دارد. اول docker ps و نگاشت را پیدا کن، بعد host port را عوض کن یا اگر caller داخلی است اصلاً publish را حذف کن.

This failure is not about nginx's internal port; host port 8080 is already owned. Find the existing mapping with docker ps, then change the host port or remove publishing if the caller is internal.

A host browser reaches web through a published host port, while the container still listens on port 80 browser on hostlocalhost:8080caller published mappingHOST 8080→ CONTAINER 80NAT / forwarding web containernginx:80destination host requestforward to

هر فلش درخواست مسیر را نشان می‌دهد. publish یک درِ host می‌سازد؛ port داخلی عوض نمی‌شود و nginx هنوز روی ۸۰ است. مسیر داخلی client به web از این نگاشت عبور نمی‌کند.

Each arrow is a request path. Publishing creates a host-side door; the internal port does not change and nginx still listens on 80. The internal client-to-web path does not need to cross this mapping.

در اجرای امن‌ترِ محلی می‌توانی host IP را هم صریح بدهی: -p 127.0.0.1:8080:80. اگر IP host را ندهی، Docker معمولاً port را روی interfaceهای host منتشر می‌کند؛ پس publish را با «فقط برای مرورگر خودم» یکی ندان.

For a safer local run, bind the host address explicitly: -p 127.0.0.1:8080:80. Without a host IP, Docker normally publishes on the host interfaces, so “published” does not automatically mean “only for my browser.”

یک بار web را بدون publish و بار دوم با publish اجرا کنRun web once without publishing, then with publishing

حالا این دو مسیر را کنار هم ببین. بار اول Nginx فقط عضو شبکهٔ داخلی است: client باید به web:80 برسد، ولی host هنوز دری روی 8080 ندارد. بار دوم همان web را با -p می‌سازیم؛ چیزی در ارتباط داخلی عوض نشده، فقط یک مسیر تازه از host به container اضافه شده است.

This experiment puts the two traffic paths side by side. First web is only a network member. client should succeed, while host localhost:8080 should not reach this web. Then we recreate web and add one host mapping.

internal path versus host path
$ docker run --rm --network demo-net curlimages/curl:latest -fsS http://web:80 > $null
$LASTEXITCODE
0

# Run on the host; choose a free host port for this experiment.
PS> curl.exe --fail --max-time 2 http://127.0.0.1:8080
curl: (7) Failed to connect to 127.0.0.1 port 8080

$ docker rm -f web
web

$ docker run -d --name web --network demo-net \
  -p 127.0.0.1:8080:80 nginx:alpine
...

PS> curl.exe --fail http://127.0.0.1:8080 | Select-Object -First 1
<!DOCTYPE html>

$ docker port web
80/tcp -> 127.0.0.1:8080

اگر client قبل از publish هم به web می‌رسد، یعنی ارتباط داخلی به -p وابسته نیست. publish فقط برای callerای لازم است که بیرون آن شبکه است—مثل مرورگر روی host. اگر 8080 روی سیستم تو اشغال است، عدد دیگری انتخاب کن؛ مفهوم عوض نمی‌شود.

The first line proves internal traffic does not depend on publishing. The second only tests host localhost:8080; another process may already own that port, so choose a free one on your machine. After recreation, client still uses http://web:80 and the host browser has the new path.

EXPOSE همان publish نیستEXPOSE is not publish

EXPOSE 80 در Dockerfile فقط قرارداد/metadata دربارهٔ port مورد انتظار image است. دسترسی host با -p ساخته می‌شود؛ و دسترسی containerهای روی شبکه مشترک به‌طور معمول به -p نیاز ندارد.

EXPOSE 80 in a Dockerfile is a contract or metadata about the image's expected port. Host access is created with -p; containers on the same network normally do not need -p for their internal path.

default bridge چه کم دارد؟What is missing from the default bridge?

شبکهٔ پیش‌فرض Docker خراب یا ممنوع نیست؛ برای آزمایش ساده کار می‌کند. مشکل این است که وقتی یک برنامه چند سرویس دارد، نام‌گذاری و مرزبندی روشن می‌خواهی. شبکهٔ نام‌دار خودت این رابطه را واضح‌تر می‌کند و پیدا کردن containerها با نام را هم ساده می‌کند.

The default bridge is not necessarily “broken.” It is useful for quick experiments, and containers on it can communicate through internal IPs. The problem appears when you need service names, stack boundaries, and attach/detach behavior during a container's lifetime.

the legacy name-resolution surprise
$ docker run -d --name legacy-web nginx:alpine
...

$ docker run --rm --network bridge curlimages/curl:latest \
  -fsS --max-time 2 http://legacy-web
curl: (6) Could not resolve host: legacy-web

# The same container's IP may work on the default bridge,
# but that IP is not a stable application name.

این رفتار با user-defined bridge فرق دارد: آنجا Docker embedded DNS نام container یا alias را در همان شبکه پاسخ می‌دهد. به همین دلیل برای web و پایگاه‌داده یک شبکه نام‌دار بساز؛ به link قدیمی یا نوشتن IP در تنظیمات برنگرد.

This differs from a user-defined bridge, where Docker's embedded DNS resolves a container name or alias on that network. That is why you create a named network for web and database instead of returning to legacy links or putting an IP in application config.

container را به شبکه وصل و جدا کنConnect and disconnect a container

اگر container از قبل روشن است، برای اضافه‌کردنش به یک شبکه لازم نیست حذفش کنی. docker network connect یک اتصال شبکهٔ تازه به همان container اضافه می‌کند و disconnect همان اتصال را برمی‌دارد. اینجا دقیقاً می‌توانی ببینی «عضویت در شبکه» یک ویژگی مستقل از خودِ روشن‌بودن container است.

When you create a container with --network, the attachment exists from the start. If a container is already running, you do not need to remove it to add a user-defined network; docker network connect adds another interface.

attach, observe, detach
$ docker network create side-net
side-net

$ docker run -d --name helper alpine sleep 3600
...

$ docker network connect side-net helper

$ docker network inspect side-net
...
"Name": "helper",
"IPv4Address": "172.20.0.2/16"

$ docker network disconnect side-net helper

$ docker network inspect side-net
"Containers": {}

attach شدن یعنی کمکی حالا یک نقطهٔ اتصال در side-net دارد؛ detach شدن همان نقطهٔ اتصال را برمی‌دارد. اگر کمکی تنها شبکه خود را از دست بدهد، commandهای بعدی‌اش فقط loopback را می‌بینند و برای ارتباط شبکه‌ای شکست می‌خورند.

Connecting means helper now has an endpoint in side-net; disconnecting removes that endpoint. If helper loses its only network, later commands see only loopback and network requests fail.

یک container می‌تواند روی دو شبکه باشدOne container can belong to two networks

یک برنامهٔ واقعی همیشه یک شبکه ندارد. شاید web روی شبکهٔ جلویی باشد، پایگاه‌داده روی شبکهٔ پشتی و یک gateway لازم باشد هر دو طرف را ببیند. قرار نیست برای راحتی همه را به همه‌جا وصل کنیم؛ هر container فقط شبکه‌هایی را می‌گیرد که واقعاً لازم دارد.

A more realistic setup has web on front-net, database on back-net, and a gateway or worker that needs both. That dual attachment should not make every container see everything.

A gateway is attached to two isolated bridge networks while web and database stay on their own side front-netweb-facing scope webfront only gateway / workerattached to bothtwo interfaces back-netdata-facing scope dbback only web → gatewaygateway → dbattachment boundary

فلش‌های افقی درخواست را نشان می‌دهند و فلش عمودی attachment را: gateway در هر دو شبکه عضو است، اما web و db فقط در محدوده خودشان هستند. همین عضویت تعیین می‌کند کدام نام قابل پیدا و کدام مسیر قابل استفاده باشد.

The horizontal arrows are request paths and the vertical arrow is an attachment. The gateway belongs to both networks, while web and db stay in their own scopes. Membership determines which names resolve and which paths exist.

isolation experiment
$ docker network create front-net
front-net
$ docker network create back-net
back-net

$ docker run -d --name web-front --network front-net nginx:alpine
...
$ docker run -d --name db-back --network back-net nginx:alpine
...

$ docker run --rm --network front-net curlimages/curl:latest \
  -fsS --max-time 2 http://db-back
curl: (6) Could not resolve host: db-back

$ docker network connect front-net db-back

$ docker run --rm --network front-net curlimages/curl:latest \
  -fsS http://db-back | Select-Object -First 1
<!DOCTYPE html>

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

Here db-back belongs to both networks after connect, so a client on front-net can resolve it. This is not a complete security boundary; it is a practical path boundary. Use separate networks and limited membership for a clearer scope.

وقتی «شبکه کار نمی‌کند»، مسیر درخواست را قدم‌به‌قدم دنبال کنDebug from the request path

«شبکه کار نمی‌کند» تقریباً هیچ اطلاعاتی به ما نمی‌دهد. اول بپرس درخواست از کجا شروع شده، چه نامی را صدا زده، دو container عضو چه شبکه‌ای هستند و برنامه روی کدام port گوش می‌دهد. همین چند سؤال معمولاً مشکل را از یک مه غلیظ به یک نقطهٔ مشخص تبدیل می‌کند.

“The network does not work” is not yet a solvable problem. Turn the request into smaller questions: who is the caller? Where does it run? Which name or address does it use? Which networks attach the two containers? Which port is the service actually listening on? Is this internal traffic or host-to-container?

نشانهمسیر محتمل خراباول چه چیزی را بررسی کن؟
curl http://localhost از client fail می‌شودcaller را به مقصد اشتباه گرفته‌ایlocalhost همان client است؛ name واقعی web را امتحان کن
name resolve نمی‌شودname اشتباه است یا caller در network مشترک نیستdocker network inspect و spelling name/alias
containerها networkهای متفاوت دارندDNS scope و path مشترک ندارندهر دو را به user-defined network مشترک وصل کن یا مسیر طراحی‌شده بده
port host قبلاً گرفته شدهدو mapping یک HOST_PORT می‌خواهندdocker ps و port آزاد؛ host port را عوض کن
-p 80:8080 جواب نمی‌دهدجهت mapping برعکس یا internal port غلط استچپ host، راست container؛ port واقعی listen را inspect کن
service فقط روی 127.0.0.1 گوش می‌دهداز interface شبکهٔ container قابل رسیدن نیستبرای service بین containerها معمولاً 0.0.0.0 یا interface مناسب لازم است
بعد از recreate آدرس عوض شدهIP را به‌عنوان config پایدار نگه داشته‌ایدر user-defined network با name سرویس کار کن
client از host port داخلی را می‌زندinternal traffic را از publish برگردانده‌ایروی network مشترک مستقیم http://web:80 بزن
name resolves, port is wrong
$ docker run --rm --network demo-net curlimages/curl:latest \
  -fsS --max-time 2 http://web:8080
curl: (7) Failed to connect to web port 8080: Couldn't connect to server

# The same service answers on its internal port:
$ docker run --rm --network demo-net curlimages/curl:latest \
  -fsS http://web:80 | Select-Object -First 1
<!DOCTYPE html>

در اینجا نام درست پیدا شده، اما port مقصد با سرویس گوش‌دهنده یکی نیست. این خطا از «Could not پیدا host» یک مرحله جلوتر است: DNS کار کرده و حالا باید port و bind آدرس را بررسی کنی.

Here the name resolved, but the destination port does not match the listener. This is one step past “Could not resolve host”: DNS worked, so now inspect the port and bind address.

دو خطا را از هم جدا کن. اگر نام پیدا نمی‌شود، هنوز به مرحلهٔ اتصال نرسیده‌ای؛ عضویت شبکه و DNS را نگاه کن. اگر نام پیدا می‌شود ولی connection refused می‌گیری، یک قدم جلوتر رفته‌ای: مقصد پیدا شده، اما روی آن port سرویسی گوش نمی‌دهد یا مسیر port اشتباه است.

Keep two nearby failures separate: “name not found” usually happens before a TCP connection; “connection refused” means the destination resolved but no listener or correct mapping exists there. That distinction changes the diagnosis path.

تمرین‌ها: قبل از تغییر دستور، مسیر را حدس بزنExercises: predict the path before changing the command

تمرین‌ها را با حفظ‌کردن دستور حل نکن. برای هر سناریو اول روی کاغذ چهار چیز بنویس: درخواست از کجا می‌آید؟ مقصد چه اسمی دارد؟ هر طرف عضو کدام شبکه است؟ port داخلی یا host port کدام است؟ وقتی این چهار مورد روشن شد، دستور تقریباً خودش معلوم می‌شود.

These exercises are not syntax memorization. For each one, name the caller, network, destination name, and port before choosing a command or diagnosis.

۱. localhost از کجا می‌آید؟1. Where does localhost come from?

web و client روی demo-net هستند. اگر داخل client به http://localhost:80 curl بزنی، انتظار موفقیت داری یا شکست؟

web and client are on demo-net. If curl inside client requests http://localhost:80, do you expect success or failure?

پاسخ و دلیلSolution and reasoning

انتظار شکست دارم، چون localhost loopback client است. مقصد درست برای ترافیک داخلی http://web:80 است؛ نام web از DNS همان شبکه پیدا می‌شود.

I expect failure because localhost is client’s loopback. The correct internal destination is http://web:80; web is resolved by that network’s DNS.

۲. مسیر را رسم کن2. Draw the path

برای مرورگر روی host که به web با localhost:8080 می‌رسد، چهار گرهٔ مسیر را بنویس.

Write the four path nodes for a host browser reaching web through localhost:8080.

پاسخ و دلیلSolution and reasoning

مرورگر روی host → host port 8080 → نگاشت publish → web container port 80. اگر -p 127.0.0.1:8080:80 باشد، host آدرس هم بخشی از در ورودی است.

The path is browser on host → host port 8080 → published mapping → web container port 80. With -p 127.0.0.1:8080:80, the host address is also part of the entry point.

۳. publish لازم هست؟3. Is publishing needed?

یک API و یک worker روی یک user-defined bridge هستند و فقط worker باید API را صدا بزند. آیا -p 9000:9000 لازم است؟

An API and a worker share a user-defined bridge, and only worker needs to call the API. Is -p 9000:9000 needed?

پاسخ و دلیلSolution and reasoning

نه. worker باید به نام API و port داخلی مثل http://api:9000 وصل شود. publish فقط وقتی لازم است که host یا یک مسیر بیرون از آن شبکه به سرویس وارد شود.

No. worker should use the API name and internal port, such as http://api:9000. Publish is needed when the host or a client outside that network must enter the service.

۴. DNS از کجا آمد؟4. Where did DNS come from?

چرا http://web در demo-net کار می‌کند، اما اسم web در هر container دلخواهی global نیست؟

Why does http://web work on demo-net, but web is not a global hostname in every container?

پاسخ و دلیلSolution and reasoning

Docker در user-defined شبکه embedded DNS دارد و نام container را در همان محدوده به آدرس نقطهٔ اتصال تبدیل می‌کند. container خارج از شبکه همان DNS عضویت و مسیر را ندارد.

Docker provides embedded DNS on a user-defined network and maps the container name to an endpoint in that scope. A container outside the network has neither that DNS membership nor that path.

۵. default یا user-defined؟5. Default or user-defined?

برای stack شامل web و پایگاه‌داده که باید با نام همدیگر را پیدا کنند، کدام شبکه را انتخاب می‌کنی و چرا؟

For a web and database stack that must find each other by name, which network do you choose and why?

پاسخ و دلیلSolution and reasoning

user-defined bridge. نام‌گذاری، automatic DNS و امکان connect/disconnect روشن‌تری دارد و containerهای نامرتبط را از همان محدوده بیرون نگه می‌دارد.

A user-defined bridge. It provides a clearer naming, automatic DNS, and attach/detach model while keeping unrelated containers outside that scope.

۶. inspect را تفسیر کن6. Interpret inspect

در docker network inspect demo-net نام web در بخش Containers نیست. قبل از تغییر IP چه نتیجه‌ای می‌گیری؟

web is absent from the Containers section of docker network inspect demo-net. What do you conclude before changing an IP?

پاسخ و دلیلSolution and reasoning

web عضو این شبکه نیست یا اسم شبکه را اشتباه inspect کرده‌ام. اول docker inspect web و docker network ls را مقایسه می‌کنم؛ IP راه‌حل عضویت نیست.

web is not attached to this network, or I inspected the wrong network name. I compare docker inspect web with docker network ls; an IP cannot repair missing membership.

۷. سمت چپ و راست -p7. The left and right of -p

nginx روی port داخلی ۸۰ است و مرورگر باید روی host port ۸۰۸۱ وارد شود. نگاشت درست را از بین -p 80:8081 و -p 8081:80 انتخاب کن.

nginx listens on internal port 80 and the browser should enter through host port 8081. Choose between -p 80:8081 and -p 8081:80.

پاسخ و دلیلSolution and reasoning

-p 8081:80 درست است: host سمت چپ، container سمت راست است. گزینهٔ دیگر درخواست را به port داخلی ۸۰۸۱ می‌فرستد، جایی که nginx گوش نمی‌دهد.

-p 8081:80 is correct: host is on the left and container on the right. The other option forwards to internal port 8081, where nginx is not listening.

۸. collision را پیدا کن8. Find the collision

دو container هر دو با -p 127.0.0.1:8080:80 ساخته می‌شوند و دومی fail می‌شود. کدام بخش collision دارد؟

Two containers both use -p 127.0.0.1:8080:80 and the second fails. Which part collides?

پاسخ و دلیلSolution and reasoning

host port ۸۰۸۰ روی همان host/آدرس قبلاً گرفته شده است. container port هر دو می‌تواند ۸۰ باشد؛ host port دوم را مثل ۸۰۸۱ عوض کن یا consumer را از publish داخلی جدا کن.

The host port 8080 on that host/address is already allocated. Both container ports may be 80; change the second host port to 8081 or avoid publishing for internal consumers.

۹. نام عوض شده9. The name changed

client به http://web:80 وصل نمی‌شود، اما inspect نشان می‌دهد container با نام frontend روی شبکه است. تشخیص چیست؟

client cannot reach http://web:80, but inspect shows a container named frontend on the network. What is the diagnosis?

پاسخ و دلیلSolution and reasoning

DNS نام مورد درخواست با نام عضو شبکه یکی نیست. یا URL را به http://frontend:80 تغییر بده یا هنگام connect یک alias مثل web بساز؛ IP را hard-code نکن.

The requested DNS name does not match the member's name. Use http://frontend:80 or connect it with an alias such as web; do not hard-code its IP.

۱۰. دو شبکه متفاوت10. Two different networks

api روی front-net و db روی back-net هستند. client روی front، db را پیدا نمی‌کند. دو راه مفهومی برای اصلاح بنویس.

api is on front-net and db on back-net. A client on front cannot resolve db. Give two conceptual fixes.

پاسخ و دلیلSolution and reasoning

راه معمول این است که db را هم با docker network connect front-net db عضو front کنی، اگر واقعاً باید آنجا دیده شود. راه دیگر طراحی یک gateway/worker با عضویت در هر دو شبکه است؛ publish کردن بی‌دلیل راه اول نیست.

The usual fix is to connect db to front with docker network connect front-net db if it truly belongs there. Another design is a gateway/worker attached to both networks; unnecessary publishing is not the first fix.

۱۱. connect یا ساخت دوباره؟11. Connect or recreate?

یک worker در حال اجراست و باید به back-net وصل شود. کدام دستور را انتخاب می‌کنی؟

A worker is running and must join back-net. Which command do you choose?

پاسخ و دلیلSolution and reasoning

docker network connect back-net worker. این دستور نقطهٔ اتصال تازه می‌سازد و برای user-defined شبکه لازم نیست فقط به‌خاطر attach شدن worker را ساخت دوباره کنی.

docker network connect back-net worker. It adds an endpoint, and a user-defined network attachment does not require recreating the worker.

۱۲. سرویس گوش‌دهنده کجا است؟12. Where is the listener?

containerها روی شبکه مشترک‌اند و نام پیدا می‌شود، اما connection refused داری. service فقط روی 127.0.0.1:9000 bind شده است. چرا؟

The containers share a network and the name resolves, but the connection is refused. The service binds only to 127.0.0.1:9000. Why?

پاسخ و دلیلSolution and reasoning

service فقط روی loopback همان container گوش می‌دهد؛ درخواست از interface شبکه و آدرس container وارد می‌شود، پس سرویس گوش‌دهنده آن را نمی‌بیند. bind آدرس مناسب مثل 0.0.0.0 را با توجه به framework تنظیم کن.

The service listens only on its container-local loopback; the request arrives through the network interface and container address, so the listener cannot see it. Configure a suitable bind address such as 0.0.0.0 for the framework.

۱۳. IP بعد از ساخت دوباره13. IP after recreation

IP web قبل و بعد از ساخت دوباره فرق کرده، اما client با نام web همچنان کار می‌کند. این چه چیزی را ثابت می‌کند؟

web's IP changed after recreation, but client still works with the name web. What does that prove?

پاسخ و دلیلSolution and reasoning

نام قرارداد بهتری از IP برای این stack است؛ DNS شبکهٔ فعلی نام را به نقطهٔ اتصال تازه وصل کرده است. تغییر IP طبیعی است و نباید تنظیمات برنامه را بشکند.

The name is a better contract for the stack; the network DNS maps it to the new endpoint. IP changes are normal and should not break application configuration.

۱۴. internal یا published؟14. Internal or published?

worker روی demo-net برای رسیدن به web از http://localhost:8080 استفاده می‌کند، در حالی که web با -p 127.0.0.1:8080:80 منتشر شده. اصلاح کوچک چیست؟

A worker on demo-net uses http://localhost:8080 to reach web, while web is published with -p 127.0.0.1:8080:80. What is the small fix?

پاسخ و دلیلSolution and reasoning

worker باید http://web:80 را بزند. publish برای مرورگر host است؛ localhost داخل worker به خودش اشاره می‌کند و host نگاشت را دور می‌زند.

The worker should use http://web:80. Publishing is for the host browser; localhost inside worker points to itself and does not mean the host mapping.

۱۵. inspect دوطرفه15. Inspect both sides

برای فهمیدن اینکه db به back-net وصل است، یک دستور از دید شبکه و یک دستور از دید container بنویس.

Write one command from the network view and one from the container view to verify db is attached to back-net.

پاسخ و دلیلSolution and reasoning

docker network inspect back-net فهرست Containers را نشان می‌دهد؛ docker inspect db --format '{{json .NetworkSettings.Networks}}' attachmentهای خود db را نشان می‌دهد. اگر یکی متفاوت بود، دستور یا نام اشتباه است.

docker network inspect back-net shows its Containers list; docker inspect db --format '{{json .NetworkSettings.Networks}}' shows db's attachments. If they disagree, a command or name is wrong.

۱۶. خطا را دسته‌بندی کن16. Classify the failure

فرق تشخیص بین Could not resolve host: web و Connection refused چیست؟

How does the diagnosis differ between Could not resolve host: web and Connection refused?

پاسخ و دلیلSolution and reasoning

اولی یعنی نام به مقصد نرسیده: شبکه مشترک، spelling و alias را ببین. دومی یعنی نام/آدرس پیدا شده اما سرویس گوش‌دهنده، port داخلی یا bind interface درست نیست.

The first means the name did not resolve: check shared network membership, spelling, and aliases. The second means the address was reached but the listener, internal port, or bind interface is wrong.

۱۷. port collision را حل کن17. Fix a port collision

web1 روی host port ۸۰۸۰ است و web2 باید همزمان اجرا شود. دو انتخاب درست پیشنهاد بده.

web1 owns host port 8080 and web2 must run at the same time. Give two correct options.

پاسخ و دلیلSolution and reasoning

اگر host باید web2 را ببیند، host port دیگری مثل 8081:80 انتخاب کن. اگر فقط یک container داخلی آن را می‌خواهد، web2 را بدون -p روی شبکه مشترک اجرا کن و با نام/port داخلی صدا بزن.

If the host must reach web2, choose another host port such as 8081:80. If only an internal container needs it, run web2 without -p on a shared network and use its name/internal port.

۱۸. یک گزارش مسیر بنویس18. Write a path report

سناریو: مرورگر host جواب نمی‌گیرد، client روی demo-net جواب می‌گیرد، و web روی port ۸۰ است. گزارش کوتاهت برای بررسی کجا شروع می‌شود؟

Scenario: the host browser fails, a client on demo-net succeeds, and web listens on port 80. Where does your short diagnosis report start?

پاسخ و دلیلSolution and reasoning

service و شبکه داخلی سالم‌اند؛ اول publish مسیر را می‌بینم: آیا web با -p HOST:80 ساخته شده، HOST_PORT آزاد است، و مرورگر همان host port را می‌زند؟ بعد host IP binding و docker port web را چک می‌کنم.

The service and internal network path work, so I start with publishing: was web created with -p HOST:80, is the host port free, and is the browser using that host port? Then I check host-IP binding and docker port web.

آزمایشگاه کامل: web و client بدون ComposeFull lab: web and client without Compose

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

We do this lab manually so every network line stays visible. By the end, you should be able to explain why client reaches web before publishing, why the host browser does not, and which path disconnect/reconnect removes.

  1. پاک‌سازی اسم‌ها: اگر اجرای قبلی مانده، فقط container/networkهای همین آزمایشگاه یعنی lab-net، lab-web و lab-client را بررسی و پاک کن.Clear names: if an earlier run remains, inspect and remove only this lab's lab-net, lab-web, and lab-client.
  2. شبکه: docker network create --driver bridge lab-net بزن و با docker network ls وجودش را ببین.Network: run docker network create --driver bridge lab-net and confirm it with docker network ls.
  3. web بدون publish: lab-web را با --network lab-net و image nginx:alpine اجرا کن؛ فعلاً -p نده.Web without publishing: run lab-web on lab-net with nginx:alpine; do not add -p yet.
  4. client: client را با curlimages/curl:latest و همان شبکه اجرا کن و http://lab-web:80 را بخوان.Client: run the client with curlimages/curl:latest on the same network and request http://lab-web:80.
  5. host: از host به یک port آزاد مثل ۸۰۸۰ curl بزن؛ باید mappingی برای این آزمایشگاه نداشته باشی. اگر port درگیر بود، عدد دیگری انتخاب کن.Host: curl an unused host port such as 8080; this lab has no mapping yet. If the port is occupied, choose another.
  6. publish: web را remove و با -p 127.0.0.1:8080:80 دوباره بساز. هم host و هم client را دوباره امتحان کن.Publish: remove web and recreate it with -p 127.0.0.1:8080:80. Test both host and client again.
  7. inspect: docker network inspect lab-net، docker inspect lab-web و docker port lab-web را کنار هم بخوان.Inspect: read docker network inspect lab-net, docker inspect lab-web, and docker port lab-web together.
  8. disconnect: یک client دائمی بساز یا همین container را با docker network disconnect lab-net lab-client جدا کن؛ درخواست ناموفق را ببین، سپس با docker network connect lab-net lab-client برگردان و recovery را ثبت کن.Disconnect: use a long-running client or the lab client, run docker network disconnect lab-net lab-client, observe the failed request, then reconnect and record recovery.
  9. localhost عمدی: داخل client به http://localhost:80 بزن و توضیح بده چرا publish host را دور می‌زنی. سپس دستور درست http://lab-web:80 را اجرا کن.Intentional localhost mistake: request http://localhost:80 inside client and explain why it does not mean the host publish. Then run the correct http://lab-web:80.
  10. پاک‌سازی: client و web را remove کن، شبکه را با نام دقیق remove کن و با docker network ls نتیجه را چک کن.Cleanup: remove client and web, remove the network by its exact name, and verify with docker network ls.
lab command path
$ docker network create lab-net
lab-net

$ docker run -d --name lab-web --network lab-net nginx:alpine
...

$ docker run --rm --network lab-net curlimages/curl:latest \
  -fsS http://lab-web:80 | Select-Object -First 1
<!DOCTYPE html>

$ docker network inspect lab-net
...
"Name": "lab-web",
"IPv4Address": "172.21.0.2/16"

$ docker rm -f lab-web
lab-web

$ docker run -d --name lab-web --network lab-net \
  -p 127.0.0.1:8080:80 nginx:alpine
...

PS> curl.exe --fail http://127.0.0.1:8080 | Select-Object -First 1
<!DOCTYPE html>

$ docker run --rm --network lab-net curlimages/curl:latest \
  -fsS http://lab-web:80 | Select-Object -First 1
<!DOCTYPE html>

برای مرحلهٔ disconnect اگر client را با --rm یک‌بار اجرا کنی، بعد از پایان دیگر containerای برای disconnect نداری. یک container ماندگار با image ابزار و دستور خواب بساز یا client را از ابتدا long-running اجرا کن. هدف آزمایشگاه مشاهدهٔ تغییر عضویت است، نه حفظ یک دستور خاص.

For the disconnect step, a one-shot --rm client is gone before you can disconnect it. Use a long-running tool container or start the client as a long-running process. The goal is to observe membership changing, not to memorize one command.

وقتی همه‌چیز را دستی انجام دادی، درد فصل بعد خودش معلوم می‌شود: اگر web، client، پایگاه‌داده و worker داشته باشی، نوشتن این همه نام، شبکه، volume و port با دستورهای جدا خیلی زود خسته‌کننده و خطاپذیر می‌شود. فصل ۸ دقیقاً از همین درد وارد Compose می‌شود.

At the end, this manual setup reveals the next chapter's problem: managing web, client, database, and worker with long, scattered commands creates room for human error. We are not implementing Compose yet; we are only making its problem visible.

جمع‌بندی: مسیر را قبل از port عوض‌کردن ببینWrap-up: see the path before changing a port

اگر فقط یک چیز از این فصل بماند، این باشد: قبل از تغییر port بپرس درخواست از کجا به کجا می‌رود. container دیگر معمولاً از شبکهٔ داخلی و نام سرویس استفاده می‌کند؛ مرورگر روی host از port publish شده. وقتی این دو مسیر را قاطی نکنی، نصف خطاهای Docker networking خیلی ساده‌تر می‌شوند.

You created a network, named and attached containers, resolved a name through internal DNS, published a port for the host, and cleaned everything up. More important than the commands, two questions are now separate: how does another container find web, and how does the host browser enter web? The first needs a shared network and a name; the second needs a host mapping.

مقایسهاز کجا؟با چه مقصدی؟وابستگی اصلی
localhost روی hosthost/browserhost خودشهیچ container دیگری را خودکار پیدا نمی‌کند
localhost داخل containerهمان containerloopback همان namespaceفقط serviceهای همان container
name روی user-defined bridgecontainer عضو networkcontainer دیگر همان networkmembership و DNS داخلی
published porthost یا client خارج از scopeHOST_PORT → CONTAINER_PORTmapping آزاد و bind address درست
container IPdiagnostic موقتendpoint فعلیبا recreate ممکن است تغییر کند

حالا عمداً به جایی رسیده‌ایم که کار دستی آزاردهنده شده: شبکه ساختیم، container نام دادیم، volume وصل کردیم و port publish کردیم. فصل ۸ قرار نیست مفهوم تازه‌ای را جادویی حل کند؛ قرار است همین چیدمان را در یک فایل مرتب و تکرارپذیر بنویسد.

Chapter 8 starts right here. With five services, creating networks, volumes, names, and publishing rules by hand is no longer just practice; you want one declarative file and one command. That chapter builds Compose; this one stays at the Docker CLI level so the paths are understood first.

برگهٔ تقلب شبکهNetworking quick reference

commands worth remembering
docker network ls
docker network create --driver bridge NAME
docker network inspect NAME
docker network connect NAME CONTAINER
docker network disconnect NAME CONTAINER
docker network rm NAME

docker run --network NAME --name web IMAGE
docker run --network NAME curlimages/curl:latest http://web:80
docker run -p 127.0.0.1:8080:80 --name web nginx:alpine
docker port web

# host port 8080 → container port 80
-p 8080:80

برای مطالعهٔ بیشتر، بخش‌های رسمی bridge شبکه driver، networking overview، port publishing و docker شبکه CLI را بخوان. مخصوصاً تفاوت default bridge، user-defined bridge و publish را با نسخهٔ Docker Engine خودت تطبیق بده.

For further reading, use Docker's official bridge network driver, networking overview, port publishing, and docker network CLI documentation. Match the default bridge, user-defined bridge, and publishing details to your Docker Engine version.