Before understanding we docker networking let's understand networking in general.
Network : A collection of interconnected devices or servers or apps, where they are able to communicate with each other and share data.
When we run an application and we access it on localhost
. Its because the application is running in our laptop within its private network.
So if you try to access localhost on some other device that will point to that system private network.
While running multiple applications on our system and they are able to communicate with each other is because they are all connected to the same network on our system.
If we use the IPv4 address we can access the apps in our local network. That is all the devices connected with the same router.
Check out this example how multiple endpoints are communicating with each other.
node index.js
Result of Service B data fetch : Encrypted_Service_Key_In_AppOne
Use the dockerfile in the app to create images and container.
docker build -t multi-port-app-test:v1.0 .
docker run --name multi-port-app -p 8080:3003 multi-port-app-test:v1.0
Result of Service B data fetch : Encrypted_Service_Key_In_ServiceA
docker run --name multi-port-app-2 -p 8181:3003 multi-port-app-test:v1.0
Now let's get back to our single app and try running it with node index.js
. It starts to run on port 3000 and logs reflect the same.
Let's open a new terminal and run the app again. But this time we get an error
info - 2024-10-16 09:36:10.523 - Failed to start server !! Error : {"code":"EADDRINUSE","errno":-4091,"syscall":"listen","address":"::","port":3000}
Our app could not run because the port 3000 was already in use. Well that was expected.
But how come the second container above was able to run on the same port 3003 and both services are still accessible. Both 8080 and 8181 address get's us the result.
Now let's use the same approach but with a minor tweak and split the services in two different project and each service gets it own dockerfile so they can have a container of their own.
Let dive in with our ubuntu terminal and create images and container
docker build -t multi-network-app:app-one .
docker build -t multi-network-app:app-two .
docker run -d --name app-one -p 8080:3002 multi-network-app:app-one
docker run --name app-two -p 8181:3003 multi-network-app:app-two
info - 2024-10-17 03:44:13.920 - registering serviceA routes
info - 2024-10-17 03:44:13.922 - greet route registered !!
info - 2024-10-17 03:44:13.923 - serviceKey route registered !!
info - 2024-10-17 03:44:13.926 - App is running on http://*:3003
We got an error : ECONNREFUSED
, which means the connection could not be established to the serverRemember what i said earlier ?? Services can communicate because they are in the same network
As long as they were in the same container, they were able to communicate but as soon as they entered different containers, they can no longer communicate.
This is because they are in different networks now.
Each docker container is completely isolated and has its own separate network namespace. So each container gets its own network and port numbers.
Referring back to our confusion the reason we were able to run our app on same port because they were in different network namespace. Each container has its own set of ports.
Docker bridges are like virtual LAN. When containers are connected with a bridge, they can communicate with each other, just like devices in physical LAN.
Docker by default creates a bridge called 'bridge'. To check the list of all available bridges, we can run docker network ls
.
Default networks
NETWORK ID NAME DRIVER SCOPE
ee69a25fedbb bridge bridge local
24145443ea38 host host local
de96e11186d1 none null local
By default, all the containers are connected with default bridge 'bridge' unless specified otherwise.
We can check more details about the bridge including the attached containers with docker network inspect <network_name>
. Let's check the default bridge and we should have our containers attached to it docker inspect bridge
.
[
{
"Name": "bridge",
"Id": "ee69a25fedbb0fd795cff17a4ca7f46a0f7aa9e1f087ca9e8fc84275238be3fe",
"Created": "2024-10-17T06:59:10.624506946+05:30",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
// I have masked these for security reasons
"Subnet": "***.**.*.*/**",
"Gateway": "***.**.*.*"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"c36452a8e3e29efccccb7ca202bf51e9fec27bcbbecd331d997e3628a8501a26": {
"Name": "app-one",
"EndpointID": "6ffbbddc447827927816c33cea5f35332911bdf74671dea3012897488e944b99",
// I have masked these for security reasons
"MacAddress": "**:**:**:**:**:**",
"IPv4Address": "***.**.*.*/**",
"IPv6Address": ""
},
"f02bf8dd6cca455ae26a84fccd009fd88c6e0e9b74393c6ed2ff8c846c7c8a1c": {
"Name": "app-two",
"EndpointID": "6a965e4dcecf1465a09f299b0fdb66f5793bb976b302638c16e2eee5779f6203",
// I have masked these for security reasons
"MacAddress": "**:**:**:**:**:**",
"IPv4Address": "***.**.*.*/**",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
Only the containers currently running are attached to networks i.e containers that are displayed with
docker ps
. To check this, start other containers withdocker start <container_name/id>
, and run the command again to check the updated list
Containers linked to default bridge:
--link
legacy flag.Let's create a custom network and see if what we learned is actually correct.
To create a new network we do docker network create <network_name>
.
I want to name my network as 'custom-network'. So, I'll run docker network create custom-network
.
Mow let's check the list of networks with docker network ls
NETWORK ID NAME DRIVER SCOPE
ee69a25fedbb bridge bridge local
c92d6eec959a custom-network bridge local
24145443ea38 host host local
de96e11186d1 none null local
Cool !! so now we have our own network.
Let's create new apps to setup on our new network. Its just our multi-network-app node with minor refactoring and updates to make more sense in the example we are going to use.
Checkout this repo : https://github.com/dev-danish-javed/technotes-demo-code/tree/main/docker-demo/node-code/bridge-app
Our notification processor needs to pick keys from vault. So it needs to connect to the other ms.
Let's set our system to communicate with each other.
docker build -t vault-ms:v1.0 .
docker run --name vault-ms vault-ms:v1.0
, notice that we have not mapped portsdocker build --build-arg vault_ms_url="http://vault-ms:3002" -t security-processor:v1.0 .
docker run -p 8282:3003 --name security-processor security-processor:v1.0
Now let's try to access our app on http://localhost:8282/fetchKey
The output is We got an error : EAI_AGAIN
which is "DNS lookup timed out error". So basically the DNS vault-ms could not be resolved.
But our both our apps are running.
Let's add them to the same network and try again. The command to do that is :
docker network connect <network_name> <container_name_or_id>
.
I'll do docker network connect custom-network vault-ms
and docker network connect custom-network security-processor
.
There will be no output for the commands as we don't have errors. If there were errors they would have been displayed.
Let's check the network to make sure they are both connected to our network.
Run docker network inspect custom-network
. There we'll have both containers listed in container section.
Now let's hit the endpoint http://localhost:8282/fetchKey again and see if that works and voila !!!
We get the desired response Result of ServiceTwo data fetch : Encrypted_Service_Key_From_Vault
Let's remove it from the network with docker network disconnect custom-network security-processor
Now endpoint get us the error We got an error : EAI_AGAIN
Instead of creating containers and then adding them to network we can also provide the network name when starting the container.
Let's create another container directly with network
docker run --network custom-network -p 8383:3003 --name security-processor-2 security-processor:v1.0
Emphasis on --network custom-network, again we get the desired result on 8383 as well.