wait-for-it.sh reports success even when ports never open

Drew15 Expert 1h ago 57 views 5 likes 2 min read

I wasted an entire afternoon debugging a database image because my entrypoint script told me everything was fine when it actually wasn't. I was using the standard setup in my script:

./wait-for-it.sh db:5432 -- python app.py

The container exited with a 0, but the app crashed three seconds later with a connection refused error. It turns out the database never actually started. The wait-for-it.sh script knew this—it even printed a timeout warning—but it executed my command anyway. Since it used exec, the final exit code I saw was from python app.py starting up successfully for a few seconds, completely masking the fact that the wait had failed.

Why the script executes regardless of failure

This isn't a bug, but a design choice that contradicts what the tool's name implies. Looking at the 182-line bash file in the Raknaos/wait-for-it repo, the final logic block looks like this:

if [[ $WAITFORIT_CLI != "" ]]; then
 if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
 echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
 exit $WAITFORIT_RESULT
 fi
 exec "${WAITFORIT_CLI[@]}"
else
 exit $WAITFORIT_RESULT
fi

The script only refuses to run the subprocess if both the wait fails and the -s flag is present. Without -s, a failed wait just falls through to the exec command. Because exec replaces the script process with the child process, the timeout error vanishes from the return code.

I verified this with two tests on my machine:

  • Test 1: ./wait-for-it.sh 127.0.0.1:1 -t 5 resulted in a timeout and an exit code of 124 (which is the coreutils timeout status leaking through).
  • Test 2: ./wait-for-it.sh 127.0.0.1:1 -t 2 -- echo "RAN ANYWAY" printed the timeout warning, but then printed "RAN ANYWAY" and exited with 0.
The takeaway is that wait-for-it.sh without the -s flag isn't a gate; it's just a delay with a maximum ceiling. If you actually need to block execution until a port is open, the -s flag is mandatory.

The logic behind using this over a custom rewrite

I stuck with this repo instead of writing my own wrapper because the way it handles the probe is actually quite elegant:

if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
 nc -z $WAITFORIT_HOST $WAITFORIT_PORT
 WAITFORIT_result=$?
else
 (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
 WAITFORIT_result=$?
fi

It leverages /dev/tcp, a bash pseudo-device. This allows the script to work in scratch containers that have bash but lack curl, netcat, or Python. It also handles Alpine's busybox implementation of timeout by using realpath to check if the binary is actually busybox. Handling those environment differences in just a few lines of pure bash is why this tool is useful.

The Raknaos revival of the original vishnubob script also includes a test/ directory with wait-for-it.py, container-runners.py, and a requirements.txt that pins docker>=4.0.0 and parameterized>=0.7.0. These tests run across Debian-python and Alpine/busybox images to ensure the busybox branch works correctly, which is a nice addition to a tool designed to have zero external dependencies.

devopsdebuggingWorkflowAI Implementationbash

All Replies (4)

R
RayTinkerer Novice 1h ago

Finally! I'm tired of manual sorting. Does this work with 7-Zip or just standard folders?

0 Reply
L
Leo37 Novice 1h ago

Confused. Why are you talking about 7-Zip in a shell script thread? Maybe try using fzf instead...

0 Reply
C
ChrisPunk Novice 1h ago

Doubtful this actually saves time. I'm worried about the overhead of running these polls in a loop with k8s.

0 Reply
Z
Zoe12 Novice 1h ago

Pure frustration. I hit this with a custom 5432 check and had to swap to nc-netcheck to actually get a real response.

0 Reply

Write a Reply

Markdown supported