Упаковка веб-приложения в Docker - довольно простая задача, если разобраться с базовыми понятиями работы контейнеров. Готовим контейнер для бэкенда, для базы данных, для фронтенд-приложения - и вуаля, приложение полноценно функционирует. В большинстве случае стандартная настройка сети и конфигурация в Docker покрывает все нужды разработчиков.
Но в данной конкретной ситуации, несмотря на кажущуюся простоту структуры проекта, этой базовой конфигурации оказалось мало.
Кейс
В работе был очень простой проект, состоящий всего из трех компонентов:
SPA приложение на ReactJS
Бэкенд на Symfony 6
База данных PostreSQL 14
Загвоздкой оказался один нюанс. Бэкенд должен был динамически создавать новый внутренний сервер, слушающий другой порт, а приложение на React должно иметь возможность обращаться к нему напрямую.
Если бы проект не был упакован в Docker, возможно, проблемы бы и не было. Веб-сервер, виртуальный хост, настроенный заранее, и любое приложение могло бы обращаться на нужный фиксированный порт, когда сам сервер был бы развернут.
Проблема
Первоначальный вариант конфигурации Docker мог бы выглядеть примерно так.

После сборки и запуска трех контейнеров у нас есть доступ как к бэкенду через localhost:8000, так и к фронтенду (стандартно на localhost:3000). Как уже было сказано, PHP контейнер создаст новый компонент. Для простоты начнем с попытки прописать в конфигурации того же контейнера фиксированный порт для этих целей. Тогда в docker-compose.yml мы сделаем примерно такие строки:
backend:
ports:
- 8000:80
- 8888:8888И с этой же попытки мы сталкиваемся с настоящей проблемой. Во время сборки и запуска контейнеров динамического компонента не существует, и он не выделен в отдельный контейнер, так что подключиться по имени контейнера средствами Docker network не получится. Как же заставить фронтенд обратиться к новому компоненту внутри контейнера “backend”?
Решение
Чтобы справиться с этой проблемой, нам нужно немного подкорректировать настройки сети между этими двумя контейнерами.
Возможности настройки предоставляют нам не только разные типы сетей (host, bridge). На самом деле, мы можем внедрить ��дин контейнер в сеть другого контейнера прямо в docker-compose.yml. В этом случае первый контейнер станет своего рода “мастер-контейнером”, а второй - дочерним с точки зрения сетевой настройки. И поскольку контейнер backend несет в себе все эти дополнительные сложности, сделаем мастером именно его.
Для начала добавим связку хоста с внутренней сетью контейнера. Для этого в конфигурации контейнера backend добавим секцию extra_hosts и уберем проброс портов для контейнера фронтенд-приложения, там они больше не понадобятся. Вместо этого, включим проброс порта 3000 с локальной машины сразу в контейнер ‘backend’.
Наконец, включим особый сетевой режим для контейнера ‘frontend’, присоединив его в один сетевой домен с бэкендом. Измененная конфигурация будет выглядеть следующим образом.

Результат
С помощью новой конфигурации контейнер фронтенд-приложения имеет возможность обращаться к любому порту, назначенному родительскому контейнеру ‘backend’, так как теперь у них обоих общий localhost. Мы же можем обращаться к ним через localhost нашей машины, поскольку привязали их друг к другу с помощью секции extra_hosts.
Разумеется, сфера применения такого решения довольно узкая, поскольку и сам кейс довольно специфичный. Как мы и говорили в самом начале, в подавляющем большинстве случаев стандартные настройки сети Docker справляются на отлично. Здесь же представлен выход из ситуации, когда источник проблемы кроется не в самой сети, а в аспектах динамически изменяемой среды.
