Совет из статьи на
docker переводится так: «
redis не должен слушать ничего, что смотрит наружу». В
compose у этого правила три уровня, и выбирать стоит по тому, кто на самом деле ходит в
redis.
Схема словами:
1. Внутри
compose:
api →
redis:6379 по имени сервиса через общую сеть проекта. Для этого ports вообще не нужен, контейнеры видят друг друга и без него. Ты это уже нащупал, когда поменял REDIS_HOST.
2. С хоста руками: ты →
redis-
cli. Тоже без ports:
docker compose exec redis redis-cli ping
Заходишь в сам контейнер, никакой порт на хосте не открыт.
3. С хоста программой: какой-нибудь скрипт по
cron или экспортер метрик, который запущен не в
docker и обязан стучаться в
TCP. Только тут нужен ports, и только с адресом:
ports:
- "127.0.0.1:6379:6379"
Главная ловушка в варианте без адреса. Запись "6379:6379" публикует порт на всех интерфейсах, а
docker сам дописывает свои правила
iptables.
Ufw их не видит: в
ufw status висит «deny incoming», но пакет до контейнера доходит раньше, чем до правил
ufw. Поэтому «у меня же фаервол» здесь не аргумент. Проверять стоит не конфиг, а то, что реально слушает:
ss -tlnp | grep 6379
Если видишь
0.0.0.0:6379 или [::]:6379 — порт открыт миру, что бы ни говорил
ufw. Если
127.0.0.1:6379 — только локально. Если строки нет совсем — порт не опубликован, и это нормально для первых двух сценариев.
Теперь кусок жизни, чтобы было понятно, откуда у меня эта паранойя. Сейчас собираю шлюз на litellm: один
endpoint, за ним несколько моделей, у каждого сервиса свой виртуальный ключ и свой лимит.
Redis там держит кэш ответов и счётчики
rate limit. Сижу сегодня вечером, проверяю, почему один ключ упирается в лимит раньше времени. Хочу посмотреть счётчики глазами и на автомате дописываю в
compose "6379:6379", потому что лень набирать exec. Перезапускаю, смотрю в
redis-
cli, нахожу свою ошибку в конфиге лимитов. А потом ради порядка делаю ss -tlnp и вижу [::]:6379. На сервере, где в
redis лежат кэшированные ответы моделей и, по сути, карта того, какой ключ сколько тратит. Пароля на нём не было, потому что «он же внутренний». Порт провисел минут двадцать. Вряд ли кто-то успел, но проверять «вряд ли» мне нечем, так что убрал строку и сделал выводы.
Отсюда то, что я теперь ставлю по умолчанию:
— у
redis нет ports вовсе. Если очень надо с хоста, то только
127.0.0.1, и закомментировано, пока не понадобится;
— отдельная сеть для бэкенда с internal: true. Тогда у
redis нет даже исходящего выхода наружу, а
api подключён к двум сетям: внутренней к
redis и обычной для внешних запросов. Получается
api ↔
redis во внутренней сети,
api ↔ мир через обычную,
redis ↔ мир никак;
— requirepass всё равно задаю, даже если порт закрыт. Это не замена закрытому порту, а второй слой на случай, если кто-то однажды так же лениво допишет ports. Насколько помню, в официальном образе
redis protected-mode выключен, так что на него как на защиту рассчитывать не стоит. Если ошибаюсь, поправьте, но лучше исходить из того, что его нет;
— пароль кладу в
.env, который не попадает в
git, а в
api передаю REDIS_URL целиком.
Так что на твой вопрос «или-или» ответ такой: не писать ports — по умолчанию,
127.0.0.1:6379:6379 — когда есть конкретный клиент на хосте, который не умеет в exec. А голое 6379:6379 на сервере с публичным адресом не нужно никогда, даже «на пять минут посмотреть». Пять минут у меня сегодня как раз превратились в двадцать.