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 с копированием за пределы сервера.