Parent network container in Docker
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.
A very simple project consisting of:
- Single page ReactJS application
- PHP (Symfony 6) backend application
- 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.
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
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
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
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.
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.
- Frontend port is mapped in backend service configuration and removed from frontend service because all networking will be done through backend service.
- 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.
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.
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.