Внутри минималистичного Docker-образа не оказалось ни curl, ни wget, ни других утилит для работы с сетью. А проверить health соседнего контейнера по HTTP нужно было прямо сейчас. Решение оказалось встроенным прямо в bash.
bash умеет открывать TCP-сокет через специальный синтаксис перенаправления. Вот как это работает: exec 3<>/dev/tcp/service/8642 открывает соединение с хостом service на порту 8642 и привязывает его к файловому дескриптору 3. Затем туда отправляется сырой HTTP-запрос через printf, а ответ читается командой cat <&3. Весь обмен идёт без внешних программ. service в примере — это DNS-имя, которое должно резолвиться внутри сети Docker.
Важный нюанс: /dev/tcp — не настоящий файл на диске. ls /dev/tcp ничего не покажет, а cat из другого шелла вызовет ошибку. Это внутренняя фича самого bash: он перехватывает такую конструкцию, сам делает DNS-запрос и системный вызов connect(2), а потом отдаёт сокет как обычный файловый дескриптор.
Но есть куча ограничений. Это не HTTP-клиент. bash не умеет парсить ответы, обрабатывать редиректы, сжатие, chunked encoding, TLS или автоматические повторы. Для HTTPS понадобится openssl s_client, что сводит на нет весь смысл трюка. Без заголовка Connection: close сервер (по умолчанию в HTTP/1.1) оставит соединение открытым, и cat будет висеть вечно, ожидая байты, которые не придут. Отправлять Connection: close в запросе — обязательно, либо оборачивать вызов в timeout.
Это фича именно bash, её нет в POSIX. dash (стандартный /bin/sh на Debian) и zsh её не поддерживают. Более того, это опция компиляции: --enable-net-redirections. В большинстве современных сборок она включена, но в старых версиях Debian её долгое время отключали. На минимальных системах лучше проверить заранее.
Для повседневной работы curl всё равно остаётся правильным инструментом. Но внутри обрезанного контейнера, где нельзя ничего доустановить, этот трюк позволяет быстро проверить доступность сервиса без установки пакетов.