Як контейнери спілкуються між собою в 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-servicenetworks:приєднує. - Механізм це той самий Docker bridge networking + embedded DNS, просто декларативно через Compose.
Швидкий приклад
# 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$ 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:
- Compose читає
compose.yaml. - Створює мережу під іменем
<project>_default(project = ім'я директорії, якщо не override). - Створює один container на сервіс, кожен приєднано до цієї мережі.
/etc/resolv.confкожного container вказує на embedded DNS Docker (127.0.0.11).- DNS знає про кожен container на мережі, по
<service-name>і<container-name>.
Це просто стандартний user-defined bridge Docker, Compose це тонка обгортка, що автоматизує танець docker network create + docker run --network ... --name ....
Чому localhost НЕ працює між сервісами
Поширена помилка новачків:
# НЕПРАВИЛЬНО
services:
api:
environment:
DATABASE_URL: postgres://postgres@localhost:5432/app # ← ПОГАНО
db:
image: postgres:16З container api localhost це сам api-container. localhost:5432 намагатиметься з'єднатися з api на порту 5432, де нічого не слухає. Правильна форма:
api:
environment:
DATABASE_URL: postgres://postgres@db:5432/app # ← service-ім'яКожен container має свій loopback. Трафік між сервісами іде по імені (або IP), ніколи не localhost.
ports vs expose vs нічого
services:
web:
image: nginx
ports:
- "80:80" # публікувати host: так
api:
image: myapp
expose:
- "3000" # тільки документація; внутрішньо доступний на порту 3000
db:
image: postgres:16
# нічого, але все одно доступний з `api` і `web` на db:5432ports:= публікація на host (еквівалент-p). Лише те, до чого має бути доступ ззовні.expose:= чиста документація/метадані. Не впливає на inter-container reach.- Нічого = сервіс все одно доступний іншим сервісам на будь-якому порту, що слухає. Лише host не дістає.
Кілька мереж для розділення
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 мережа):
services:
api:
image: myapp
networks: [shared]
networks:
shared:
external: true # уже існує, не створюватиКорисно для речей як Traefik, що стежить за єдиною shared-мережею для нових container між багатьма Compose-проектами.
Типові помилки
Використання localhost між сервісами
Згадано вище. Найпоширеніший Compose-баг.
Забути, що ports: host-facing
services:
api:
image: myapp
ports: ["3000:3000"] # НЕ потрібно для трафіку db→api або web→apiЯкщо api дістають лише інші сервіси project, прибери ports:. Він допомагає лише, коли ТИ на host хочеш дістати localhost:3000.
Hardcoding container-імен замість service-імен
# НЕПРАВИЛЬНО: працює випадково, якщо ім'я project збігається
DATABASE_URL: postgres://postgres@myapp-db-1:5432/app
# ПРАВИЛЬНО: service-ім'я портативне
DATABASE_URL: postgres://postgres@db:5432/appContainer-ім'я це <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 на
proxyexternal-мережі; багато 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 спілкуванням
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-мережа між проектами
# 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 працює
$ 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_defaultEmbedded resolver на 127.0.0.11 це те, що робить db робочим. Він є частиною кожного user-defined bridge.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів