DOCKER
#docker#networking#containers#backend

Docker networking: почему localhost не работает между контейнерами

Объяснение работы сетей Docker: почему localhost внутри контейнера — это не хост и как правильно подключаться к сервисам.

Как использовать

  1. В строках подключения к БД из приложения в контейнере используйте имя сервиса (db, mysql, postgres), а не localhost или 127.0.0.1.
  2. Задавайте хост через переменные окружения (DATABASE_URL, DB_HOST) и в docker-compose подставляйте имя сервиса.
  3. Проверка: docker compose exec app ping -c 1 db или getent hosts db.

Приложение в одном контейнере не может подключиться к базе по localhost или 127.0.0.1, хотя на машине разработчика так работало. Причина: внутри контейнера localhost — это сам контейнер, а не хост и не другой контейнер. Проблема возникает при переносе приложения в Docker: в коде или в .env остаётся DB_HOST=localhost, и из контейнера приложения запрос идёт «к себе», а не к контейнеру с БД. Симптомы: connection refused, timeout при подключении к БД из app-контейнера. Ниже — почему так устроено, как Docker DNS резолвит имена сервисов, правильные строки подключения и проверка по документации Docker Networking.

Решение

localhost и 127.0.0.1 — loopback, обращение к самому себе. В контейнере у каждого контейнера свой сетевой namespace: свой loopback. Для контейнера app 127.0.0.1 — это контейнер app; для контейнера db — контейнер db. База в другом контейнере с точки зрения сети — другой хост. Подключаться нужно по имени сервиса (в Compose или в user-defined сети): db, mysql, postgres и т.п. Порт — внутренний порт контейнера (5432, 3306), не проброшенный на хост.

Как Docker DNS разрешает имена

На user-defined сети (Docker Compose по умолчанию) работает embedded DNS: контейнеры видят друг друга по имени сервиса. Имя из docker-compose.yml (или --name при docker run) и есть хост для подключения.

Пример docker-compose.yml

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://postgres:secret@db:5432/appdb
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Хост в DATABASE_URLdb, не localhost.

Node.js → PostgreSQL

Неправильно:

// ❌ Подключение к порту 5432 внутри контейнера app, а не к db
const connectionString = "postgresql://postgres:secret@localhost:5432/appdb";

Правильно:

const connectionString =
  process.env.DATABASE_URL || "postgresql://postgres:secret@db:5432/appdb";

В Compose: DATABASE_URL: postgresql://postgres:secret@db:5432/appdb.

PHP → MySQL

Неправильно:

$host = '127.0.0.1'; // ❌ внутри контейнера PHP — это сам PHP, MySQL там нет
$dsn = "mysql:host=$host;port=3306;dbname=appdb";

Правильно:

$host = getenv('DB_HOST') ?: 'db';
$dsn = "mysql:host=$host;port=" . (getenv('DB_PORT') ?: '3306') . ";dbname=" . (getenv('DB_NAME') ?: 'appdb');

В Compose для app: DB_HOST: db, DB_PORT: "3306", DB_NAME: appdb.

Проверка

  1. Резолв имени сервиса из контейнера приложения:
docker compose exec app ping -c 1 db

Ожидаем ответ от контейнера db. Если «unknown host» — сервисы не в одной сети или имя сервиса другое.

  1. Проверка через getent:
docker compose exec app getent hosts db

Должен вывести IP контейнера db. Подставьте имя своего сервиса вместо db.

  1. Проверка подключения к порту БД (если в контейнере есть nc или telnet):
docker compose exec app sh -c "nc -zv db 5432 2>&1 || true"

Для MySQL порт 3306. Успех — строка вида «db (172.x.x.x:5432) open».

Типичные ошибки

  • DB_HOST=localhost или 127.0.0.1 в Docker — в контейнере приложения localhost это сам контейнер. Задайте хост именем сервиса через переменные окружения в Compose (DB_HOST: db, DATABASE_URL с хостом db).
  • Работает на машине, не работает в Docker — на хосте БД слушает на 127.0.0.1; в Docker БД в другом контейнере. Для локального запуска без Docker оставьте localhost в .env.local; в Docker не переопределяйте переменные на localhost.
  • Default bridge — на default bridge контейнеры не резолвят друг друга по имени. Используйте Compose (создаёт user-defined сеть) или docker network create и подключайте контейнеры к ней.

Где применять

  • Разработка и prod: любой стек в Docker (app + db в контейнерах). Всегда подключаться к БД по имени сервиса в той же сети.
  • Compose: по умолчанию все сервисы в одном проекте в одной сети; имена сервисов = имена хостов.

Связанные сниппеты: Volumes vs bind mounts в Compose, Разница docker run / start / exec.

Ссылки: Networking overview, User-defined bridge.