PunchBruno Fernandes’ home burgled while in Italy for Como matchEl ComercioMaría Corina Machado redobla la presión en Venezuela: protestas, elecciones, presos políticos y su regreso al paísInquirerClasses suspended in Pagudpud due to Typhoon RosalRTP DesportoI Liga: Jornada abre em GuimarãesDaily MaverickClaude Monet’s ‘Iris’ becomes most expensive artwork sold at auction in France, Sotheby’s saysBollywood HungamaNo football, no gym, no restaurants? Tiger Shroff shooting raises a FRIGHTENING question: Are Bollywood stars becoming PRISONERS of their own fame?ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioRapplerRain eases in Batanes as Typhoon Rosal starts moving awayThe Jerusalem PostIsrael’s election is being cast as a historic showdown - but four trends tell a different storyVanguard2027: Adeleke backs Tinubu; maintains support for Accord legislative candidatesESPNCaleb Williams, Bears find just enough to hold off PatsRai NewsTG3 Fuori TG del 23/10/2026
The Daily Newsstand · Free, Always
Friday, October 23, 2026

Установка Docker на VPS: от пакета до первого контейнера

Translate

Установка Docker на VPS обычно заканчивается на hello-world: приветствие напечаталось, значит, всё работает. Для движка так и есть. Другие вопросы всплывают через месяц: какие порты сервер теперь отдаёт в интернет, куда пропали данные после обновления образа и почему у пользователя из группы docker фактически есть root. Их проще закрыть сразу, пока на сервере нет базы.

Ниже весь путь от чистого VPS с Ubuntu 24.04 LTS до веб-сервиса в Compose с отдельным томом. Нужны виртуальная машина, доступ по SSH и sudo. На Ubuntu 26.04 команды те же, отличается только кодовое имя в версиях пакетов. У Debian и других дистрибутивов свой репозиторий Docker, и смешивать инструкции не стоит.

Ставить Docker из официального репозитория или пакета ОС?

Для свежей установки берите официальный репозиторий Docker: Engine, CLI и плагины приходят из одного источника и обновляются вместе, а APT проверяет подпись ключом Docker. Пакет docker.io собирает сам Ubuntu, версии в нём обычно старше. На работающем сервере смену источника планируют отдельно.

Сначала выясните, что уже есть на сервере

Docker официально поддерживает Ubuntu 24.04 на amd64, arm64 и нескольких других архитектурах, но первым делом стоит проверить виртуализацию. Контейнеры работают на ядре самого VPS. На KVM ядро у машины своё, а при контейнерной виртуализации оно общее с хостом, и провайдер может отрезать нужные Docker возможности. Переустановкой Docker это не лечится.

Установку удобно сразу писать в журнал. Утилита script сохранит в файл все команды вместе с выводом, так что пароли и токены во время записи лучше не вводить. Закрывается запись командой exit, но это уже в самом конце.

mkdir -m 700 docker-check
cd docker-check
script -q -a install.typescript
date -Is
cat /etc/os-release
dpkg --print-architecture
uname -r
systemd-detect-virt
free -h
df -hT /
df -i /

Из вывода нужны свободная память, место на диске и запас inode, записей о файлах. Про inode часто забывают, а когда они кончаются, диск перестаёт принимать новые файлы при свободных гигабайтах. Образы и логи занимают место быстрее, чем кажется в день установки.

Перед тем как установить Docker на VPS из официального репозитория, проверьте, нет ли в системе пакетов, которые с ним конфликтуют. Удалять их стоит с пониманием, что на них держится: на узле Kubernetes, например, удаление containerd остановит всю его нагрузку.

dpkg-query -W -f='${binary:Package}\t${Status}\n' \
  docker.io docker-compose docker-compose-v2 docker-doc \
  docker-buildx podman-docker containerd runc

Ненайденный пакет – хороший знак, его просто нет. Если нашёлся docker.io, сначала посмотрите, что APT собирается удалить вместе с ним, и только потом удаляйте:

sudo apt -s remove docker.io
sudo apt remove docker.io

С остальными найденными пакетами то же самое. Образы, контейнеры и тома в /var/lib/docker при удалении пакетов остаются на диске.

Один репозиторий и проверенный ключ

Настройка Docker Engine на VPS начинается с репозитория. В официальной инструкции ключ скачивают сразу в /etc/apt/keyrings. Здесь он сначала сохраняется в рабочий каталог и попадает в систему только после сверки отпечатка.

sudo apt update
sudo apt install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o docker.asc
gpg --show-keys --fingerprint docker.asc

Отпечаток ключа Docker Release (CE deb) должен совпасть с этим:

9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88

Сверяйте все 40 символов: последние восемь, короткий ID, подделать гораздо проще. При несовпадении дальше не идите и сначала выясните по официальным материалам Docker, не сменился ли ключ.

После сверки ключ и описание репозитория в формате deb822 устанавливаются так:

sudo install -d -m 0755 /etc/apt/keyrings
sudo install -m 0644 docker.asc /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
apt-cache policy docker-ce docker-ce-cli

Signed-By разрешает ключу подписывать только этот репозиторий, на остальные источники он не влияет. В выводе apt-cache policy у Candidate должен стоять download.docker.com с компонентом stable и кодовым именем noble. Ошибку подписи разбирайте до конца: отключить проверку проще, но тогда ключ теряет смысл.

Пять пакетов для установки

Самая свежая версия Engine на 14 октября 2026 года – 29.9.0. Номер в команде зафиксирован, чтобы на втором сервере встало ровно то же самое. Перед установкой сверьтесь с apt-cache madison и при необходимости замените строку целиком, с префиксом 5: и суффиксом Ubuntu.

apt-cache madison docker-ce
docker_version='5:29.9.0-1~ubuntu.24.04~noble'
sudo apt install docker-ce="$docker_version" \
  docker-ce-cli="$docker_version" containerd.io \
  docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker.service containerd.service
sudo systemctl is-active docker
sudo systemctl is-enabled docker
sudo docker version
sudo docker compose version
dpkg-query -W docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

В docker-ce лежит сам Engine, сервер, которым управляет CLI из docker-ce-cli. containerd.io запускает контейнеры, Buildx собирает образы, а Compose поднимает сервисы по файлу. Compose здесь работает как плагин, и команда пишется через пробел: docker compose. Старый docker-compose с дефисом из пакетов Ubuntu ставить не нужно.

В выводе docker version ищите два блока, Client и Server. Если второго нет, CLI не достучался до сервера. Статус active значит, что служба работает, enabled – что она поднимется после перезагрузки. Первый контейнер на VPS запустим позже, сначала проверим сам сервер.

Как безопасно запускать Docker без root?

Для работы без root используйте rootless-режим: в нём и сервер Docker, и контейнеры запускаются от обычного пользователя. Группа docker эту задачу не решает: её участник управляет системным сервером, а тот работает от root. Rootless требует пользовательских пространств имён, а сеть и лимиты через cgroup в нём ограничены.

Что проверить в docker info

Установка Docker из репозитория закончена, и самое время сохранить параметры сервера и свежий журнал. Если через месяц что-то сломается, будет с чем сравнить.

sudo docker info
sudo docker info --format '{{.DriverStatus}}'
sudo docker system df
sudo journalctl -u docker -u containerd -b --no-pager -n 100

На чистой установке Engine 29 образы хранит containerd, а слои собирает snapshotter overlayfs: контейнер видит файловую систему из слоёв образа и пишет свои изменения поверх. После обновления со старой версии в docker info может остаться драйвер overlay2. Трогать его без причины не нужно, после смены хранилища прежние образы и контейнеры пропадут из вида.

У такой схемы есть последствие для дисков. Образы и слои лежат в /var/lib/containerd, а тома и прочие данные Docker в /var/lib/docker. Смена Docker Root Dir переносит только второй каталог, так что при разнесённом хранении место придётся смотреть на обоих.

Ещё в docker info стоит найти Cgroup Driver и Cgroup Version: от них зависят лимиты CPU и памяти. Рядом стоит Logging Driver, он решает, куда уходят логи контейнеров. Если служба не стартует, причину почти всегда видно в journalctl. Чужой daemon.json целиком лучше не копировать: одной неподходящей опции хватит, чтобы dockerd не запустился.

Группа docker даёт больше прав, чем кажется

Во всех командах этой статьи стоит sudo, и это сознательный выбор. Участник группы docker может запустить контейнер с подключённым корнем хоста и переписать любой системный файл, так что членство в группе равносильно root. По той же причине запись в /var/run/docker.sock не открывают всем и не прокидывают сокет в контейнеры без крайней нужды.

Rootless эту проблему снимает, зато приносит свои. Сервер Docker работает как пользовательская служба systemd и без включённого linger останавливается после выхода из SSH. Лимитам ресурсов нужны cgroup v2 и делегирование контроллеров, а порты ниже 1024 придётся разрешать отдельно.

Docker Compose на VPS получает ровно те полномочия, что и CLI, через который его запускают, отдельной защиты у него нет. Запуск приложения внутри контейнера от обычного пользователя настраивают в образе или compose.yaml, и к rootless на хосте он отношения не имеет.

Порт 8080 может открыться вопреки UFW

Запись 8080:80 пробрасывает порт 8080 хоста на порт 80 контейнера, причём по умолчанию на всех адресах хоста. EXPOSE в Dockerfile ничего не публикует, он только документирует container ports, которые слушает приложение. Наружу порт открывают флаг -p или секция ports в Compose.

Для теста порт привязывается к 127.0.0.1:8080:80: сервис виден с самого VPS и через SSH-туннель, но не из интернета. Для внешней публикации указывают адрес нужного интерфейса. Публичный IP за NAT провайдера может вообще не принадлежать интерфейсу машины, и привязать к нему порт не получится.

С firewall связан главный сюрприз этого раздела. Docker сам пишет правила для своих мостовых сетей, и пакет к опубликованному порту идёт по цепочке пересылки, мимо входящих правил UFW. Включённый UFW с запретом на 8080 спокойно уживается с открытым портом контейнера.

При стандартном бэкенде iptables собственные ограничения кладут в цепочку DOCKER-USER, она срабатывает раньше правил Docker. Пакет там уже несёт порт контейнера после перенаправления, поэтому для фильтра по исходному порту нужен модуль conntrack. И не путайте iptables-nft, который Ubuntu использует по умолчанию, с экспериментальным бэкендом Docker firewall-backend=nftables: в нём цепочки DOCKER-USER нет.

Как запустить и открыть наружу первый контейнер?

1.     Запустите проверенный образ, явно указав адрес хоста, внешний порт и порт контейнера.

2.     Ограничьте доступ firewall с учётом цепочек Docker и внешних правил провайдера.

3.     С другой машины проверьте ответ с разрешённого адреса и отказ с запрещённого, затем снимите тестовую публикацию.

Тестовый Nginx и его digest

Для проверки возьмём официальный nginx:1.30.6-alpine из стабильной ветки. Если тег свежего патч-релиза ещё не появился в Docker Hub, подойдёт предыдущий. Тег с номером версии могут переопубликовать с исправлениями, поэтому из RepoDigests сохраняется digest, по которому потом запустится ровно тот же образ.

sudo docker pull nginx:1.30.6-alpine
nginx_image=$(sudo docker image inspect nginx:1.30.6-alpine \
  --format '{{index .RepoDigests 0}}')
printf '%s\n' "$nginx_image"
sudo docker run -d --name vps-web-test \
  -p 127.0.0.1:8080:80 --log-driver local "$nginx_image"
curl --fail --retry 10 --retry-connrefused --retry-delay 1 \
  http://127.0.0.1:8080/
sudo docker port vps-web-test
sudo docker logs --tail 30 vps-web-test
sudo docker stop vps-web-test
sudo docker rm -v vps-web-test

Digest выглядит как имя образа с хешем sha256 и указывает на конкретную сборку, даже если тег потом переопубликуют. Успешный curl подтверждает, что сервис отвечает по HTTP, а в журнале контейнера должна появиться строка с этим запросом. Флаг -v при удалении убирает только анонимные тома, именованные тома и сам образ остаются.

Внешнюю проверку делают отдельным контейнером: его пересоздают с адресом интерфейса вместо 127.0.0.1, а порт в firewall открывают только для своего IP. На второй машине подставьте реальный адрес VPS:

curl --connect-timeout 5 http://АДРЕС_VPS:8080/

С вашего адреса должен прийти ответ, с любого другого запрос должен упереться в таймаут. Оба результата стоит записать, а тестовый контейнер удалить: дальше снова понадобится только локальная публикация.

Пока контейнер ничего не хранит, его можно удалять без сожаления. Файлы сайта замену контейнера так не переживут, для них нужны volumes, отдельные тома с данными.

Новый контейнер и прежние данные

Дальше настройки переезжают в compose.yaml. Сайт лежит в именованном томе, а журнал сервиса ограничен по размеру:

name: vps-demo
services:
  web:
	image: ${NGINX_IMAGE:?Укажите образ с digest}
	ports:
  	- "127.0.0.1:8080:80"
	volumes:
  	- site:/usr/share/nginx/html:ro
	restart: unless-stopped
	logging:
  	driver: json-file
  	options:
    	max-size: "10m"
    	max-file: "3"
volumes:
  site:
    name: vps-demo-site

Значение nginx_image из предыдущего шага уходит в .env:

printf 'NGINX_IMAGE=%s\n' "$nginx_image" > .env
sudo docker compose config --quiet
sudo docker compose up -d
curl --fail --retry 10 --retry-connrefused --retry-delay 1 \
  http://127.0.0.1:8080/

При первом запуске Docker заполнит пустой том файлами из образа, и дальше том живёт отдельно: новый образ Nginx этот сайт уже не перезапишет. При смене образа или настроек Docker Compose пересоздаёт контейнер и подключает к нему тот же том. Перед обновлением запишите прежний digest, после обновления проверьте HTTP и журнал. Откат на старый образ данные в томе не вернёт, их восстанавливают из бэкапа.

Бэкап тома делают, пока в него никто не пишет. Для статического сайта хватит остановить сервис и упаковать том временным контейнером из того же образа:

sudo docker compose stop web
sudo docker run --rm -v vps-demo-site:/data:ro -v "$PWD":/backup \
  "$nginx_image" tar -C /data -czf /backup/site.tar.gz .
sudo docker compose start web

Пока архив ни разу не разворачивали, это скорее надежда, чем бэкап. Разверните его в отдельный том, сравните контрольные суммы файлов и удалите проверочный том:

sudo docker volume create vps-demo-site-restore
sudo docker run --rm -v vps-demo-site-restore:/data -v "$PWD":/backup:ro \
  "$nginx_image" tar -C /data -xzf /backup/site.tar.gz
sudo docker run --rm -v vps-demo-site:/a:ro -v vps-demo-site-restore:/b:ro \
  "$nginx_image" sh -c 'cd /a && find . -type f -exec sha256sum {} + | sort > /tmp/a &&
  cd /b && find . -type f -exec sha256sum {} + | sort > /tmp/b && diff /tmp/a /tmp/b && echo OK'
sudo docker volume rm vps-demo-site-restore

С базой данных так нельзя: архив тома работающей базы может оказаться несогласованным. Базу копируют её собственными средствами или после полной остановки.

Лимит журнала в compose.yaml действует только на вывод контейнера, а файлы логов, которые приложение пишет само, ограничивают отдельно. Убрать контейнеры проекта и оставить данные можно так:

sudo docker compose down

С флагом -v эта команда удалит и том с сайтом, и добавлять его по привычке не стоит. Архив тома вынесите с VPS: копия на том же диске пропадёт вместе с ним.

Тестовый Nginx проверил только то, что движок работает и порт отвечает. Сервер готов к нагрузке, когда он переживает перезагрузку, порт открыт ровно туда, куда задумано, а архив тома действительно разворачивается. После установки Docker на VPS сохраните compose.yaml, .env и журнал сессии вместе с порядком восстановления, а саму запись закройте командой exit. Если у приложения есть миграции базы, откат на старую версию стоит испытать заранее: когда для него понадобится бэкап, всё записанное после его создания пропадёт.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.