Networking in Docker

Complete documentation available at https://docs.docker.com/network/


Docker containers do not live in isolation: They can be connected to other containers, reach resources in the non-Docker world (e.g., on the public Internet), or be reached by the non-Docker world (e.g., our Http Web server myapache). In addition, services running in containers do not need to know if they are running in a container or their counterparts do. In other words, the way Docker manages network connectivity is completely transparent from the service perspective.

Docker controls network connectivity via a pluggable subsystem that offers diverse functionalities according to the network driver being used. Several drivers exist, but for this tutorial the discussion is limited to:

- bridge: It is the default network driver and, likely, the one that you want to use in most cases. With this driver, Docker containers run in a dedicated network and are able to communicate with each other. Reachability from the outside world depends on the ports that are exposed by every container.

- host: This driver removes the isolation between the container and the Docker host. The container will be able to use the host network directly.

- none: All networking is disabled. The processes running in containers are isolated and can communicate neither with other containers nor with the outside world.


Using the bridge driver

When nothing is specified in the run command, the bridge driver is the one chosen by default (and also the one used so far by our containers).

Let's consider myapache once again and investigate from the network perspective:

> # docker inspect builtapache 
[...cut...]
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "06d3feef0aac5d3849bd0cdec93563670116988a4bb86e714b490b02cb57d4f6",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "80/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "32769"
                    }
                ]
            },
            "SandboxKey": "/var/run/docker/netns/06d3feef0aac",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "49b78e8dca9d7e1eaf6f92f2db157349371edb193fb11b5afe2c727cb35560f8",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "1df2e889a8116d1ed9cf91be978738b069314f192df65adc804c7198a0ab8e09",
                    "EndpointID": "49b78e8dca9d7e1eaf6f92f2db157349371edb193fb11b5afe2c727cb35560f8",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }


Viceversa, we can list the Docker networks:

> # docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1df2e889a811        bridge              bridge              local
963724a94ad6        host                host                local
d52def2c22c0        none                null                local

... and inspect the bridge network:

> # docker network inspect bridge 
[
    {
        "Name": "bridge",
        "Id": "1df2e889a8116d1ed9cf91be978738b069314f192df65adc804c7198a0ab8e09",
        "Created": "2018-08-26T14:43:31.858793924Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "e561e24d5da98fe2a6a7ad24e6c2fb46d10663bb2a3de4ac23893089fb41a0a1": {
                "Name": "builtapache",
                "EndpointID": "49b78e8dca9d7e1eaf6f92f2db157349371edb193fb11b5afe2c727cb35560f8",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "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": {}
    }
]


Using the host driver

The host driver can be particularly helpful when the application running inside the container needs to be reachable on ports which are not known a priori. With the host driver, the processes in the container are reachable on any port without any further mapping, as if they were running on the host machine.

As an example, consider running a nginx server listening on port 8080 in the same myapahce container:

> # docker run --net=host --name=apache_nginx -dt myapache:dockerfile
1a137db89b2277a30c8c51ba33b00508c614e495ed9ff3181cbe0f26b2d86d26

Then, install nginx in the container and configure it to listen on port 8080:

> # docker exec -it apache_nginx bash

\ # yum -y install epel-release
\ # yum -y install nginx

\ # vi /etc/nginx/nginx.conf

Now, we can start nginx

\ # nginx

\ # ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       236  0.0  0.0 120792  2100 ?        Ss   15:27   0:00 nginx: master process nginx
nginx      237  0.0  0.0 121176  3108 ?        S    15:27   0:00  \_ nginx: worker process
nginx      238  0.0  0.0 121176  3108 ?        S    15:27   0:00  \_ nginx: worker process
root        11  0.0  0.0  11820  1936 pts/1    Ss   15:25   0:00 bash
root       239  0.0  0.0  51704  1700 pts/1    R+   15:27   0:00  \_ ps auxf
root         1  0.0  0.1 228248  5076 pts/0    Ss+  15:23   0:00 httpd -DFOREGROUND
apache       6  0.0  0.0 228248  2976 pts/0    S+   15:23   0:00 httpd -DFOREGROUND
apache       7  0.0  0.0 228248  2976 pts/0    S+   15:23   0:00 httpd -DFOREGROUND
apache       8  0.0  0.0 228248  2976 pts/0    S+   15:23   0:00 httpd -DFOREGROUND
apache       9  0.0  0.0 228248  2976 pts/0    S+   15:23   0:00 httpd -DFOREGROUND
apache      10  0.0  0.0 228248  2976 pts/0    S+   15:23   0:00 httpd -DFOREGROUND

... and verify from the host that the processes are listening on different ports using the host network interface:

> # netstat -ltpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      236/nginx: master p 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp        0      0 10.10.0.12:10010        0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::111                  :::*                    LISTEN      -                   
tcp6       0      0 :::8080                 :::*                    LISTEN      236/nginx: master p 
tcp6       0      0 :::80                   :::*                    LISTEN      1/httpd             
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      -

We can now open a browser and reach nginx on port 8080 without enforcing additional port mappings.


Warning: As there is no isolation between the container and the host network, it is not possible to run more than one service listening on the same port at the same time. For example, it is not possible to have Apache listening on port 80 in the container running together with a nginx server listening again on port 80 on the host. This introduces several limitations when coming to the deployment of containers and might introduce security issues if applications in the container are not properly secured.


Define custom networks

It is also possible to create custom networks using, e.g., the bridge driver.

First, let's list the available networks:

> # docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1df2e889a811        bridge              bridge              local
963724a94ad6        host                host                local
d52def2c22c0        none                null                local

Second, create a custom network named mynet with the default bridge driver:

> # docker network create mynet --driver bridge --subnet 10.0.0.0/24
48b565896469eeaacce09849feece0a48248c313d7a536a1d39461a72b3020dd

> # docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "48b565896469eeaacce09849feece0a48248c313d7a536a1d39461a72b3020dd",
        "Created": "2018-08-26T14:57:37.103058968Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "10.0.0.0/24"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

Now, let's start two myapache containers with name apache1 and apache2, both attached to mynet network:

> # docker run --name=apache1 --net=mynet -dt myapache:dockerfile
005d4e94f2754e5927f1507270e079e41202b6cb3ae18bebb6f4eceb18fc82c1
> # docker run --name=apache2 --net=mynet -dt myapache:dockerfile
f38dfb1febbcd0e4b52c390ed0f57c243ed3e42b8758c9f53a75a8206c9b6557

There you have the two containers attached to the network:

> # docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "48b565896469eeaacce09849feece0a48248c313d7a536a1d39461a72b3020dd",
        "Created": "2018-08-26T14:57:37.103058968Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "10.0.0.0/24"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "005d4e94f2754e5927f1507270e079e41202b6cb3ae18bebb6f4eceb18fc82c1": {
                "Name": "apache1",
                "EndpointID": "9203b33ad76b9b3c6f50ffd9650bfefa0c59997ba801e380465be8ef13ed8ad6",
                "MacAddress": "02:42:0a:00:00:02",
                "IPv4Address": "10.0.0.2/24",
                "IPv6Address": ""
            },
            "f38dfb1febbcd0e4b52c390ed0f57c243ed3e42b8758c9f53a75a8206c9b6557": {
                "Name": "apache2",
                "EndpointID": "131dfe4e121384fd3c60c32089e73e020c6bc70a462245c6c92f9d38b39229b8",
                "MacAddress": "02:42:0a:00:00:03",
                "IPv4Address": "10.0.0.3/24",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]


One of the biggest advantages of creating custom networks and attaching containers to them is that Docker provides automatic name-to-IP resolution. For example, from apache1 we can ping apache2:

> # docker exec apache1 ping -c 5 apache2
PING apache2 (10.0.0.3) 56(84) bytes of data.
64 bytes from apache2.mynet (10.0.0.3): icmp_seq=1 ttl=64 time=0.068 ms
64 bytes from apache2.mynet (10.0.0.3): icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from apache2.mynet (10.0.0.3): icmp_seq=3 ttl=64 time=0.093 ms
64 bytes from apache2.mynet (10.0.0.3): icmp_seq=4 ttl=64 time=0.029 ms
64 bytes from apache2.mynet (10.0.0.3): icmp_seq=5 ttl=64 time=0.105 ms

--- apache2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4000ms
rtt min/avg/max/mdev = 0.029/0.066/0.105/0.030 ms


But names are not resolved into IP addresses for containers attached to any other network, e.g., the default system-wide bridge network:

> # docker run --name=apache_default -dt myapache:dockerfile
71f5e540edaacca942011d041f4939e76a23d1c690ac783bf4c3c85042b96f5a

> # docker exec apache1 ping -c 5 apache_default
ping: apache_default: Name or service not known


More details at: https://docs.docker.com/network/network-tutorial-standalone/

 PreviousNext