Docker: Finding Container That Binds A Port In The Host Network Mode

Docker: Finding Container That Binds A Port In The Host Network Mode

During deployment, the other day, running an HTTP server application in a Docker container in the host network mode kept resulting in an “Address already in use” error. I knew which port was in the conflict but couldn’t tell which container already bound that port. I was already running quite a set of containers and didn’t want to stop them one by one. I figured out how to quickly identify the conflicting container and would like to share this knowledge with whoever finds it useful.

OS Error (48) Address already in use

If you ever struggled with “Address already in use” being shown in your console and weren’t able to find out what other process already binds that port, then you are probably not aware of the lsof command:

Lsof revision N lists on its standard output file information about files opened by processes for the following UNIX dialects

Consider the following example:

# lsof -Pi TCP:8080
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
python  25676 root    3u  IPv4 39156234      0t0  TCP *:8080 (LISTEN)

From the output, we can tell that port TCP:8080 is bound by the process with id 25676 that is running command python.

Docker network_mode: “host”

Docker allows processes within containers to bind to the host’s network interfaces using the host network mode.

Let’s create a simple Dockerfile:

FROM python:latest
CMD python -m http.server 0

And a simple Compose file:

version: "3.9"
services:
  http-server:
    build: .
    network_mode: host

Running the following command will spawn 5 instances of a python-based HTTP server each of which will bind to and listen at a random system-allocated dynamic port:

# docker-compose up --scale http-server=5 -d

Now let’s run lsof (still on the host machine) to see what ports these are:

# lsof -i
COMMAND     PID            USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
...
python    26731            root    3u  IPv4 39179287      0t0  TCP *:40447 (LISTEN)
python    26733            root    3u  IPv4 39176617      0t0  TCP *:39271 (LISTEN)
python    26739            root    3u  IPv4 39171825      0t0  TCP *:42439 (LISTEN)
python    26760            root    3u  IPv4 39174565      0t0  TCP *:36945 (LISTEN)
python    26761            root    3u  IPv4 39176031      0t0  TCP *:36787 (LISTEN)

Notice that in the output there is no sign of the actual docker containers running these processes.

“docker ps” doesn’t show the ports

Let’s list the running containers first using the docker ps command:

# docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED          STATUS         PORTS     NAMES
9748dde410b0   root_http-server   "/bin/sh -c 'python …"   10 minutes ago   Up 9 minutes             root_http-server_4
18a40f0e9ddf   root_http-server   "/bin/sh -c 'python …"   10 minutes ago   Up 9 minutes             root_http-server_5
666b6eb1a0e8   root_http-server   "/bin/sh -c 'python …"   10 minutes ago   Up 9 minutes             root_http-server_1
e85a079e75d6   root_http-server   "/bin/sh -c 'python …"   10 minutes ago   Up 9 minutes             root_http-server_2
c7f7db8d2d3f   root_http-server   "/bin/sh -c 'python …"   10 minutes ago   Up 9 minutes             root_http-server_3

Notice that each container has a NAME and a CONTAINER ID, but there is no information regarding the port that the processes bind to. That’s specific for the host network mode.

Finding out the container to blame

Let us say that we want to find the container that binds to TCP/36787. From the lsof output above we know the process id (PID) is 26761.

To find the ID of the container, that spawns a subprocess with PID 26761, we will use the ps command with the --forest (or -f) flag that will print the “ASCII art process tree”. We will also use the grep command so that only relevant lines from the output are displayed (that is two lines before the searched string):

# ps axf | grep -B 2 26761
  ...
--
  26658 ?        Sl     0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 18a40f0e9ddffb6d4f6caf33eb13a9e4a047cf44114c775e1e75ffc5f445ce89 -address /run/containerd/containerd.sock
  26697 ?        Ss     0:00  \_ /bin/sh -c python -m http.server 0
  26761 ?        S      0:00      \_ python -m http.server 0

Voilà!!! We can see that the container with CONTAINER ID that starts with 18a40f0e9ddf is the one that spawns subprocess with PID 26761, and thus the container we’re looking for is named root_http-server_5

Conclusion

This post describes how to find a Docker container in the host network mode that binds to a particular port using a combination of lsof -i, docker ps, and ps axf commands.