Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як контейнери спілкуються між собою в Docker Compose?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Docker Compose створює дефолтну user-defined bridge-мережу на project.** Усі сервіси приєднуються автоматично і дістають одне одного по service-імені через embedded DNS Docker, без IP, без `localhost`, без ручного config. ```yaml services: api: image: myapp environment: DATABASE_URL: postgres://postgres:dev@db:5432/app # ← "db", не localhost db: image: postgres:16 ``` **Головне:** service-імена це hostname. З `api` `db:5432` працює. З host треба `localhost:<published-port>`. Додавай `expose:` для документації, `ports:` лише коли потрібен host-доступ.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Спілкування між 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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.