Firewalld и Docker на Rocky Linux / AlmaLinux 9

После перехода с CentOS 7 на Rocky или AlmaLinux 9 типичная проблема: «контейнер слушает порт, а снаружи не открывается» или наоборот — «порт торчит мимо firewalld». Docker сам правит цепочки netfilter, и без понимания правил легко получить дыру или «глухой» сервис.

Как это устроено

  • firewalld управляет зонами и политиками на хосте.
  • Docker добавляет свои цепочки (DOCKER, DOCKER-USER и др.) и часто публикует -p 8080:80 так, что трафик проходит DNAT в обход ожидаемых правил зоны.

Практичный подход: держать firewalld включённым, ограничивать публикацию портов и фильтровать вход через цепочку DOCKER-USER.

1. Проверка базового состояния

systemctl status firewalld --no-pager
firewall-cmd --state
firewall-cmd --get-active-zones
docker info | grep -i firewall

2. Не публикуйте порты «в мир» без нужды

Если сервис идёт через Traefik во внутренней сети Docker, не делайте -p 80:80 на сам сервис. Достаточно сети traefik_network и ярлыков Traefik — тогда наружу торчит только 80/443 хоста (или только Traefik).

Для сервисов, которым всё же нужен прямой порт:

# пример: открыть 8080/tcp в зоне public
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload

3. Фильтр через DOCKER-USER

Цепочка DOCKER-USER обрабатывается до правил Docker. Удобно запретить доступ к опубликованным портам отовсюду, кроме доверенной подсети:

# разрешить только LAN 192.168.100.0/24 к любому published-порту Docker
iptables -I DOCKER-USER -i eth0 -s 192.168.100.0/24 -j RETURN
iptables -I DOCKER-USER -i eth0 -j DROP

Правила iptables после перезагрузки пропадут. Для постоянства сохраните их через свой привычный способ (скрипт в systemd, iptables-save, или интеграцию с firewalld). На RHEL-подобных часто делают unit, который применяет правила после docker.service.

Пример unit:

cat > /etc/systemd/system/docker-user-firewall.service << 'EOF'
[Unit]
Description=DOCKER-USER firewall rules
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/usr/sbin/iptables -C DOCKER-USER -i eth0 -s 192.168.100.0/24 -j RETURN || /usr/sbin/iptables -I DOCKER-USER -i eth0 -s 192.168.100.0/24 -j RETURN
ExecStart=/usr/sbin/iptables -C DOCKER-USER -i eth0 -j DROP || /usr/sbin/iptables -A DOCKER-USER -i eth0 -j DROP
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now docker-user-firewall.service

Подставьте свой интерфейс (ip -br a) и подсеть.

4. Если Docker «ломает» firewalld после рестарта

Варианты:

  1. Перезагрузить firewalld после Docker: systemctl restart firewalld (иногда помогает, но это костыль).
  2. Минимизировать -p и ходить только через reverse proxy.
  3. Для жёсткого контроля — iptables: false в /etc/docker/daemon.json и ручной проброс (сложнее, только если понимаете последствия).

5. Быстрая проверка снаружи

С другой машины:

nmap -Pn -p 22,80,443,8080 YOUR_SERVER_IP

Должны быть открыты только ожидаемые порты.

Итог

На Alma/Rocky 9 связка firewalld + Docker живёт спокойно, если:

  • сервисы прячете за Traefik без лишних -p;
  • нужные порты явно открываете в firewalld;
  • чувствительные published-порты режете в DOCKER-USER.

Так домашний сервер остаётся доступным для себя и менее «шумным» для интернета.