Docker і Docker Compose на VPS — і пастка з файрволом
Майже будь-який самохостинг сьогодні постачається у вигляді docker-compose.yml. Встановити Docker на VPS — одна команда. А щоб він працював безпечно, треба знати ще одну річ, яку пропускають майже всі інструкції: опубліковані порти Docker обходять ufw.
Який тариф
Сам Docker легкий, а от те, що ви в ньому запускаєте, — ні. Орієнтир такий: один застосунок із базою вміщується на Pulse (2 vCPU, 2 ГБ), зв’язка з трьох-п’яти сервісів — на Drive (4 ГБ, $15 на місяць) або Forge (8 ГБ, $22 на місяць). Образи й томи непомітно ростуть, тож стежте не лише за пам’яттю, а й за диском.
1. Встановлення
Офіційний скрипт встановлює Docker Engine і плагін Compose з репозиторію Docker:
curl -fsSL https://get.docker.com | sh
docker run --rm hello-world
docker compose version
Працюєте під звичайним користувачем — виконайте usermod -aG docker користувач і перелогіньтеся. Пам’ятайте: членство в групі docker по суті дорівнює root на сервері.
2. Справжній стек
/opt/app/docker-compose.yml: застосунок і PostgreSQL. Застосунок опубліковано лише на localhost — до нього проксуватиме nginx на самому сервері.
services:
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db:/var/lib/postgresql/data
app:
image: ghcr.io/example/app:latest
restart: unless-stopped
depends_on: [db]
environment:
DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db:5432/postgres
ports:
- "127.0.0.1:8080:8080"
volumes:
db:
cd /opt/app && echo 'DB_PASSWORD=змініть-пароль' > .env && chmod 600 .env
docker compose up -d
docker compose ps
docker compose logs -f app
restart: unless-stopped піднімає контейнери після падіння та після перезавантаження сервера.
3. Пастка з файрволом
На цьому щотижня хтось потрапляє. Ви вмикаєте ufw, дозволяєте лише SSH і HTTPS, публікуєте контейнер із ports: - "5432:5432" — і база відкрита всьому інтернету. Docker записує власні правила iptables, і вони спрацьовують раніше за правила ufw.
Дві звички рятують від цього:
- Публікуйте на localhost (
127.0.0.1:8080:8080) усе, до чого мають звертатися лише nginx або ви через SSH-тунель. - Порти баз даних не публікуйте взагалі. Контейнери одного compose-проєкту бачать одне одного за іменем сервісу (
db:5432у прикладі вище) без жоднихports:.
Перевірте ззовні, що насправді відкрито: nmap -Pn <IP сервера> зі свого комп’ютера.
4. Логи й диск
За замовчуванням Docker зберігає логи контейнерів вічно, і балакучий застосунок може заповнити диск за кілька тижнів. Обмежте їх у /etc/docker/daemon.json:
{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }
systemctl restart docker
docker system df # що займає місце
docker image prune -a # видалити невикористовувані образи
Обмеження діє для контейнерів, створених після зміни, тому перестворіть свої: docker compose up -d --force-recreate.
Чого Docker за вас не зробить
Не збереже томи. docker compose down -v видаляє їх, як і необережний prune. Для PostgreSQL мінімум — нічний дамп docker compose exec -T db pg_dump -U postgres postgres > dump.sql з копіюванням за межі сервера.