**Про пользователя:** Ubuntu при установке создаёт непривилегированного пользователя с sudo. Если устанавливал с таким пользователем - используй его. Если получил root-only сервер (облако, VPS) - создадим пользователя в разделе SSH.
Все команды выполняются с `sudo` если не указано иное. Если работаешь от root - `sudo` можно опустить.
> [!info] ВАЖНО
> Все команды выполняются с `sudo` если не указано иное. Если работаешь от root - `sudo` можно опустить.
В этой статье - Docker и всё что нужно чтобы контейнеры не стали дырой в безопасности хоста.
**Что получишь на выходе:**
- Docker установлен из официального репозитория (не snap)
- daemon.json настроен безопасно - без userns-remap и icc:false по умолчанию
- UFW и Docker работают вместе
- systemd-resolved и DNS в контейнерах - рабочее решение
- docker.sock - понимание риска и контроль доступа
- Контейнеры изолированы по сетям, ограничены по ресурсам
- Образы проверяются на уязвимости
## Исходные данные
- Ubuntu 24.04 LTS с выполненным базовым hardening
- Минимум 2GB RAM, 20GB диска
- Пользователь с sudo
Проверь версию:
```bash
lsb_release -a
```
```
Distributor ID: Ubuntu
Description: Ubuntu 24.04.4 LTS
Release: 24.04
Codename: noble
```
> [!info] ВАЖНО!
> Все команды выполняются с `sudo` если не указано иное.
> Если работаешь от root - `sudo` можно опустить.
## Установка Docker
> [!danger] ОЧЕНЬ ВАЖНО!
> Не используй snap версию в production!
В Ubuntu можно поставить Docker через snap (`sudo snap install docker`).
Snap-версия Docker работает в собственном confinement-окружении со своими путями (`/var/snap/docker/`), хуже интегрируется с системным `iptables`/`UFW`, и обновляется по графику snap, а не когда нужно тебе. Используй официальный репозиторий Docker - там стандартный путь `/var/lib/docker`, предсказуемое поведение, полный контроль над версией.
Выйди и зайди снова (Выполни `newgrp docker` для текущей сессии без перелогина). Проверь:
```bash
docker run --rm hello-world
```
## docker.sock - главный риск
Прежде чем настраивать daemon - разберёмся с самым опасным местом.
`/var/run/docker.sock` - Unix сокет Docker daemon. Любой процесс с доступом к нему имеет **полный root на хосте**. Через один вызов API можно смонтировать корень хоста и получить туда доступ:
```bash
# Пример атаки - одна команда и ты root на хосте
docker run --rm -v /:/host alpine chroot /host
```
**Правила работы с docker.sock:**
> [!info] ВАЖНО
> Никогда не монтируй docker.sock в контейнер без явной необходимости.
Если сервис требует его (Portainer, Watchtower, Traefik с автообнаружением) - этот сервис имеет root-доступ к хосту.
`log-driver` + лимиты - без них логи контейнеров забивают `/var/lib/docker/containers/` за дни на активном сервисе.
`live-restore: true` - контейнеры продолжают работать пока daemon перезапускается.
`userland-proxy: false` - iptables вместо userland proxy, быстрее, меньше процессов.
`no-new-privileges: true` - контейнеры не поднимают привилегии через setuid/setgid бинарники.
`storage-driver: overlay2` - зафиксирован явно, хотя на Ubuntu 24.04 он и так используется по умолчанию.
### Почему нет userns-remap
Root в контейнере мапится на непривилегированный UID на хосте (например 100000). Звучит безопасно, но ломает bind mounts: файлы на хосте принадлежат UID 1000, контейнер видит UID 100000 и получает `Permission denied`. Каждый сервис с записью на хост требует `chown -R 100000:100000`, и пересчёт нужен для каждого нового сервиса.
> [!info] ВАЖНО
> Включай только если осознанно готов управлять UID маппингом для каждого тома.
### Почему нет icc:false
Глобальный `icc: false` ломает общение контейнеров внутри одного docker-compose стека - Compose создаёт свою bridge сеть, и без `icc` контейнеры в ней не видят друг друга без явных `--link`.
Правильная изоляция - через отдельные сети Docker, не через глобальный флаг daemon. Раздел ниже.
Примени:
```bash
sudo systemctl restart docker
```
Проверь:
```bash
docker info | grep -E "Storage Driver|Logging Driver|live-restore"
```
## sysctl для Docker-хоста
```bash
sudo nano /etc/sysctl.d/99-docker.conf
```
```
# IP forwarding - обязательно для Docker
net.ipv4.ip_forward = 1
# IPv6 forwarding - только если используешь IPv6 в контейнерах
# net.ipv6.conf.all.forwarding = 1
# Лимиты inotify
fs.inotify.max_user_instances = 512
fs.inotify.max_user_watches = 524288
# Для Java приложений (Elasticsearch и подобных) в контейнерах
vm.max_map_count = 262144
```
```bash
sudo sysctl -p /etc/sysctl.d/99-docker.conf
```
## systemd-resolved и DNS в контейнерах
Это самая частая проблема на Ubuntu специфично.
Ubuntu 24.04 использует `systemd-resolved` - DNS резолвер слушает на `127.0.0.53`. Контейнер не может достать этот адрес - `127.0.0.53` это loopback хоста, контейнер находится в своём network namespace и видит свой собственный loopback, не хостовый.
Симптом:
```bash
docker run --rm alpine nslookup google.com
# ;; connection timed out; no servers could be reached
```
**Решение 1 (быстрое, для отдельных контейнеров):**
```bash
docker run --dns 8.8.8.8 --dns 8.8.4.4 alpine nslookup google.com
```
**Решение 2 (для всех контейнеров через daemon.json):**
```bash
sudo nano /etc/docker/daemon.json
```
Добавь:
```json
{
"dns":["8.8.8.8","8.8.4.4"]
}
```
```bash
sudo systemctl restart docker
```
**Решение 3 (если нужен внутренний DNS, не публичный):**
Узнай реальный upstream DNS на хосте:
```bash
resolvectl status | grep "DNS Server"
```
Используй именно его IP (не `127.0.0.53`) в `daemon.json`.
Docker Compose поддерживает то же самое на уровне сервиса:
```yaml
services:
app:
dns:
- 8.8.8.8
- 8.8.4.4
```
## UFW + Docker интеграция
Та же проблема что и на любом дистрибутиве - Docker пишет свои iptables правила напрямую, обходя UFW.
**Подход 1 (рекомендуемый): порты только на localhost**
```bash
# Плохо - обходит UFW
docker run -p 80:80 nginx
# Хорошо
docker run -p 127.0.0.1:80:80 nginx
```
Reverse proxy на хосте слушает 0.0.0.0:443, UFW разрешает только 443, контейнеры недоступны напрямую.
**Подход 2: DOCKER-USER chain**
```bash
sudo nano /etc/ufw/after.rules
```
Добавь в конец:
```
# BEGIN UFW AND DOCKER
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -s 10.0.0.0/8 -j RETURN
-A DOCKER-USER -s 172.16.0.0/12 -j RETURN
-A DOCKER-USER -s 192.168.0.0/16 -j RETURN
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
```
```bash
sudo ufw reload
```
Для большинства случаев достаточно **подхода 1**.
## Сетевая изоляция контейнеров
Вместо глобального `icc: false` - отдельные сети под каждое приложение:
```bash
docker network create --driver bridge app_network
docker run -d --name app --network app_network myapp
docker run -d --name db --network app_network postgres
```
Контейнеры из разных сетей не видят друг друга. В docker-compose сети создаются автоматически для каждого стека - это и есть рабочая изоляция.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.