Parent network container in Docker

Docker logo

Wrapping web application in Docker is quite simple once you learned it a bit. You create a service for backend application, for database, for frontend part, you map ports and volumes to each — voila, application is up and running. In most cases, default networking and other configurations are sufficient. A case I’d like to bring out, however, did not benefit from those default configurations, even though container structure was pretty basic.

Layout

A very simple project consisting of:

  1. Single page ReactJS application
  2. PHP (Symfony 6) backend application
  3. PostgreSQL 14 database

The only significant (and problematic) aspect of this project is that the backend dynamically adds new component with new internal port during runtime, and the frontend needs to be able to access it.

In non-Docker environment we would make it running without any problem with web server’s virtual hosts — one for frontend, one for backend. New published port would be reachable as long as we can guarantee that it is always the same port.

The problem

A draft for docker-compose.yml in this case would look something like that.

For simplicity sake let’s assume that we already have a Dockerfile to build backend service from php:8.1-apache image.

After starting the container, we should have access to frontend from localhost:3000 and to backend from localhost:8000.

As we know by now, PHP application will set up a component with different port during runtime. Let’s hardcode it to be :8888 in the code and modify docker-compose.yml accordingly.

And now we’re stuck. How to make frontend application reach both backend ports? They are separate services with separate ‘localhosts’. Host system (our machine) can reach anything on localhost as ports are published and mapped, but from the frontend service perspective only localhost:3000 exists.

As a matter of fact, the last statement was not entirely true. Default Docker network provides services access to each other. So we can build React application service with a URL to backend service. To ensure that on container start application will update this link, let’s introduce an environment variable.

But what about the component created in backend with port 8888?

This is where the real problem is. No variable and no hardcoded URL will make frontend reach this component. Strictly speaking, the component didn’t exist on container start and is not a standalone service for Docker network to connect, so entire container doesn’t know anything about it.

The solution

To resolve this issue we should dive into Docker networking and tweak our Docker compose configuration.

Besides the possibility to use different network types we actually can inject one service into the network domain of another. This way one container will define all ports and act as some kind of ‘network parent’ for injected one. Since all complexity in this case comes from backend service, we should make it parent.

First, we need to adjust backend service configuration.

Two important changes were made here.

  1. Frontend port is mapped in backend service configuration and removed from frontend service because all networking will be done through backend service.
  2. Extra hosts configuration was introduced. Essentially, we mapped host machine localhost to container localhost thus making it possible to reach any port both from host and other services.

Note that we don’t change database service configuration at all.

As parent network container configured, frontend service now can be injected into it.

The most important thing here is ‘network_mode’ configuration. Syntax is quite straightforward: declare network mode as ‘service’ and provide a name for this service (‘backend’ in our case). As a precaution measure ‘depends_on’ configuration was added. It will ensure that frontend service will not start unless backend service is up and running.

The result

With new Docker configuration frontend service now is able to operate in the same domain as backend, with no ports issues. Resulting docker-compose.yml looks like this.

Overview

The solution provided (along with case itself) has limited usage.

Most of the time default Docker networks will do just fine in connecting containers of the same image together.

However, it may be quite an option in situations where network alone is not the source of a problem, but application runtime aspects are.

--

--

PHP Developer, Teacher and TechLead. See you on GitHub: https://github.com/sauromates.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store