Привіт! Я Kernelka 🐳. Якщо ви запускаєте сервіси у Docker на Linux, то знаєте, що зручність контейнерів не скасовує потребу в безпеці. Сьогодні налаштуємо три мегакорисні щити: user namespaces, профіль seccomp і read‑only файлову систему. Це мінімізує права, обмежить системні виклики ядра та закриє непотрібний запис у файловій системі. Все робимо практично — через термінал Linux, без зайвої магії.
Навіщо це потрібно
Контейнер — не повноцінна віртуальна машина. Він ділить ядро з хостом. Якщо процес у контейнері отримує забагато прав або має доступ до небезпечних системних викликів, компрометація може торкнутися хоста. Тому ми:
- ізолюємо UID/GID процесів контейнера через user namespaces, щоб root всередині був «нікому» зовні;
- обмежуємо системні виклики через seccomp;
- вмикаємо read-only файлову систему контейнера, дозволяючи запис лише там, де це потрібно.
Ці кроки чудово вписуються у сучасну контейнеризацію і конкретно підсилюють права доступу Linux для процесів у контейнерах.
Покрокове налаштування
Передумови
- Встановлений Docker Engine (root або sudo).
- Доступ до системних файлів конфігурації та логів.
- Базові навички роботи з терміналом Linux.
1) Вмикаємо user namespaces (userns-remap)
User namespaces змінюють відображення користувачів контейнера на хості. Навіть якщо процес у контейнері — root, на хості він перетворюється на «звичайного» користувача з UID із піддіапазону.
# Увімкнути userns-remap з дефолтним користувачем dockremap
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"userns-remap": "default"
}
EOF
# Перезапустити Docker
sudo systemctl daemon-reexec || sudo systemctl daemon-reload
sudo systemctl restart docker
# Перевірити, що працює user namespaces
docker info | grep -i userns || true
# Подивитися відображення UID/GID для dockremap
getent passwd dockremap || true
getent group dockremap || true
cat /etc/subuid | grep dockremap || true
cat /etc/subgid | grep dockremap || true
# Перевірка в контейнері (root всередині не дорівнює root на хості)
docker run --rm alpine sh -c 'id -u; id -g'
Якщо ви мапите томи на хост, для запису контейнера потрібно виставити власника з піддіапазону UID/GID dockremap (див. нижче у FAQ).
2) Жорсткіший seccomp-профіль
За замовчуванням Docker вже використовує seccomp, але ми можемо зробити профіль суворішим. Спочатку заберемо «дефолт» і збережемо:
# Завантажити стандартний профіль seccomp від Moby
sudo curl -fsSL \
https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json \
-o /etc/docker/seccomp-default.json
# Створити більш суворий варіант (лише приклад; адаптуйте під вашу програму)
sudo tee /etc/docker/seccomp-restrict.json > /dev/null <<'EOF'
{
"defaultAction": "SCMP_ACT_ERRNO",
"archRuntimes": [],
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
"syscalls": [
{"names": ["read", "write", "close", "fstat", "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "ioctl", "lseek", "getpid", "gettid", "clone", "set_tid_address", "nanosleep", "clock_gettime", "socket", "connect", "accept4", "bind", "listen", "recvfrom", "sendto", "shutdown", "getsockname", "getsockopt", "setsockopt"], "action": "SCMP_ACT_ALLOW"}
]
}
EOF
# Запустити контейнер з кастомним профілем
docker run --rm -it \
--security-opt seccomp=/etc/docker/seccomp-restrict.json \
alpine:latest sh -c 'echo ok && sleep 1'
Порада: починайте з /etc/docker/seccomp-default.json і по кроку забороняйте зайві виклики (наприклад, bpf, keyctl, perf_event_open), тестуйте сервіс на працездатність.
3) Read‑only файлові системи + винятки
Забороняємо запис у контейнері загалом і дозволяємо лише те, що потрібно додатку: тимчасові каталоги (tmpfs) і дані (томи).
# Приклад: веб-додаток потребує /tmp і /run, а дані пише у /data
# / є read-only, /tmp і /run — tmpfs, /data — окремий том
docker volume create app-data
docker run -d --name app \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m \
--tmpfs /run:rw,noexec,nosuid,nodev,size=16m \
-v app-data:/data:rw \
nginx:stable
Тут ми різко зменшуємо поверхню атаки: навіть RCE у контейнері не зможе бездумно писати у файлову систему.
4) Комбінований приклад (разом: userns + seccomp + read‑only)
docker run -d --name secure-svc \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m \
--tmpfs /run:rw,noexec,nosuid,nodev,size=16m \
--security-opt seccomp=/etc/docker/seccomp-restrict.json \
--security-opt no-new-privileges=true \
--cap-drop ALL --cap-add NET_BIND_SERVICE \
-p 8080:8080 \
myorg/myapp:latest
Compose-варіант (для CI/CD і зручності):
cat > docker-compose.yml <<'YAML'
services:
app:
image: myorg/myapp:latest
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=64m
- /run:rw,noexec,nosuid,nodev,size=16m
security_opt:
- seccomp:/etc/docker/seccomp-restrict.json
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
ports:
- "8080:8080"
volumes:
- app-data:/data:rw
volumes:
app-data:
YAML
docker compose up -d
Альтернативні способи
- Rootless Docker: запускає демон і контейнери без root на хості. Сумісно не з усіма сценаріями, але додає ще один шар безпеки.
- Podman: за замовчуванням бездемонний і добре працює в rootless-режимі, підтримує seccomp, user namespaces і read‑only так само.
- AppArmor/SELinux: доповнює seccomp політиками доступу до файлів і ресурсів ядра; корисно для продакшн-серверів.
GUI-спосіб (через Portainer)
Якщо вам зручніше клікати, у Portainer при створенні контейнера:
- У Security & host: увімкніть «Readonly container»;
- У Capability settings: натисніть «Drop all», потім додайте тільки потрібні capability;
- У Seccomp profile: виберіть «custom» і вкажіть шлях до вашого seccomp JSON;
- Додайте tmpfs для /tmp та /run, і окремий том для /data.
User namespaces конфігуруються на рівні демона Docker (див. крок 1) і діятимуть для всіх контейнерів.
FAQ
Контейнер не може писати у том після userns-remap. Чому?
Через відображення UID/GID. Перевірте діапазон у /etc/subuid та /etc/subgid для користувача dockremap (зазвичай починається з 165536). Зробіть том належним цьому UID/GID:
# Приклад: якщо у /etc/subuid є "dockremap:165536:65536"
sudo chown -R 165536:165536 /var/lib/docker/volumes/app-data/_data
Чи впливають seccomp і read-only на продуктивність?
Вплив мінімальний. Найчастіше обмеження відчутні лише, якщо ваш додаток потребує екзотичних системних викликів або часто пише у ФС (а ми це перенесли в томи/tmpfs).
Мій сервіс падає з EPERM після увімкнення seccomp. Що робити?
Перевірте логи контейнера, dmesg або auditd, щоб побачити, який syscall блокується. Тимчасово запустіть з дефолтним профілем, знайдіть потрібний виклик і додайте його в allowlist вашого профілю.
Як вимкнути user namespaces для одного контейнера?
Запустіть з опцією:
docker run --userns=host ...
У Compose використовуйте:
userns_mode: "host"
Як поєднати це з firewall?
Ці техніки доповнюють firewall, але не замінюють його. Обмежуйте мережу контейнера (наприклад, окремими мережами Docker), і контролюйте вхідні порти через iptables/nftables на хості.
Порада від Kernelka
Почніть із найсуворішого профілю та read‑only режиму, а далі додавайте мінімально необхідні права. Ведіть невеликий чеклист для сервісів: які capability справді потрібні, які каталоги мають бути writable, які syscalls має дозволити seccomp. Автоматизуйте перевірки у CI: тест-контейнери з вашим профілем seccomp та smoke‑тести. І так, робіть резервні копії томів — на випадок «ой» 🙂
Підсумок
- Увімкнули user namespaces — root у контейнері більше не root на хості.
- Застосували seccomp-профіль — обмежили системні виклики.
- Увімкнули read‑only ФС — запис лише там, де це потрібно.
- Додали no-new-privileges та мінімізували capability.
- Розглянули альтернативи: Rootless Docker, Podman, AppArmor/SELinux.
Тримайте свій Docker на Linux міцно захищеним — і спіть спокійно 🔒

Прокоментувати
На сайті відображається лише твоє ім'я та коментар. Електронна пошта зберігається виключно для зв'язку з тобою за потреби та в жодному разі не передається стороннім особам.