Today I'd thought I'd write up a short article on how to have a process running in a Docker container wait for a server process running in another Docker container.
Docker is a containerization platform which, building on Linux Containers, provides OS-level virtualization. Docker Compose is a part of the Docker software suite which allows developers to initialize and start up multiple containers and link them together.
It's often the case that there is a process running in a Docker container which depends on a server process running in a different Docker container.
For example, consider a simple web application which connects to a database. Often times the web application process will run in its own container and will require that there be a database container listening for TCP connections on a certain port.
If the database isn't listening on a given port yet then the web application might error out. When might this happen? With a Docker container running a database this might occur because we are initializing the database (setting it up, initializing the DBs, creating tables, inserting records, etc).
Docker compose has 'link' directives so we can specify that a given container depends on another container, but there's no way to specify that we have a dependency for a process which is bound to a certain port.
To handle this possibility we would need to add code to our web application container to check to see if the database process is listening on that port. This could be done in any number of ways. For instance, if the web application is a Python process (running Flask let's say), then we could add Python code which performs this check. We could also install programs like Nmap inside our container to perform the check before we begin the web application process.
Well, there's another way friends. Enter this simple (albeit kludgy) bash script:
#!/bin/bash exec 3>&2 # store stderr's file in fd 3 exec 2>/dev/null # make it so stderr writes to /dev/null while ! exec 4<>/dev/tcp/$1/$2 # attempt to open socket do sleep 1 # if process not bound, sleep for 1 second done exec 2>&3 # point stderr back to the original file
If you save this file as 'wait-for.sh', you can invoke the script like so:
./wait-for.sh $db_host $db_port
How does it work? What is this wizardry?
Linux has a pseudo-device file located at /dev/tcp (additionally there's a /dev/udp pseudo-device file as well). Similarly to how /proc is a pseudo-file system which provides process information for the system, /dev/tcp allows you to open files which can be used to open TCP connections.
As an example of what you can do with /dev/tcp, try this:
exec 3<>/dev/tcp/www.google.com/80 # open file for reading and writing echo -e 'GET / HTTP/1.1\n' >&3 # write HTTP request to file cat <&3 # read HTTP response from file exec 3>&- # close file
You should see an HTTP response from Google!
In the wait-for.sh script we attempt to open a /dev/tcp file (with file descriptor 4) for both reading and writing (because we use <>). If the server process is not listening, then the statement will fail and the body of the while loop will execute which puts the process to sleep for 1 second. After the 1 second is up, the script will attempt to open the file again.
This is a cool hack but shouldn't really be depended on; if you're experiencing an issue like this in production do things the correct way without hacks like this!