Skip to main content

Як контейнери спілкуються між собою в Docker Compose?

Спілкування між container у Docker Compose це найбільш магічна частина Compose для новачків. Ти пишеш db у connection string, container резолвить це у правильний IP, і все просто працює. Під капотом Compose створює user-defined bridge-мережу і використовує embedded DNS Docker, ось і весь трюк.

Теорія

TL;DR

  • Compose створює дефолтну мережу для кожного project: <projectname>_default, тип bridge, user-defined.
  • Усі сервіси у compose.yaml приєднуються автоматично; імена сервісів стають hostname всередині мережі.
  • З сервісу A <service-B-name>:<port> резолвиться у IP сервісу B.
  • Container дістають одне одного на будь-якому порту, що destination слухає, не лише на опублікованих. ports: важить лише для host-доступу.
  • Можливі кілька мереж: блок networks: нагорі визначає; per-service networks: приєднує.
  • Механізм це той самий Docker bridge networking + embedded DNS, просто декларативно через Compose.

Швидкий приклад

yaml
# compose.yaml services: api: image: myapp environment: DATABASE_URL: postgres://postgres:dev@db:5432/app REDIS_URL: redis://redis:6379 ports: - "3000:3000" # лише api відкритий host db: image: postgres:16 environment: POSTGRES_PASSWORD: dev redis: image: redis:7
bash
$ docker compose up -d [+] Running 4/4 ✔ Network myapp_default Created ✔ Container myapp-db-1 Started ✔ Container myapp-redis-1 Started ✔ Container myapp-api-1 Started # З будь-якого container резолвимо сусідів по імені $ docker compose exec api nslookup db Name: db Address: 172.18.0.2 $ docker compose exec api curl http://redis:6379 # підключається

Три сервіси, одна мережа створена автоматично (myapp_default), DNS резолвить db і redis на правильні IP.

Що Compose створює під капотом

Коли робиш docker compose up:

  1. Compose читає compose.yaml.
  2. Створює мережу під іменем <project>_default (project = ім'я директорії, якщо не override).
  3. Створює один container на сервіс, кожен приєднано до цієї мережі.
  4. /etc/resolv.conf кожного container вказує на embedded DNS Docker (127.0.0.11).
  5. DNS знає про кожен container на мережі, по <service-name> і <container-name>.

Це просто стандартний user-defined bridge Docker, Compose це тонка обгортка, що автоматизує танець docker network create + docker run --network ... --name ....

Чому localhost НЕ працює між сервісами

Поширена помилка новачків:

yaml
# НЕПРАВИЛЬНО services: api: environment: DATABASE_URL: postgres://postgres@localhost:5432/app # ← ПОГАНО db: image: postgres:16

З container api localhost це сам api-container. localhost:5432 намагатиметься з'єднатися з api на порту 5432, де нічого не слухає. Правильна форма:

yaml
api: environment: DATABASE_URL: postgres://postgres@db:5432/app # ← service-ім'я

Кожен container має свій loopback. Трафік між сервісами іде по імені (або IP), ніколи не localhost.

ports vs expose vs нічого

yaml
services: web: image: nginx ports: - "80:80" # публікувати host: так api: image: myapp expose: - "3000" # тільки документація; внутрішньо доступний на порту 3000 db: image: postgres:16 # нічого, але все одно доступний з `api` і `web` на db:5432
  • ports: = публікація на host (еквівалент -p). Лише те, до чого має бути доступ ззовні.
  • expose: = чиста документація/метадані. Не впливає на inter-container reach.
  • Нічого = сервіс все одно доступний іншим сервісам на будь-якому порту, що слухає. Лише host не дістає.

Кілька мереж для розділення

yaml
services: web: image: nginx networks: [frontend] ports: ["80:80"] api: image: myapp networks: [frontend, backend] # міст між frontend і backend db: image: postgres:16 networks: [backend] networks: frontend: backend:

web не дістає db напряму, не діляють жодної мережі. Лише api стоїть на обох. Класична three-tier ізоляція: edge ↔ app ↔ data.

External мережі

Іноді сервіс має дістати мережу, що існує поза Compose-проектом (наприклад, спільна reverse-proxy мережа):

yaml
services: api: image: myapp networks: [shared] networks: shared: external: true # уже існує, не створювати

Корисно для речей як Traefik, що стежить за єдиною shared-мережею для нових container між багатьма Compose-проектами.

Типові помилки

Використання localhost між сервісами

Згадано вище. Найпоширеніший Compose-баг.

Забути, що ports: host-facing

yaml
services: api: image: myapp ports: ["3000:3000"] # НЕ потрібно для трафіку db→api або web→api

Якщо api дістають лише інші сервіси project, прибери ports:. Він допомагає лише, коли ТИ на host хочеш дістати localhost:3000.

Hardcoding container-імен замість service-імен

yaml
# НЕПРАВИЛЬНО: працює випадково, якщо ім'я project збігається DATABASE_URL: postgres://postgres@myapp-db-1:5432/app # ПРАВИЛЬНО: service-ім'я портативне DATABASE_URL: postgres://postgres@db:5432/app

Container-ім'я це <project>-<service>-<index>, міняється при перейменуванні project. Service-ім'я стабільне.

Cross-project спілкування без external-мережі

Два окремих Compose-проекти (projectA і projectB) за замовчуванням не дістають один одного, кожен має свою мережу. Щоб з'єднати, оголоси мережу external: true і нехай сервіси з обох проектів приєднуються.

Реальне застосування

  • Three-tier dev-стеки: web/api/db, усі в одному Compose-файлі, усі говорять по service-імені. Найпоширеніший Compose use.
  • Shared infrastructure: Traefik або nginx-proxy на proxy external-мережі; багато Compose-проектів приєднуються для routing.
  • Доступ до бази з one-off job: docker compose run --rm migrator крутиться у новому container на project-мережі, дістає db по імені.
  • Test-фікстури у CI: docker compose up -d db redis && run-tests && docker compose down. Тести крутяться на тій самій мережі і дістають сервіси по імені.

Питання для поглиблення

Q: Що таке project-ім'я і звідки воно?


A: За замовчуванням директорія, що містить compose.yaml. Override через флаг -p myname, env-змінну COMPOSE_PROJECT_NAME або name: нагорі compose.yaml. Ім'я мережі <project>_default, container-імена <project>-<service>-<index>.

Q: Чому web дістає db на порту 5432, якщо я ніколи його не публікував?


A: Бо публікація (ports:) про HOST-доступ, не container-to-container. Всередині project-мережі кожен container дістає кожен порт, що слухає інший container.

Q: Чи можу вимкнути дефолтну мережу?


A: Так, постав default: у null-еквівалент і визнач свою. Рідко на практиці; зазвичай просто override'аєш driver або subnet через networks: { default: { driver: bridge, driver_opts: ... } }.

Q: Як container у різних Compose-проектах знаходять одне одного?


A: Або визнач мережу external: true і нехай обидва project приєднуються, або docker network connect running container до мережі іншого project руками. Поширений патерн це shared proxy-мережа.

Q: (Senior) Як Compose відрізняється від Swarm для service discovery?


A: Compose крутить сервіси як звичайні container; service discovery через embedded DNS bridge-мережі (один IP на сервіс, IP container). Swarm крутить сервіси як replicated task на overlay-мережах; discovery через virtual IP, що балансує між реплік (IPVS-backed). Механізм Compose простіший і single-host; Swarm multi-host і load-balanced. Обидва виглядають однаково для застосунку (db:5432 працює в обох), але шлях пакета різний.

Приклади

Three-service стек з cross-service спілкуванням

yaml
services: web: image: nginx:1.27-alpine ports: - "80:80" depends_on: [api] volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro api: image: myapp environment: DATABASE_URL: postgres://postgres:dev@db:5432/app REDIS_URL: redis://redis:6379 depends_on: db: condition: service_healthy db: image: postgres:16 environment: POSTGRES_PASSWORD: dev POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s redis: image: redis:7 volumes: pgdata:

Чотири сервіси. Лише web доступний з host (порт 80). Внутрішньо web → api → (db, redis) усе по service-імені.

Shared proxy-мережа між проектами

bash
# One-time setup $ docker network create --driver bridge proxy # compose.yaml project A services: app-a: image: app-a networks: [proxy] networks: proxy: external: true # compose.yaml project B services: app-b: image: app-b networks: [proxy] networks: proxy: external: true # Тепер з app-a `app-b:8080` резолвиться і підключається.

Traefik зазвичай сидить на цій мережі, стежить за новими container через Docker labels і маршрутизує трафік. external: true це те, що робить cross-project connectivity можливим.

Перевірка, що DNS працює

bash
$ docker compose exec api sh -c 'cat /etc/resolv.conf && nslookup db' nameserver 127.0.0.11 options ndots:0 Server: 127.0.0.11 Name: db Address 1: 172.18.0.2 db.myapp_default

Embedded resolver на 127.0.0.11 це те, що робить db робочим. Він є частиною кожного user-defined bridge.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів