Docker networking: почему localhost не работает между контейнерами
Объяснение работы сетей Docker: почему localhost внутри контейнера — это не хост и как правильно подключаться к сервисам.
Как использовать
- В строках подключения к БД из приложения в контейнере используйте имя сервиса (db, mysql, postgres), а не localhost или 127.0.0.1.
- Задавайте хост через переменные окружения (DATABASE_URL, DB_HOST) и в docker-compose подставляйте имя сервиса.
- Проверка: 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_URL — db, не 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.
Проверка
- Резолв имени сервиса из контейнера приложения:
docker compose exec app ping -c 1 db
Ожидаем ответ от контейнера db. Если «unknown host» — сервисы не в одной сети или имя сервиса другое.
- Проверка через getent:
docker compose exec app getent hosts db
Должен вывести IP контейнера db. Подставьте имя своего сервиса вместо db.
- Проверка подключения к порту БД (если в контейнере есть 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.