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

Docker Compose

Docker Compose

در فصل قبل شبکه را با دست ساختیم و containerها را به هم وصل کردیم. حالا که این دستورها زیاد شده‌اند، چطور راه‌اندازی را طوری نگه داریم که هم‌تیمی بعدی هم همان برنامه را بالا بیاورد؟

In the previous chapter we created a network by hand and connected containers. Now that the commands are adding up, how do we keep the setup so the next teammate can start the same application?

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

دستورهای دستی جواب می‌دهند؛ ولی قرار است همهٔ این‌ها را حفظ کنیم؟The manual setup works—but how many times should we remember it?

فصل قبل برای اینکه فقط دو سرویس با هم حرف بزنند، شبکه ساختیم، volume ساختیم، PostgreSQL را بالا آوردیم، وب را به همان شبکه وصل کردیم و پورت را هم جدا منتشر کردیم. همه‌چیز کار می‌کرد؛ اما حالا فرض کن فردا یکی از هم‌تیمی‌ها بخواهد همین برنامه را روی لپ‌تاپ خودش بالا بیاورد. آیا باید همهٔ این دستورها، اسم‌ها و ترتیبشان را از روی README دنبال کند؟

In Chapter 7 we connected web and a database to a network by hand. A clean run required a network, a volume, a database, a web container, environment values, and a host port. Now imagine a teammate trying to reproduce that setup on another laptop.

the manual setup · representative command sequence
$ docker network create shop-net
shop-net
$ docker volume create shop-db
shop-db
$ docker run -d --name shop-postgres --network shop-net \
  -e POSTGRES_USER=shop -e POSTGRES_PASSWORD=dev-only \
  -e POSTGRES_DB=shop --mount type=volume,src=shop-db,dst=/var/lib/postgresql/data \
  postgres:17
...
$ docker build -t shop-web:dev ./web
...
$ docker run -d --name shop-web --network shop-net \
  -e DB_HOST=shop-postgres -e DB_PORT=5432 \
  -e DB_USER=shop -e DB_PASSWORD=dev-only -e DB_NAME=shop \
  -p 127.0.0.1:8080:3000 shop-web:dev
...
$ docker logs shop-web
$ docker exec -it shop-postgres psql -U shop -d shop
$ docker stop shop-web shop-postgres
$ docker rm shop-web shop-postgres

اینجا مشکل دیگر «Docker بلد نیستیم» نیست. مشکل این است که راه‌اندازی برنامه بین چند دستور پخش شده. یک گزینه جا بیفتد، نام شبکه عوض شود یا پورت اشتباه نوشته شود، نتیجه با سیستم نفر قبلی فرق می‌کند. ما به چیزی نیاز داریم که شکل برنامه را یک‌جا توصیف کند؛ نه اینکه آدم‌ها هر بار همان شکل را از نو بسازند.

A natural question follows: do we really want every developer to type these commands in exactly this order? Names must match; one forgotten flag changes the network or data path; cleanup takes several steps. If the commands live in a README, the next application change means manually keeping several lines in sync.

اینجا Compose وارد داستان می‌شودThis is where Compose enters the story

اینجاست که Compose وارد ماجرا می‌شود. به‌جای اینکه بگوییم «اول این دستور، بعد آن یکی»، می‌نویسیم برنامه چه سرویس‌هایی دارد، چه شبکه و volumeای لازم دارد و کدام پورت باید روی میزبان دیده شود. بعد Compose این توصیف را می‌خواند و از همان Docker Engine می‌خواهد منابع لازم را بسازد.

Instead of maintaining scattered commands, we describe the services and their relationships in one file. A Compose command then asks Docker to create the required resources or reconcile them with the updated file. Let us see the model before building the file step by step.

Manual Docker commands are gathered into a Compose file which Compose sends to Docker Engine to create application resources manual commandsrun · network · volumeflags repeated by people compose.yamlservicesnetworks · volumesconfiguration docker composereads the modelasks Docker Enginecontainers · networkvolume · image describe oncereconcile

فلش اول نشان می‌دهد تنظیمات پراکنده را به یک مدل خوانا تبدیل می‌کنیم. فلش دوم یعنی CLI مربوط به Compose فایل را می‌خواند و از Docker Engine می‌خواهد container، network، volume و در صورت نیاز image را بسازد. خود Compose جای Engine را نمی‌گیرد.

The first arrow means scattered settings become one readable model. The second means the Compose CLI reads that model and asks Docker Engine to create containers, networks, volumes, and images when needed. Compose does not replace the Engine.

Compose موتور تازه‌ای نیست؛ فقط کارهای پراکنده را یک‌جا توصیف می‌کندCompose is not a new container runtime

فایل compose.yaml را مثل نقشهٔ راه‌اندازی برنامه ببین. داخلش می‌گوییم چه سرویس‌هایی داریم، هر سرویس از چه imageای می‌آید یا چطور ساخته می‌شود، چه volume و شبکه‌ای می‌خواهد و چه پورتی باید در دسترس باشد. خود containerها همچنان توسط Docker Engine اجرا می‌شوند؛ Compose فقط هماهنگ‌کنندهٔ این مجموعه است.

A compose.yaml file is a declarative description: it says which services the application contains and which image or build, ports, volumes, networks, and configuration each needs. The docker compose command passes that description to Docker Engine.

نام رایج و ترجیحی فایل compose.yaml است و اگر آن را در پوشهٔ کاری پروژه بگذاری، Compose معمولاً خودش پیدایش می‌کند. فایل نمونه version: ندارد؛ برای Compose Specification فعلی لازم نیست آن کلید قدیمی را به‌عنوان نسخهٔ فرمت اضافه کنی.

The preferred filename is compose.yaml; when it is in the project working directory, Compose normally discovers it automatically. Our example has no version: field; the current Compose Specification does not require that older format-version key.

پس وقتی docker compose up می‌زنی، زیر Docker اتفاق عجیب و تازه‌ای نمی‌افتد. همان container، شبکه و volumeهای فصل‌های قبل ساخته می‌شوند؛ فقط این‌بار تنظیماتشان از یک فایل خوانده می‌شود. این تفاوت وقتی تعداد سرویس‌ها بیشتر شود واقعاً خودش را نشان می‌دهد.

When you run docker compose up, containers still run using the Docker model from the previous chapters. Compose makes the group reproducible and keeps resources aligned with the file.

پرسشdocker runDocker Compose
تنظیم کجا نوشته می‌شود؟در فرمانی که اجرا می‌کنیدر compose.yaml
چند سرویس را چطور می‌آوریم بالا؟با چند فرمان و ترتیب دستییک فرمان روی یک project
network و volume چه می‌شوند؟جداگانه می‌سازی و نام می‌دهیدر مدل تعریف می‌کنی؛ Compose آن‌ها را فراهم می‌کند
کدام‌یک container را اجرا می‌کند؟Docker Engineباز هم Docker Engine؛ Compose درخواست می‌دهد

در درس بعدی سراغ environment و secret می‌رویم. اینجا فقط چند مقدار محلی را به service می‌دهیم تا برنامه به پایگاه‌داده وصل شود؛ بحث فایل .env، اولویت متغیرها و نگهداری secret را باز نمی‌کنیم.

The next chapter covers environment variables and secrets. Here we pass a few local values to the service so it can reach the database; we will not go into .env strategy, variable precedence, or secret storage.

فایل را مرحله‌به‌مرحله می‌سازیمBuild the file one piece at a time

اگر کل YAML را یک‌جا ببینی، شاید فقط فرورفتگی‌ها به چشم بیاید. پس هر بار یک سؤال را جواب می‌دهیم و بعد همان تکه را اضافه می‌کنیم: اول service چیست؟ بعد پایگاه‌داده کجا می‌ماند؟ web چطور پیدایش می‌کند؟

A complete YAML file can make indentation look like the main lesson. We will answer one question at a time and add one piece: what is a service, where does the database live, and how does web find it?

گام اول: فقط یک service آمادهStep one: one service from an image

در پوشه‌ای به نام compose-lab، فایل compose.yaml بساز. service اول را web می‌نامیم و از image آمادهٔ Nginx استفاده می‌کنیم. اسم زیر services اسم همین service است، نه اسم image.

Create a folder named compose-lab and a compose.yaml file inside it. Name the first service web and use the ready-made Nginx image. The key under services is the service name, not the image name.

compose.yaml · stage 1
services:
  web:
    image: nginx:alpine

حالا از همان پوشه docker compose config را اجرا کن. Compose فایل را parse می‌کند و شکل resolvedش را چاپ می‌کند؛ این کار قبل از ساخت container کمک می‌کند فرورفتگی یا کلید اشتباه را پیدا کنی.

Run docker compose config from that folder. Compose parses the file and prints its resolved form; doing this before creating containers helps catch indentation and key mistakes.

گام دوم: پایگاه‌داده را اضافه کنStep two: add the database

یک service دوم با نام postgres اضافه می‌کنیم. برای image رسمی PostgreSQL، رمز آزمایش را از environment می‌دهیم. این مقدار فقط برای محیط محلی این فصل است؛ برای نگهداری امن credential به فصل ۰۹ می‌رسیم.

Add a second service named postgres. The official PostgreSQL image needs a lab password through its environment. This value is for this local lesson only; Chapter 9 covers safer credential handling.

compose.yaml · stage 2
services:
  web:
    image: nginx:alpine
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: lesson
      POSTGRES_PASSWORD: dev-only
      POSTGRES_DB: lesson

در این مرحله فقط هر دو service را تعریف کرده‌ایم. ارتباطشان را هنوز به دستور line اضافه نمی‌کنیم؛ Compose برای این پروژه network می‌سازد و هر دو service را عضو آن می‌کند، مگر اینکه network دیگری تعریف کنیم.

At this point we have only described both services. We have not added a command-line network; Compose creates a project network and attaches both services to it unless we define a different network design.

گام سوم: serviceها با نام یکدیگر را پیدا می‌کنندStep three: services find each other by name

در فصل ۰۷ دستی ساختیم که web به نام پایگاه‌داده روی network مشترک وصل شود. Compose همان مدل را فراهم می‌کند: serviceها روی پروژه network هستند و اسم service یک نام میزبان در همان network است. برای app آدرس پایگاه‌داده برابر postgres:5432 می‌شود؛ localhost به خود web اشاره می‌کند.

In Chapter 7 we manually arranged for web to reach the database by name on a shared network. Compose provides the same model: services join the project network, and a service name is a hostname on that network. The app uses postgres:5432; localhost would point back to web.

The web service resolves postgres through the Compose project network and sends a database request to port 5432 web serviceDB_HOST=postgrescaller project networkservice DNSpostgres → endpointshared membership postgres servicePostgreSQL :5432database listener resolve namequery :5432

فلش اول مرحلهٔ name resolution را نشان می‌دهد: postgres در پروژه network به endpoint service تبدیل می‌شود. فلش دوم درخواست دیتابیس به port داخلی ۵۴۳۲ است. این مسیر داخل Docker است و به publish کردن پایگاه‌داده port نیاز ندارد.

The first arrow is name resolution: postgres becomes the service endpoint on the project network. The second is the database request to internal port 5432. This path stays inside Docker and does not need the database port published.

گام چهارم: فقط web را به میزبان نشان بدهStep four: publish only web to the host

مرورگر روی میزبان عضو پروژه network نیست. در این مرحله Nginx هنوز port داخلی ۸۰ دارد، پس میزبان port ۸۰۸۰ را به همان ۸۰ وصل می‌کنیم. پایگاه‌داده را publish نمی‌کنیم، چون serviceهای داخلی روی network مشترک‌اند.

The host browser is not a member of the project network. At this stage Nginx still listens on internal port 80, so map host port 8080 to 80. We do not publish the database because internal services share a network.

ports: host entry → web listener
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: lesson
      POSTGRES_PASSWORD: dev-only
      POSTGRES_DB: lesson

این مرحله با image فعلی معتبر است: Nginx روی ۸۰ گوش می‌دهد و میزبان از ۸۰۸۰ وارد می‌شود. بعداً که web را به برنامهٔ Node تغییر بدهیم، فقط سمت container را به ۳۰۰۰ عوض می‌کنیم.

This stage is valid with the current image: Nginx listens on 80 and the host enters on 8080. When we replace web with the Node app later, only the container side changes to 3000.

گام پنجم: پایگاه‌داده باید داده‌اش را نگه داردStep five: the database must keep its data

اگر data پوشه داخل writable layer بماند، با حذف container از بین می‌رود. از فصل ۰۶ می‌دانیم که پایگاه‌داده به named volume نیاز دارد. volume را در بخش بالای فایل نام می‌بریم و در service به مسیر درست وصل می‌کنیم.

If the data directory stays in the writable layer, removing the container destroys it. Chapter 6 taught us that the database needs a named volume. Declare the volume at the top level and mount it at the correct path in the service.

compose.yaml · stage 5
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: lesson
      POSTGRES_PASSWORD: dev-only
      POSTGRES_DB: lesson
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  pg-data:

کلید pg-data در سطح بالا می‌گوید Compose یک named volume برای این پروژه مدیریت کند. سطر زیر postgres.volumes می‌گوید volume داخل container کجا دیده شود. YAML خود فایل‌های پایگاه‌داده را نگه نمی‌دارد؛ فقط محل اتصال را تعریف می‌کند.

The top-level pg-data key asks Compose to manage a named volume for this project. The entry under postgres.volumes says where that volume appears inside the container. The YAML does not contain database files; it describes the mount.

گام ششم: تنظیمات لازم برای اتصالStep six: the configuration needed to connect

app باید نام میزبان، port و نام کاربری پایگاه‌داده را بداند. در این فصل فقط نشان می‌دهیم service چه environmentای دریافت می‌کند. مقدار dev-only رمز آزمایشی و محلی است؛ آن را برای سرویس واقعی کپی نکن.

The app needs the database hostname, port, and user name. Here we only show which environment the service receives. dev-only is a local lab password; do not copy it into a real service.

environment is service configuration
  web:
    image: shop-web:dev
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_USER: lesson
      DB_PASSWORD: dev-only
      DB_NAME: lesson

اگر DB_HOST را جا بیندازی یا به‌اشتباه localhost بگذاری، Compose نمی‌تواند قصد برنامه را حدس بزند. در ادامه از docker compose config برای دیدن تنظیمات نهایی و از log برای فهمیدن رفتار برنامه استفاده می‌کنیم.

If you omit DB_HOST or set it to localhost by mistake, Compose cannot infer what the app meant. We will use docker compose config to inspect the resolved configuration and logs to understand the app's behavior.

گام هفتم: به‌جای image آماده، image خودمان را build کنStep seven: build our own image instead of using a ready-made one

service web قرار است کد کوچک خودمان را اجرا کند. پس در پوشهٔ web سه فایل می‌سازیم. برنامه فقط به پایگاه‌داده query می‌زند و جواب ساده می‌دهد؛ منطق اصلی درس این نیست، اما نتیجهٔ واقعی network را قابل دیدن می‌کند.

The web service will run our own small program. Create three files in a web folder. The program makes a database query and returns a simple response; application logic is not the lesson, but it makes the network result visible.

web/package.json
{
  "name": "compose-web",
  "private": true,
  "version": "1.0.0",
  "scripts": { "start": "node server.js" },
  "dependencies": { "pg": "^8.13.0" }
}
web/server.js
const http = require("node:http");
const { Pool } = require("pg");

const db = new Pool({
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT || 5432),
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
});

http.createServer(async (_req, res) => {
  try {
    const result = await db.query(
      "SELECT current_database() AS name, current_timestamp AS time"
    );
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify(result.rows[0]));
  } catch (error) {
    console.error("database request failed:", error.message);
    res.writeHead(503, { "content-type": "text/plain" });
    res.end("database is not ready yet");
  }
}).listen(3000, "0.0.0.0", () => {
  console.log("web listening on 0.0.0.0:3000");
});

اگر پایگاه‌داده هنوز query را نمی‌پذیرد، برنامه با ۵۰۳ جواب می‌دهد اما از کار نمی‌افتد؛ درخواست بعدی دوباره تلاش می‌کند. این تصمیم کوچک کمک می‌کند startup order را از readiness جدا ببینیم. آدرس 0.0.0.0 باعث می‌شود server روی interface شبکهٔ container هم گوش بدهد، نه فقط loopback خودش.

If the database is not accepting queries yet, the app returns 503 but stays alive; the next request tries again. This small choice helps us distinguish startup order from readiness. Binding to 0.0.0.0 lets the server listen on the container network interface, not only its own loopback.

web/Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY server.js ./
EXPOSE 3000
CMD ["npm", "start"]

حالا image: را با build: جایگزین می‌کنیم. build: ./web می‌گوید context و Dockerfile در آن پوشه‌اند؛ Compose از آن‌ها image می‌سازد و بعد container مربوط به service را اجرا می‌کند.

Now replace image: with build:. build: ./web says the context and Dockerfile are in that folder; Compose builds an image from them and then runs the service container.

Compose build context and Dockerfile produce an image which Compose runs as the web service container web/ sourceDockerfile + server.jsbuild context Compose buildreads build: ./webcreates imageproject-web web service containerruns imagelistens on :3000 buildcreate / start

فلش اول جریان build را نشان می‌دهد: کد منبع و Dockerfile ورودی ساخت image هستند. فلش دوم یعنی Compose از image ساخته‌شده برای ساخت و اجرای container service استفاده می‌کند؛ تغییر کد منبع به‌خودی‌خود container فعلی را جادویی عوض نمی‌کند.

The first arrow is the build flow: source and Dockerfile are inputs to image creation. The second means Compose uses that image to create and run the service container; changing source does not magically alter an existing container.

فایل نهایی: دو service، یک network و یک volumeThe final file: two services, one network, and one volume

حالا تکه‌ها را کنار هم می‌گذاریم. اسم پروژه را برای اینکه نام منابع lab قابل پیش‌بینی باشد صریح می‌کنیم. Compose اگر network دیگری تعریف نکرده باشیم، یک پروژه network پیش‌فرض می‌سازد؛ volume هم با پروژه name namespace می‌شود.

Now put the pieces together. We set a project name so the lab's resource names are predictable. Unless another network is declared, Compose creates a default project network; the volume is also namespaced by the project name.

compose.yaml · complete working version
name: compose-lab

services:
  web:
    build: ./web
    ports:
      - "127.0.0.1:8080:3000"
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_USER: lesson
      DB_PASSWORD: dev-only
      DB_NAME: lesson
    depends_on:
      - postgres

  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: lesson
      POSTGRES_PASSWORD: dev-only
      POSTGRES_DB: lesson
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  pg-data:

به یک ریزه‌کاری YAML دقت کن: services و volumes از ریشه شروع می‌شوند؛ web و postgres زیر services هستند؛ و pg-data در دو جای متفاوت دو نقش دارد: بالا تعریف منبع، پایین اتصال آن به مسیر container.

Notice the YAML shape: services and volumes start at the root; web and postgres sit under services. The two appearances of pg-data have different roles: declaring the resource at the top level and mounting it into the container.

برای دیدن نتیجهٔ interpolation و defaultها، اول docker compose config بزن؛ بعد docker compose up --build را اجرا کن. خروجی تنظیمات آن چیزی است که Compose فهمیده، نه صرفاً همان فاصله‌گذاری فایل ورودی.

Run docker compose config to inspect interpolation and defaults, then use docker compose up --build. The config output is Compose's resolved model, not just a reprint of the source indentation.

config and first start · representative output
$ docker compose config
name: compose-lab
services:
  postgres:
    environment:
      POSTGRES_DB: lesson
      POSTGRES_PASSWORD: dev-only
      POSTGRES_USER: lesson
    image: postgres:17
  web:
    build:
      context: .../compose-lab/web
    depends_on:
      - postgres
    ports:
      - mode: ingress
        target: 3000
        published: "8080"
...

$ docker compose up --build
[+] Building ...
[+] Running 4/4
 ✔ Network compose-lab_default       Created
 ✔ Volume compose-lab_pg-data        Created
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started
web-1      | web listening on 0.0.0.0:3000

اسم واقعی resourceها ممکن است با نسخه و پروژه پوشه تو فرق کند؛ نکتهٔ مهم این است که Compose آن‌ها را زیر یک پروژه گروه می‌کند. در خروجی بالا network و volume ساخته شده‌اند و هر دو service container بالا آمده‌اند. «Started» هنوز به معنی آماده‌بودن پایگاه‌داده برای query نیست.

Exact resource names can differ with your version and project directory; the key point is that Compose groups them under one project. The output shows a network and volume created and both service containers started. “Started” still does not mean the database is ready for queries.

چرخهٔ روزمره: بالا بیاور، وضعیت را ببین، لاگ بخوان، داخل سرویس بروThe everyday Compose loop

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

Compose removes several tasks from “what was that container called again?” Instead of generated container names, you work with service names. Let us connect each command to the job it answers.

start and inspect services
$ docker compose up -d
[+] Running 2/2
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started

$ docker compose ps
NAME                    IMAGE               SERVICE    STATUS          PORTS
compose-lab-postgres-1   postgres:17         postgres   Up 8 seconds    5432/tcp
compose-lab-web-1        compose-lab-web     web        Up 8 seconds    127.0.0.1:8080->3000/tcp

$ curl.exe --fail http://127.0.0.1:8080
{"name":"lesson","time":"2026-09-24T..."}

up -d در پس‌زمینه اجرا می‌کند تا prompt آزاد بماند. ps می‌گوید هر service چه containerای دارد، status چیست و کدام میزبان port منتشر شده. درخواست HTTP جواب می‌دهد و نشان می‌دهد web توانسته از داخل network به PostgreSQL query بزند.

up -d runs in the background and returns the prompt. ps shows each service's container, status, and published host port. The HTTP response demonstrates that web queried PostgreSQL over the internal network.

اگر docker compose up را بدون -d بزنی، خروجی serviceها در همان terminal می‌آید و دستور به آن‌ها attach می‌ماند. برای دنبال‌کردن چند log هنگام یادگیری خوب است؛ Ctrl+C در حالت attached باعث توقف containerها می‌شود. برای کار روزمره معمولاً -d می‌خواهی.

Without -d, docker compose up streams service output in the terminal and stays attached. That is useful while learning; Ctrl+C in attached mode stops the containers. For routine work, detached mode is often more convenient.

follow one service, then inspect it
$ docker compose logs --tail=10
postgres-1 | database system is ready to accept connections
web-1      | web listening on 0.0.0.0:3000
web-1      | database request failed: connect ECONNREFUSED ...

$ docker compose logs -f web
web-1      | web listening on 0.0.0.0:3000
web-1      | database request failed: connect ECONNREFUSED ...

$ docker compose exec postgres psql -U lesson -d lesson
lesson=#

بدون نام service، logs خروجی همه را کنار هم می‌آورد؛ با logs -f web زنده خروجی web را دنبال می‌کنی و با Ctrl+C فقط دنبال‌کردن را تمام می‌کنی. exec یک دستور را داخل container در حال اجرا اجرا می‌کند؛ اینجا وارد psql شده‌ایم، نه یک container تازه.

Without a service name, logs combines output from all services. logs -f web follows web live; Ctrl+C stops following, not the service. exec runs a command inside the already-running container; here we entered psql, not a new container.

فرمانچه چیزی را تغییر می‌دهد؟چه زمانی مناسب است؟
upمی‌سازد، در صورت نیاز recreate می‌کند و start می‌کنداولین اجرا یا هماهنگ‌کردن با فایل تازه
startcontainerهای موجود و متوقف‌شده را start می‌کندبعد از stop، وقتی config عوض نشده
stopprocessها را متوقف می‌کند، containerها را نگه می‌داردوقفهٔ موقت که state موجود بماند
restartcontainerهای موجود را دوباره راه می‌اندازدراه‌اندازی مجدد process با همان config
downcontainerها و networkهای project را حذف می‌کندجمع‌کردن stack با نگه‌داشتن named volume
down --volumesهمهٔ بالا به‌علاوهٔ volumeهای projectفقط وقتی حذف داده عمدی است
pause, resume, and restart existing services
$ docker compose stop web
[+] Stopping 1/1
 ✔ Container compose-lab-web-1 Stopped

$ docker compose start web
[+] Running 1/1
 ✔ Container compose-lab-web-1 Started

$ docker compose restart web
[+] Restarting 1/1
 ✔ Container compose-lab-web-1 Started

$ docker compose down
[+] Running 3/3
 ✔ Container compose-lab-web-1      Removed
 ✔ Container compose-lab-postgres-1 Removed
 ✔ Network compose-lab_default      Removed

در این خروجی stop container را نگه می‌دارد؛ start همان container را برمی‌گرداند؛ restart فرایند موجود را دوباره اجرا می‌کند؛ down containerها و network پروژه را جمع می‌کند. هیچ‌کدام از این سه فرمان اول تنظیمات جدیدی را از YAML اعمال نمی‌کنند.

stop keeps the containers; start starts those same containers; restart runs their existing processes again; down removes project containers and network. None of the first three commands applies a new YAML configuration.

نکتهٔ کلیدی این است که start و restart فایل تازه را اعمال نمی‌کنند. اگر ports یا environment را عوض کردی، از up استفاده کن تا Compose وضعیت موجود را با مدل جدید مقایسه کند.

The key distinction is that start and restart do not apply a changed file. If you change ports or environment, use up so Compose can reconcile the existing state with the new model.

down برنامه را جمع می‌کند؛ داده را فقط وقتی خواستی پاک کنdown keeps data; the volumes flag can remove it

اینجا فصل ۶ دوباره مهم می‌شود: عمر container با عمر volume یکی نیست. docker compose down containerها و شبکهٔ پروژه را جمع می‌کند، اما named volume را نگه می‌دارد. این رفتار خوب است؛ پایین‌آوردن برنامه نباید مساوی پاک‌شدن پایگاه‌داده باشد.

Chapter 6 separated a volume from its container. Compose preserves that separation: docker compose down removes project containers and networks, but keeps the declared named volume. A later up can mount the same database data again.

Compose down removes containers and network but preserves the named volume, while down with volumes removes the data volume too running projectweb containerpostgres containerproject networknamed pg-datadatabase bytes docker compose downremove containers + networkkeep named volume down --volumesremove containers + networkremove named volume too after downdata can return on up after --volumesfresh empty database

اما down --volumes داستان دیگری است. این گزینه volumeهای پروژه را هم حذف می‌کند. برای دادهٔ آزمایشی مفید است؛ برای داده‌ای که می‌خواهی نگه داری، می‌تواند همان دستور اشتباهی باشد که بعداً بابتش پشیمان می‌شوی. قبل از زدنش بپرس: «این volume واقعاً دورریختنی است؟»

The project splits into two teardown paths. The green arrow shows down removing containers and network while keeping the named volume. The warning path shows down --volumes also removing the volume and its data, so a later up creates an empty database.

survival experiment · disposable data only
$ docker compose exec -T postgres psql -U lesson -d lesson \
  -c "CREATE TABLE IF NOT EXISTS memo (body text); INSERT INTO memo VALUES ('still here');"
CREATE TABLE
INSERT 0 1

$ docker compose down
[+] Running 3/3
 ✔ Container compose-lab-web-1      Removed
 ✔ Container compose-lab-postgres-1 Removed
 ✔ Network compose-lab_default      Removed

$ docker compose up -d
[+] Running 3/3
 ✔ Network compose-lab_default       Created
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started

$ docker compose exec -T postgres psql -U lesson -d lesson \
  -c "SELECT body FROM memo;"
    body
------------
 still here
(1 row)

ردیف بعد از down برگشته، چون named volume باقی مانده و PostgreSQL همان را mount کرده است. حالا اگر برای نمایش خطر روی دادهٔ آزمایشی docker compose down --volumes بزنی، volume حذف می‌شود؛ اجرای بعدی یک پایگاه‌داده تازه می‌سازد و ردیف قبلی دیگر نیست. این را روی دادهٔ واقعی اجرا نکن.

The row returned after down because the named volume remained and PostgreSQL mounted it again. If you run docker compose down --volumes on disposable lab data, the volume is removed; the next run creates a fresh database and the old row is gone. Never use this demonstration on data you need.

عملcontainernetwork projectnamed volume
stopمی‌ماند، متوقفمی‌ماندمی‌ماند
downحذف می‌شودحذف می‌شودمی‌ماند
down --volumesحذف می‌شودحذف می‌شودحذف می‌شود

چه زمانی build کنیم و چه زمانی container را بسازیم؟When should we build, and when should we recreate?

کد منبع یا Dockerfile را تغییر داده‌ای؟ image باید دوباره build شود. docker compose build web فقط image را می‌سازد؛ docker compose up --build اول image لازم را build می‌کند و بعد stack را بالا می‌آورد یا با تغییرها هماهنگ می‌کند.

Did you change source or the Dockerfile? The image needs a rebuild. docker compose build web only builds the image; docker compose up --build builds what is needed and then starts or reconciles the stack.

change source, build, reconcile
$ docker compose build web
[+] Building 4.2s (10/10) FINISHED
 => exporting to image
 => naming to docker.io/library/compose-lab-web

$ docker compose up -d
[+] Running 2/2
 ✔ Container compose-lab-postgres-1 Running
 ✔ Container compose-lab-web-1      Recreated

# Or combine both decisions:
$ docker compose up -d --build

وقتی service تنظیمات یا image عوض شده باشد، up container قدیمی را متوقف و container تازه می‌سازد؛ volumeهای mount‌شده حفظ می‌شوند. فایل جدید با start یا restart اعمال نمی‌شود، چون آن‌ها فقط container موجود را دوباره راه می‌اندازند.

When a service configuration or image changes, up stops the old container and creates a replacement; mounted volumes are preserved. start and restart do not apply a new file because they only run existing containers again.

فرمان/کلیدمعنیاثر بعد از تغییر source/config
image: postgres:17از image آماده استفاده کنCompose pull می‌کند اگر لازم باشد؛ Dockerfile این service را build نمی‌کند
build: ./webاز context و Dockerfile image بسازبرای source/Dockerfile تغییرکرده image rebuild می‌شود
docker compose buildimageها را build کنcontainerها به‌تنهایی لزوماً تعویض نمی‌شوند
docker compose up --buildbuild لازم، سپس create/start یا reconcilecontainer جدید با image/config تازه ساخته می‌شود

depends_on می‌گوید چه چیزی زودتر شروع شود؛ نه اینکه چه چیزی آماده استdepends_on orders startup; it does not guarantee readiness

فرض کن وب خیلی سریع بالا می‌آید و همان لحظه اولین اتصال را به PostgreSQL می‌زند. Compose ممکن است PostgreSQL را زودتر start کرده باشد، اما start شدن فرایند با آماده‌بودن پایگاه‌داده برای پرس‌وجو یکی نیست. پس depends_on را «صبر کن تا دیتابیس آماده شود» ترجمه نکن؛ فعلاً فقط ترتیب شروع را از آن بگیر.

When web depends on postgres, depends_on tells Compose to respect the dependency order while creating and starting services. That helps, but the database process can be running before initialization has finished.

a timing window is not a Compose bug
$ docker compose up -d
[+] Running 2/2
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started

$ curl.exe -i http://127.0.0.1:8080
HTTP/1.1 503 Service Unavailable
database is not ready yet

$ docker compose logs postgres
postgres-1 | database system is ready to accept connections

$ curl.exe --fail http://127.0.0.1:8080
{"name":"lesson","time":"..."}

در نمونهٔ ما web زنده ماند و درخواست بعدی موفق شد. اگر برنامه در اولین اتصال exit کند، docker compose ps -a و docker compose logs web نشان می‌دهند کدام service خارج شده و چرا. برنامهٔ واقعی باید retry یا رفتار مناسبی برای اتصال داشته باشد؛ depends_on به‌تنهایی readiness check نیست.

In this example web stayed alive and the next request succeeded. If an app exits on its first failed connection, docker compose ps -a and docker compose logs web show which service exited and why. A real app needs suitable retry behavior; depends_on alone is not a readiness check.

در Compose شرط‌های پیشرفته‌تری هم وجود دارد، اما این فصل وارد healthcheck نمی‌شود؛ آن موضوع فصل ۱۰ است. فعلاً جملهٔ دقیق را نگه دار: short syntax ترتیب startup را تنظیم می‌کند، نه آماده‌بودن پایگاه‌داده برای query.

Compose has more advanced dependency conditions, but this chapter does not cover healthchecks; that belongs in Chapter 10. Keep the precise rule for now: short-form depends_on orders startup, not database readiness for queries.

اسم پروژه، مرز نام‌گذاری منابعThe project name groups resource names

Compose یک پروژه را بر اساس پوشهٔ فایل می‌شناسد و معمولاً نام پروژه را به نام resourceها اضافه می‌کند. در مثال ما compose-lab پیشوند network و volume است؛ serviceهای web و postgres هم داخل همان پروژه پیدا می‌شوند.

Compose identifies a project from the directory containing the file and usually prefixes resource names with the project name. In our example, compose-lab prefixes the network and volume; services web and postgres are discovered within that project.

دو نسخه از یک پروژه را می‌توان با نام پروژه متفاوت کنار هم اجرا کرد؛ همین کار جلوی برخورد منابع را می‌گیرد. اما نام میزبان داخلی همچنان اسم service است، نه اسم container تولیدشده یا پروژه.

You can run two copies of a project side by side with different project names to avoid resource collisions. The internal hostname is still the service name, not the generated container or project name.

same file, separate project namespace
$ docker compose -p feature-a up -d
$ docker compose -p feature-b up -d

$ docker compose -p feature-a ps
NAME                       SERVICE
feature-a-postgres-1       postgres
feature-a-web-1            web

$ docker compose -p feature-b ps
NAME                       SERVICE
feature-b-postgres-1       postgres
feature-b-web-1            web

اگر از -p استفاده نکنی، نام پوشه معمولاً پروژه name می‌شود؛ مقدار name: یا environment/CLI هم می‌تواند آن را عوض کند. برای شروع کافی است بدانی هم‌نام بودن service در دو پروژه به معنی یکی بودن containerها نیست.

Without -p, the directory name normally becomes the project name; a name: value or CLI/environment setting can override it. For now, remember that same-named services in two projects are not the same containers.

وقتی مجموعه بالا نمی‌آید، اول ببین کدام سرویس و کدام مرحله مشکل داردWhen the stack does not start, where do we begin?

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

“Compose is broken” is not a diagnosis. First find out whether Compose understood the file; then see which service was created or exited; then read that service's logs. Finally compare the hostname, port, and volume with the actual request path.

failure one · a service is at the wrong YAML level
services:
  web:
    image: nginx:alpine
  postgres:
  image: postgres:17

$ docker compose config
validating ...: services.postgres must be a mapping

نگاه اول ممکن است بگوید postgres تعریف شده؛ اما indentation آن را از زیر services بیرون برده و shape مورد انتظار را شکسته است. قبل از اجرای stack، config بزن و ساختار را درست کن. این تشخیص از ساختن نصف منابع و بعد دنبال‌کردن خطای نام بهتر است.

At a glance, postgres looks declared, but its indentation moved it out of services and broke the expected shape. Run config before starting the stack and fix the structure. That is better than creating half the resources and then chasing a hostname error.

failure two · the required database environment is missing
$ docker compose ps -a
NAME                       SERVICE    STATUS
compose-lab-postgres-1     postgres   Exited (1)
compose-lab-web-1          web        Up

$ docker compose logs postgres
postgres-1 | Error: Database is uninitialized and superuser password is not specified.
postgres-1 | You must specify POSTGRES_PASSWORD to a non-empty value.

اینجا Compose container را ساخته، اما entrypoint خود image به‌خاطر نبودن مقدار لازم خارج شده است. network را عوض نکن؛ environment نهایی را با docker compose config ببین و مقدار لازم آزمایش را برگردان. credential این مثال محلی است و در production باید روش دیگری انتخاب شود.

Compose created the container, but the image entrypoint exited because a required value was missing. Do not change the network; inspect the resolved environment with docker compose config and restore the lab value. This local credential is not a production pattern.

نشانهمرز محتملبررسی بعدی
YAML parse error یا key ناپدیدindentation یا ساختار YAMLdocker compose config و مقایسهٔ سطح کلیدها
web می‌گوید host را پیدا نکردservice name یا network scopeDB_HOST، service key و docker compose config
browser به پورت وصل نمی‌شودpublish یا listener portdocker compose ps، نگاشت HOST:CONTAINER و log web
Postgres فوراً خارج می‌شودenvironment لازم ناقص یا data init ناموفقdocker compose ps -a و docker compose logs postgres
service بالا آمده اما query 503 استdatabase هنوز آماده نیست یا تنظیم DB غلط استlogهای هر دو service؛ readiness را با startup order یکی نگیر
بعد از down داده برگشترفتار درست named volumeنام volume و mount path را inspect کن
داده ناگهان خالی شدdown --volumes یا project name دیگرdocker volume ls و project name را بررسی کن
container قبلی هنوز اجرا می‌شودفایل تازه با restart اعمال نشدهdocker compose up یا up --build
a small diagnosis sequence
$ docker compose config
services:
  postgres: ...
  web: ...

$ docker compose ps -a
NAME                    SERVICE    STATUS
compose-lab-postgres-1  postgres   Up
compose-lab-web-1       web        Exited (1)

$ docker compose logs --tail=20 web
web-1 | database request failed: getaddrinfo ENOTFOUND local-db

$ docker compose exec postgres psql -U lesson -d lesson -c 'SELECT 1;'
 ?column?
----------
        1
(1 row)

در این مثال پایگاه‌داده زنده است و query مستقیم داخلش جواب می‌دهد؛ خط log می‌گوید web دنبال hostnameای به نام local-db گشته که در Compose تعریف نشده. پس network و PostgreSQL را از نو نمی‌سازیم؛ مقدار DB_HOST را با service name یعنی postgres هماهنگ می‌کنیم و با up دوباره اعمالش می‌کنیم.

Here the database is running and a direct query succeeds; the log says web looked for local-db, which is not a Compose service. We do not rebuild the network or PostgreSQL. We align DB_HOST with the service name postgres and apply it again with up.

اگر یک پروژه دیگر هم serviceای به نام postgres داشته باشد، اسم‌ها با هم برخورد نمی‌کنند؛ اما web این پروژه هم به آن پایگاه‌داده وصل نمی‌شود. service name در scope شبکه معنا دارد. برای ارتباط بین دو پروژه باید network مشترک را آگاهانه طراحی کنی، نه اینکه از global بودن name فرض بگیری.

A second project can also have a service named postgres without a naming collision, but this project's web will not reach that other database. A service name is scoped to its network. Cross-project communication requires an intentionally shared network, not an assumption that names are global.

برای دیدن attachmentهای Engine-level، وقتی خروجی Compose کافی نیست از docker inspect CONTAINER استفاده کن. اما شروع عیب‌یابی معمولاً با سه فرمان Compose روشن‌تر است: config، ps، logs.

Use docker inspect CONTAINER when Compose output is not enough to understand Engine-level attachments. But most investigations start more clearly with three Compose commands: config, ps, and logs.

تمرین‌ها: از روی مسئله تصمیم بگیر، نه از روی حفظ‌کردن دستورExercises: turn a command setup into a model

قبل از بازکردن پاسخ هر تمرین، یک جمله برای خودت بگو: «الان دقیقاً چه چیزی را می‌خواهم ثابت کنم؟» بعضی سؤال‌ها دربارهٔ شبکه‌اند، بعضی دربارهٔ volume و بعضی فقط دربارهٔ تفاوت up و start. اگر دلیل انتخابت را بتوانی توضیح بدهی، مفهوم را گرفته‌ای.

For each exercise, first say what you expect to observe. Then open the solution; the goal is not memorizing YAML but predicting resources and their lifetimes from the model.

۱. دستور را به service ترجمه کن1. Translate a command into a service

docker run -d --name cache redis:7 را به بخش Compose تبدیل کن. کدام قسمت‌ها identity اجرایی‌اند و کدام فقط اسم انتخابی ماست؟

Convert docker run -d --name cache redis:7 into a Compose service. Which parts describe the runtime and which are only our chosen name?

یک جواب ممکنOne possible answer

services: cache: image: redis:7 هستهٔ تبدیل است؛ در YAML هر سطح را با indentation می‌سازی. -d مربوط به نحوهٔ اجرای Compose در همان نوبت است، نه صفتی که باید زیر service کپی شود؛ برای اجرای پس‌زمینه docker compose up -d می‌زنی.

The core translation is services: cache: image: redis:7, with indentation defining each level. -d controls this Compose invocation rather than a service property; use docker compose up -d for detached execution.

۲. image یا build؟2. Image or build?

برای PostgreSQL رسمی و web کد منبع خودت کدام service باید image داشته باشد و کدام build؟

Which service should use image for official PostgreSQL and which should use build for your own web source?

چرا این انتخاب؟Why this choice?

PostgreSQL از image آماده مثل postgres:17 می‌آید؛ web از پوشهٔ Dockerfile خودش مثل build: ./web ساخته می‌شود. build آدرس منبع ساخت است، نه نام container.

PostgreSQL comes from a ready image such as postgres:17; web is built from its Dockerfile directory, such as build: ./web. build identifies build input, not a container name.

۳. port داخلی را پیدا کن3. Find the internal port

web داخل container روی ۳۰۰۰ گوش می‌دهد و مرورگر میزبان باید از ۸۰۸۸ وارد شود. سطر ports را بنویس.

web listens on 3000 inside the container and the host browser should use 8088. Write the ports entry.

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

ports: - "127.0.0.1:8088:3000" مسیر میزبان-local را محدود می‌کند؛ سمت چپ میزبان port و سمت راست container port است. اگر میزبان IP را حذف کنی، bind می‌تواند روی interfaceهای بیشتری باشد.

ports: - "127.0.0.1:8088:3000" limits access to the host loopback; left is the host port and right is the container port. Omitting the host IP can bind on more host interfaces.

۴. پایگاه‌داده را کجا mount کنی؟4. Where should database data mount?

برای PostgreSQL 17 volume نام‌دار تعریف کن و آن را در data پوشه مورد استفادهٔ این image وصل کن.

Declare a named volume for PostgreSQL 17 and mount it at the data directory used by this image.

نمونهٔ ساختارA structural answer

در service: volumes: - pg-data:/var/lib/postgresql/data؛ در ریشهٔ فایل: volumes: pg-data:. تعریف resource و محل اتصال دو بخش از یک قراردادند.

In the service: volumes: - pg-data:/var/lib/postgresql/data; at the file root: volumes: pg-data:. Resource declaration and mount target are two parts of one contract.

۵. نام میزبان از کجا می‌آید؟5. Where does the hostname come from?

اسم service دیتابیس postgres است. web برای وصل‌شدن باید DB_HOST را روی چه چیزی بگذارد؟

The database service is named postgres. What should web use for DB_HOST?

پاسخ کوتاه، دلیل مهمShort answer, important reason

postgres. Compose service name را در پروژه network resolve می‌کند؛ container IP یا نام تولیدشده مثل compose-lab-postgres-1 را به تنظیمات برنامه نده.

postgres. Compose resolves the service name on the project network; do not configure the app with a container IP or generated name such as compose-lab-postgres-1.

۶. این localhost مال کی است؟6. Whose localhost is this?

web در اتصال پایگاه‌داده از localhost:5432 استفاده می‌کند و اتصال رد می‌شود. توضیح بده درخواست کجا رفته و نام میزبان درست چیست.

web uses localhost:5432 for the database and the connection is refused. Explain where the request went and the correct hostname.

ردیابی درخواستTrace the request

localhost به خود web اشاره کرده، پس برنامه دنبال PostgreSQL داخل container خودش گشته است. service مقصد postgres است و آدرس داخلی postgres:5432 خواهد بود.

localhost pointed back to web, so the app looked for PostgreSQL inside its own container. The destination service is postgres, giving the internal address postgres:5432.

۷. YAML یک سطح جابه‌جا7. One YAML level is misplaced

در فایل، postgres: هم‌سطح services: نوشته شده است. قبل از اجرا چه کار می‌کنی و چرا؟

In the file, postgres: is aligned with services:. What do you do before starting, and why?

یک گام تشخیصی کم‌هزینهA cheap diagnostic step

docker compose config را اجرا می‌کنم. اگر سرویس به‌عنوان کلید زیر services parse نشده باشد، از خروجی یا خطای validation معلوم می‌شود؛ ابتدا nesting را درست می‌کنم، بعد منابع می‌سازم.

I run docker compose config. If the service was not parsed under services, the output or validation error exposes it. I fix the nesting before creating resources.

۸. بخشی از compose تنظیمات را بخوان8. Read a compose config excerpt

در خروجی نهایی، published: "8080" و target: 3000 می‌بینی. کدام port را میزبان مرورگر می‌زند؟

The resolved output shows published: "8080" and target: 3000. Which port does the host browser use?

تفسیرInterpretation

میزبان port همان published یعنی ۸۰۸۰ است؛ target port داخل container و برابر ۳۰۰۰ است. تنظیمات خروجی‌شده راه خوبی است تا جهت mapping را پیش از اجرا بخوانی.

The host uses published port 8080; target 3000 is inside the container. Resolved config is a useful way to read mapping direction before starting.

۹. up یا start؟9. Up or start?

بعد از docker compose stop هیچ تنظیمی عوض نشده و می‌خواهی همان containerها برگردند. چه می‌زنی؟ اگر port را در YAML تغییر داده باشی چه؟

After docker compose stop, nothing changed and you want the same containers back. What do you run? What if you changed a port in YAML?

دو موقعیت متفاوتTwo different situations

برای همان containerهای متوقف‌شده start مناسب است. برای تنظیمات تازه up -d بزن تا Compose تغییر را reconcile و در صورت نیاز container را recreate کند.

start is appropriate for the same stopped containers. For changed configuration, run up -d so Compose reconciles and recreates containers when needed.

۱۰. logs یا exec؟10. Logs or exec?

web از connection refused می‌گوید. اول log زندهٔ web را دنبال می‌کنی، یا وارد postgres می‌شوی و query می‌زنی؟ چه ترتیبی دلیل بیشتری می‌دهد؟

web reports connection refused. Do you first follow web logs or enter postgres and query it? Which order gives better evidence?

ترتیب کم‌حدس‌ترA lower-guesswork order

اول docker compose ps و logs web و logs postgres وضعیت هر دو طرف را نشان می‌دهند؛ اگر لازم شد exec postgres psql ... زنده‌بودن listener و پایگاه‌داده را جدا می‌کند. هر فرمان یک فرض را می‌سنجد.

docker compose ps and the logs for both services first show each side's state; if needed, exec postgres psql ... separately tests the database. Each command tests one hypothesis.

۱۱. down، داده را کجا گذاشت؟11. Where did down leave the data?

بعد از docker compose down، containerها پاک شده‌اند اما جدول هنوز بعد از up هست. این نتیجه را توضیح بده.

After docker compose down, containers are gone but the table remains after up. Explain the result.

چرخهٔ عمر resourceResource lifetime

named volume در down معمولی حذف نمی‌شود؛ container تازه همان volume را در data مسیر mount کرده است. فایل پایگاه‌داده مال writable layer container نبود.

A named volume survives ordinary down; the new container mounted it at the data path. The database files did not belong to the container writable layer.

۱۲. دستور خطرناک را پیش‌بینی کن12. Predict the destructive command

چه تغییری در نتیجهٔ پاک‌سازی می‌دهد اگر به docker compose down فلگ --volumes اضافه کنی؟

How does cleanup change if you add --volumes to docker compose down?

قبل از زدن دستورBefore running it

named volumeهای تعریف‌شده و anonymous volumeهای containerها هم حذف می‌شوند؛ پس data پوشه پایگاه‌داده پاک می‌شود. این فقط روی data آزمایشی انجام می‌شود و باید اثرش را با یک up تازه ببینی.

Declared named volumes and container anonymous volumes are removed too, so the database data directory is lost. Use it only on disposable data and verify the next up creates a fresh database.

۱۳. depends_on چه چیزی را ثابت می‌کند؟13. What does depends_on prove?

Compose پایگاه‌داده را قبل از web start کرده، اما اولین query رد شده است. آیا Compose ترتیب را نادیده گرفته؟ چه چیزی هنوز ثابت نشده؟

Compose started the database before web, but the first query failed. Did Compose ignore the order? What is still unproven?

پاسخ دقیقThe precise answer

نه؛ startup order رعایت شده است. اما آماده‌بودن پایگاه‌داده برای query هنوز ثابت نشده. فرایند می‌تواند Running باشد و initialization ادامه داشته باشد؛ برنامه باید خطای موقت را مدیریت کند.

No; startup order was respected. Database readiness for queries is still unproven. A process can be running while initialization continues, so the application must handle transient failures.

۱۴. کدام build دستور؟14. Which build command?

Dockerfile عوض شده و می‌خواهی image تازه build شود و web با آن بالا بیاید. از build، up و up --build کدام را انتخاب می‌کنی؟

The Dockerfile changed and you want a fresh image built and web running from it. Which do you choose: build, up, or up --build?

انتخاب بر اساس هدفChoose by the goal

docker compose up --build هر دو کار لازم را انجام می‌دهد. build فقط image را می‌سازد؛ up بدون flag ممکن است از image قبلی استفاده کند اگر سیاست build نیاز را تشخیص ندهد.

docker compose up --build performs both needed steps. build only creates the image; plain up may use the existing image when no rebuild is requested.

۱۵. service name با container name یکی نیست15. Service name is not container name

اسم container تولیدشده compose-lab-postgres-1 است. آیا web باید همین مقدار را برای نام میزبان ذخیره کند؟ چرا service name بهتر است؟

The generated container is named compose-lab-postgres-1. Should web store that as its hostname? Why is the service name better?

قرارداد پایدارترA more stable contract

خیر؛ از postgres استفاده کن. پروژه، replica number یا container recreation نام تولیدشده را عوض می‌کند، اما service discovery روی شبکه با service name ساخته شده است.

No; use postgres. Project names, replica numbers, or container recreation can change generated names, while network service discovery is designed around the service name.

۱۶. service بالا است یا کامل؟16. Is the service up or complete?

در docker compose ps -a، web وضعیت Exited (1) دارد. یک نفر می‌گوید «ولی up موفق شد». قدم‌های بعدی چیست؟

In docker compose ps -a, web is Exited (1). Someone says, “But up succeeded.” What do you inspect next?

از وضعیت به علتFrom state to cause

up موفق یعنی Compose توانست منابع را ایجاد/شروع کند، نه اینکه برنامه سالم بماند. docker compose logs web را بخوان، environment و نام میزبان را با docker compose config تطبیق بده، بعد همان فرض را اصلاح کن.

A successful up means Compose created or started resources, not that the app stayed healthy. Read docker compose logs web, compare environment and hostname with docker compose config, then fix the specific hypothesis.

۱۷. دو پروژه هم‌زمان17. Run two projects side by side

دو نفر از یک compose.yaml استفاده می‌کنند و منابعشان تداخل پیدا کرده است. چه راهی برای نام‌گذاری پروژه پیشنهاد می‌کنی؟

Two developers use the same compose.yaml and their resources collide. What project-naming approach do you recommend?

تفکیک منابعSeparate the resources

به هر اجرا پروژه name جدا بده، مثلاً docker compose -p alice up -d و -p bob. Compose نام network و volume و containerهای پروژه را جدا می‌کند؛ نام میزبان داخل هر پروژه همچنان service name می‌ماند.

Give each run a distinct project name, such as docker compose -p alice up -d and -p bob. Compose separates project containers, networks, and volumes; service hostnames remain service names inside each project.

۱۸. پنج دقیقهٔ اول عیب‌یابی18. The first five minutes of diagnosis

web به پایگاه‌داده وصل نمی‌شود. قبل از دست‌کاری تنظیمات، چهار سؤال و فرمانی بنویس که مسیر بررسی را محدود کنند.

web cannot reach the database. Write four questions and commands that narrow the path before changing configuration.

ترتیب معنادارAn evidence-led order

آیا Compose فایل را درست فهمیده؟ compose config. کدام service زنده است؟ compose ps -a. چه خطایی دید؟ compose logs web postgres. نام میزبان کدام است و آیا service همین نام را دارد؟ نهایی تنظیمات و service keys را مقایسه کن؛ فقط بعد از این شواهد مقدار را عوض کن.

Did Compose parse the file? Run compose config. Which service is alive? Use compose ps -a. What failed? Read compose logs web postgres. Which hostname is configured, and does a service with that name exist? Compare resolved config and service keys before changing anything.

آزمایشگاه: راه‌اندازی دستی را تبدیل کن به برنامه‌ای که هم‌تیمی‌ات هم بتواند بالا بیاوردLab: a reproducible web and PostgreSQL setup

هدف این تمرین کپی‌کردن یک YAML آماده نیست. همان وب و PostgreSQL را با Compose بالا می‌آوریم، بعد عمداً نام میزبان پایگاه‌داده را خراب می‌کنیم و از روی وضعیت و لاگ می‌فهمیم مشکل کجاست. در پایان هم با یک ردیف آزمایشی فرق down و down --volumes را با چشم می‌بینیم.

The goal is not to copy a file. We will turn a manual setup into a reproducible project, deliberately break its hostname, trace the failure, and use one disposable row to prove the difference between preserving and deleting data.

اول پروژه را بسازیم و فایل را بخوانیمFirst, create the project and inspect its file

این تمرین را در یک پوشهٔ تازه انجام بده. این کار فقط برای مرتب‌بودن نیست؛ می‌خواهیم پروژه و volume این فصل از تمرین‌های قبلی جدا باشند، تا وقتی در انتها پاک‌سازی می‌کنیم مطمئن باشیم چیزی خارج از همین آزمایش حذف نمی‌شود.

Create a fresh compose-lab folder with a web subfolder. Put the three application files from the build section in web, and place the final compose.yaml beside it. The fresh folder matters: the volume removed at the end will contain only disposable lab data.

قبل از ساخت resourceها، از پوشهٔ پروژه این دستور را بزن. اگر YAML بدفرم باشد، هنوز هیچ container یا volumeای نساخته‌ایم.

Run this from the project directory before creating resources. If the YAML is malformed, no containers or volumes have been created yet.

read the resolved model first
$ docker compose config
name: compose-lab
services:
  postgres:
    image: postgres:17
    volumes:
      - type: volume
        source: pg-data
        target: /var/lib/postgresql/data
  web:
    build:
      context: .../compose-lab/web
    depends_on:
      - postgres
    ports:
      - target: 3000
        published: "8080"
...
volumes:
  pg-data: {}

در خروجی دنبال سه قرارداد بگرد: web از ./web build می‌شود، پایگاه‌داده داده را در named volume می‌نویسد، و فقط port web روی میزبان منتشر شده است. اگر نام میزبان یا target volume اشتباه باشد، حالا ارزان‌تر است که فایل را اصلاح کنی.

Look for three contracts: web builds from ./web, the database writes to a named volume, and only web is published to the host. If a hostname or volume target is wrong, fixing the file now is cheaper.

حالا baseline را بالا بیاوریمNow bring up a working baseline

وقتی تنظیمات معنی‌دار شد، up -d --build هم image web را می‌سازد و هم پروژه را در پس‌زمینه اجرا می‌کند. بلافاصله بعدش status را بخوان؛ Compose توانسته container را شروع کند، اما باید ببینیم هر فرایند چه وضعی دارد.

Once the config is valid, up -d --build builds the web image and starts the project in the background. Read the status right afterward: Compose may have started containers, but we still need to see what each process is doing.

build, start, and inspect state
$ docker compose up -d --build
[+] Building ...
[+] Running 3/3
 ✔ Network compose-lab_default       Created
 ✔ Volume compose-lab_pg-data        Created
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started

$ docker compose ps -a
NAME                       SERVICE    STATUS          PORTS
compose-lab-postgres-1     postgres   Up 6 seconds    5432/tcp
compose-lab-web-1          web        Up 5 seconds    127.0.0.1:8080->3000/tcp

حالا میزبان از درِ publish شده web را امتحان می‌کند. در PowerShell نام curl.exe را صریح می‌نویسیم تا با alias خود PowerShell اشتباه نشود. JSON باید پایگاه‌داده با نام lesson را گزارش کند.

Now the host tests web through the published entry. In PowerShell, writing curl.exe explicitly avoids confusion with PowerShell's curl alias. The JSON should report a database named lesson.

host request and service logs
PS> curl.exe --fail http://127.0.0.1:8080
{"name":"lesson","time":"2026-09-24T..."}

$ docker compose logs --tail=10 web postgres
web-1      | web listening on 0.0.0.0:3000
postgres-1 | database system is ready to accept connections

$ docker compose logs -f web
web-1      | web listening on 0.0.0.0:3000

وقتی logs -f را اجرا کردی، خروجی زنده را ببین و با Ctrl+C فقط از حالت follow بیرون بیا. بعد با exec وارد همان service شو و یک ردیف آزمایشی بساز؛ -T برای فرمان غیرتعاملی، terminal مجازی را خاموش می‌کند.

Watch the live output from logs -f, then press Ctrl+C to stop following. Use exec to enter the existing service and create one disposable row; -T disables the pseudo-terminal for a non-interactive command.

prove the internal service connection
$ docker compose exec -T postgres psql -U lesson -d lesson \
  -c "CREATE TABLE IF NOT EXISTS memo (body text); INSERT INTO memo VALUES ('compose-kept-it');"
CREATE TABLE
INSERT 0 1

$ docker compose exec -T postgres psql -U lesson -d lesson \
  -c "SELECT body FROM memo;"
      body
-----------------
 compose-kept-it
(1 row)

این query از terminal میزبان با exec اجرا شد؛ خود web هم جداگانه از طریق postgres:5432 وصل می‌شود. هیچ publishای برای ۵۴۳۲ نداریم. دو مسیر را از هم نگه دار: میزبان-to-web و web-to-پایگاه‌داده.

This query ran from the host terminal through exec; web separately connects through postgres:5432. Port 5432 is not published. Keep the two paths distinct: host-to-web and web-to-database.

data را نگه دار، بعد نام میزبان را عمداً خراب کنKeep the data, then deliberately break the hostname

اول پروژه را با down جمع می‌کنیم؛ این کار container و network را حذف می‌کند و named volume را نگه می‌دارد. بعد دوباره بالا می‌آوریم و همان query را تکرار می‌کنیم. اگر ردیف برگشت، چرخهٔ عمر فصل ۰۶ را در Compose هم دیده‌ای.

First tear the project down with down; that removes containers and network while preserving the named volume. Bring it up again and repeat the query. If the row returns, you have seen Chapter 6's lifecycle inside Compose too.

down and up preserve the named volume
$ docker compose down
[+] Running 3/3
 ✔ Container compose-lab-web-1      Removed
 ✔ Container compose-lab-postgres-1 Removed
 ✔ Network compose-lab_default      Removed

$ docker compose up -d
[+] Running 3/3
 ✔ Network compose-lab_default       Created
 ✔ Container compose-lab-postgres-1 Started
 ✔ Container compose-lab-web-1      Started

$ docker compose exec -T postgres psql -U lesson -d lesson \
  -c "SELECT body FROM memo;"
      body
-----------------
 compose-kept-it
(1 row)

حالا درخواست اول را بشکنیم. در فایل، DB_HOST را از postgres به localhost عوض کن و docker compose up -d بزن. از میزبان به 127.0.0.1:8080 درخواست بده؛ مرورگر هنوز به web می‌رسد، اما web دنبال پایگاه‌داده داخل خودش می‌گردد.

Now break the first request. Change DB_HOST from postgres to localhost and run docker compose up -d. Request 127.0.0.1:8080 from the host: the browser still reaches web, but web now looks for a database inside itself.

trace the wrong destination before repairing it
PS> curl.exe -i http://127.0.0.1:8080
HTTP/1.1 503 Service Unavailable
database is not ready yet

$ docker compose logs --tail=5 web
web-1 | database request failed: connect ECONNREFUSED 127.0.0.1:5432

خطا پیش از PostgreSQL رخ داده: web به loopback خودش وصل شده و آنجا listenerای روی ۵۴۳۲ نیست. مقدار را به postgres برگردان، docker compose config را ببین و سپس up -d بزن تا تنظیمات تازه اعمال شود. پاسخ HTTP باید دوباره موفق شود.

The failure happened before reaching PostgreSQL: web connected to its own loopback, where nothing listens on 5432. Restore postgres, inspect with docker compose config, then run up -d to apply the configuration. The HTTP request should succeed again.

پایان کار: اثر پاک‌سازی را خودت ببینFinish by observing cleanup's effect

فایل نهایی از قبل depends_on دارد. اجرای سرد را از اول انجام بده و logهای web و postgres را کنار هم بخوان. در یک ماشین سریع شاید اولین query از همان ابتدا موفق شود؛ در ماشین دیگر ممکن است web موقتاً ۵۰۳ بدهد. هر دو با startup order سازگارند، چون آماده‌بودن پایگاه‌داده تضمین نشده و برنامهٔ ما خطا را برمی‌گرداند و زنده می‌ماند.

The final file already includes depends_on. Start cold and read web and PostgreSQL logs together. On a fast machine the first query may succeed immediately; on another, web may briefly return 503. Both are consistent with startup ordering because database readiness is not guaranteed and our app reports the error while staying alive.

بعد از بررسی، docker compose down بزن و در docker volume ls دنبال compose-lab_pg-data بگرد. چون این پروژه را در پوشهٔ تازه ساختی و داده‌اش آزمایشی است، حالا می‌توانی فرمان پاک‌کننده را هم اجرا کنی.

After inspecting, run docker compose down and look for compose-lab_pg-data in docker volume ls. Because this project lives in a fresh folder and its data is disposable, you can now demonstrate the destructive cleanup command.

destructive cleanup · this lab's disposable volume only
$ docker compose down --volumes
[+] Running 1/1
 ✔ Volume compose-lab_pg-data Removed

$ docker volume ls
DRIVER    VOLUME NAME
local     ...

حالا می‌دانی یک فایل می‌تواند resourceها را مرتب کند، اما تشخیص را از تو نمی‌گیرد: Compose orchestration را انجام می‌دهد، Engine containerها را اجرا می‌کند، service name روی network مقصد داخلی است و volume data را نگه می‌دارد. وقتی خطا دیدی، هنوز باید caller، نام میزبان، port و چرخهٔ عمر را بفهمی.

A file can organize resources without doing the diagnosis for you: Compose orchestrates, Engine runs the containers, service names identify internal destinations, and volumes keep data. When something fails, you still need to understand the caller, hostname, port, and resource lifetime.

یک فایل داریم؛ حالا سؤال بعدی این است که مقدارهای هر محیط را کجا نگه داریمWe have one file; what does the next chapter organize?

تا اینجا شبکه، volume، پایگاه‌داده و وب را در یک فایل تعریف کردیم و با یک دستور بالا آوردیم. اما هنوز یک چیز را عمداً ساده نگه داشتیم: رمزها و تنظیماتی که بین لپ‌تاپ، محیط آزمایش و سرور فرق می‌کنند. فصل ۹ دقیقاً همین سؤال را باز می‌کند: کدام مقدار فقط تنظیم است، کدام مقدار secret است و چطور همان image را بدون ساخت دوباره در چند محیط اجرا کنیم.

We can now describe a network, volume, database, and web service in one file and start them with one command. We still need to decide how environments are configured and where credentials belong. Chapter 9 handles that; here we used only the lab values directly.

سؤالجواب این فصلمرز فصل بعد/بعدتر
چند container را چطور تکرارپذیر اجرا کنم؟compose.yaml و docker compose upقواعد پیشرفتهٔ اجرای production اینجا نیست
web چطور database را پیدا می‌کند؟service name روی project networklocalhost یعنی خود caller
داده بعد از down کجا می‌ماند؟named volume می‌ماند تا حذفش کنیdown --volumes داده را پاک می‌کند
database حتماً آماده است؟depends_on ترتیب startup می‌دهدhealthcheck فصل ۱۰ است
رمز را چطور نگه دارم؟فقط مقدار lab برای تمرینenvironment و secret در فصل ۰۹

مرجع سریعQuick reference

docker compose configمدل parse/نهایی را پیش از اجرا ببینinspect the parsed and resolved model before running
docker compose up -dserviceها را در پس‌زمینه بساز/هماهنگ کن و start کنcreate or reconcile services and start them detached
docker compose up -d --buildimage لازم را build کن و پروژه را بالا بیاورbuild required images and bring up the project
docker compose ps -astatus containerهای service را ببینinspect service container states
docker compose logs -f webخروجی زندهٔ web را دنبال کنfollow web output live
docker compose exec postgres psql ...دستور را در container موجود اجرا کنrun a command in the existing container
docker compose stop / start / restartcontainerهای موجود را متوقف، start یا restart کنstop, start, or restart existing containers
docker compose downcontainer و network را بردار؛ named volume بماندremove containers and network; keep named volumes
docker compose down --volumesnamed/anonymous volumeهای پروژه را هم حذف کنalso remove project named and anonymous volumes

برای مرجع رسمی از مدل برنامه در Compose، شبکه و service name، ترتیب startup و depends_on، رفتار up و build و رفتار down و volumeها استفاده کن. جزئیات commandها ممکن است با نسخهٔ Compose فرق کند.

For official reference, see the Compose application model, networking and service names, startup order and depends_on, up and rebuild behavior, and down and volume behavior. Command details can vary by Compose version.