В этом руководстве мы установим Docker Engine на Ubuntu 24.04/26.04 LTS из официального репозитория Docker, проверим работу daemon и запустим тестовый контейнер hello-world.
После этого подключим Docker Compose, соберем небольшой проект с Nginx, настроим работу с Docker без постоянного sudo и проверим, что Docker и контейнеры автоматически возвращаются в работу после перезагрузки VPS.
Также разберем основные команды для повседневной работы, безопасное обновление Docker Engine и Compose, а в конце пройдемся по типовым ошибкам: проблемам с daemon, docker.sock, пакетами, репозиторием, портами и автозапуском.
По итогу у вас будет готовый базовый Docker-хост на Ubuntu, пригодный для дальнейшего запуска контейнерных приложений.
Что, как, зачем и почему
Распределение ролей. Docker, контейнер и VPS — кто за что отвечает
Сегодня у нас на повестке дня тема, которая поможет разобраться, как устроен современный запуск приложений на сервере и зачем между VPS и самим проектом вообще нужен еще один слой в виде Docker.
Нас сегодня ждёт и теория, и практика. Начнём с первого.
VPS отвечает на вопрос «где будет работать приложение», а Docker — «как именно мы его там запустим».
Классический вариант без Docker выглядит так:

Все компоненты устанавливаются прямо в систему. Это стандартный, рабочий подход, особенно если сервер небольшой и на нем запущено одно простое приложение.
Но допустим, что у нас несколько приложений и у каждого свои зависимости. Со временем системные пакеты, библиотеки и конфигурации начинают пересекаться, а обновление одного проекта может затронуть другой.
Docker помогает привести это рабочее пространство в порядок, добавляя еще один слой:

Каждый контейнер получает собственное файловое окружение, набор зависимостей и процессы. При этом контейнеры используют ядро Linux самого VPS, поэтому они заметно легче полноценных виртуальных машин и более того, могут запускаться на виртуальных машинах без необходимости вложенной виртуализации.
Контейнер можно представить как отдельную рабочую среду внутри сервера. Приложение видит только те файлы, библиотеки и настройки, которые были подготовлены именно для него.
Например, на одном VPS вполне могут одновременно работать:
- Project-a → Node.js 20
- Project-b → Node.js 22
- Project-c → Python 3.12
Эти версии не конфликтуют между собой, потому что находятся в разных контейнерах.
Почему контейнеры удобнее ручной установки приложений
Для особых скептиков я подготовил еще несколько аргументов в пользу контейнеров.
Допустим, сначала на сервере работает один проект на Node.js 20. Через несколько месяцев появляется второй, которому уже нужен Node.js 22. Затем добавляется PostgreSQL одной версии, Redis другой версии и еще несколько системных библиотек.
Постепенно VPS превращается в среду, где разные приложения зависят от одних и тех же системных пакетов.
В какой-то момент обновление одной зависимости может неожиданно затронуть другой проект. В лучшем случае вы получите ошибку при запуске и быстро поймете, какой пакет оказался несовместимым. В худшем — приложение перестанет работать уже после обновления, а причина будет спрятана где-то между версиями библиотек, системными пакетами и конфигурацией.
И тогда, вместо того чтобы заслуженно отдыхать, вам придётся поиграть в Шерлока Холмса. Вас ждут очень «веселые» занятия по просмотру логов, откату пакетов и так далее.
Для продуктового и коммерческого сервера последствия могут быть более неприятными: простой приложения, недоступность сервиса для пользователей и необходимость срочно восстанавливать рабочую конфигурацию.
Docker уменьшает эту связанность. Вместо установки всех зависимостей непосредственно в Ubuntu приложение получает собственное окружение. Обновление компонентов одного контейнера не должно менять среду соседнего проекта, а значит меньше времени уходит на борьбу с неожиданными конфликтами.
Например:

Обновление среды второго проекта при этом не требует менять Node.js первого.
Есть и еще одно преимущество — повторяемость.
Если окружение описано в Dockerfile или Compose-конфигурации, тот же проект можно поднять на другом сервере без долгого ручного воспроизведения всех шагов установки.
Вместо инструкции на несколько десятков команд разработчик получает примерно такую последовательность:
- Docker compose pull
- Docker compose up -d
Конечно, Docker не делает инфраструктуру полностью автоматической. По-прежнему нужно настраивать сеть, volumes, переменные окружения, резервное копирование и безопасность.
Но он позволяет значительно лучше отделить само приложение от операционной системы хоста.
Именно поэтому контейнеры особенно удобны, когда на сервере несколько сервисов или проект регулярно переносится между development, staging и production.
Где здесь Docker Engine, а где Docker Compose
Docker — это не одна программа, которая делает всё, везде и сразу.
Основой является Docker Engine. Именно он создает и запускает контейнеры, управляет образами, сетями и volumes.
Когда мы выполняем: docker run nginx
Docker Engine получает образ Nginx, создает контейнер и запускает его.
Это намного проще визуализировать в схеме:

Для одного контейнера этого вполне достаточно.
Но представим проект, в котором нужно запустить, причём в правильном порядке:
- Приложение;
- PostgreSQL;
- Redis;
- Nginx;
- Отдельные сети;
- Несколько volumes.
Запускать каждый компонент длинной командой docker run уже неудобно.
Вот здесь на сцену выходит Docker Compose.
Он позволяет описать весь проект в одном YAML-файле, например:
services:
app:
image: my-app
db:
image: postgres:17
redis:
image: redis:7После этого вся схема запускается одной командой: docker compose up -d
Compose читает конфигурацию и передает Docker Engine инструкции о том, какие контейнеры, сети и volumes нужно создать.
То есть Docker Engine выполняет работу, а Docker Compose помогает удобно описать сразу несколько связанных компонентов.
Думаю, что благодаря картинке будет более понятно:

Что-ж, с теорией покончили, приступаем к самому интересному – практике.
В этом руководстве мы установим оба компонента: сначала сам Docker Engine, затем Compose plugin. После этого проверим установку на простом контейнере и соберем небольшой проект, чтобы увидеть разницу между обычным docker run и работой через Compose.
Готовим Ubuntu к установке Docker
Прежде чем копировать команды из документации и запускать установку, стоит быстро проверить сам сервер: поддерживаемую версию Ubuntu, архитектуру и доступные ресурсы.
Это займет несколько минут, зато позволит заранее отсеять часть типовых проблем.
Подходит ли ваша версия Ubuntu
На момент подготовки материала официальный Docker Engine поддерживает 64-битные версии:
- Ubuntu 26.04 LTS;
- Ubuntu 24.04 LTS;
- Ubuntu 22.04 LTS.
Для нашей статьи будем использовать первые две: Ubuntu 24.04 LTS и Ubuntu 26.04 LTS. Обе можно использовать для установки Docker Engine из официального APT-репозитория Docker.
Да и к тому же с LTS-версиями работать особенно удобно: они рассчитаны на длительный цикл поддержки, поэтому не приходится регулярно переносить инфраструктуру на новый релиз ОС только ради обновлений системы.
Проверяем версию системы и архитектуру
Сначала посмотрим информацию об Ubuntu: cat /etc/os-release
Для Ubuntu 24.04 вывод будет содержать примерно такие значения:
NAME="Ubuntu"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_ID="24.04"
VERSION_CODENAME=nobleНа Ubuntu 26.04 соответствующим кодовым именем будет resolute.
Версию можно проверить и более короткой командой: lsb_release -a
Если lsb_release отсутствует, устанавливать его только ради этой проверки необязательно — /etc/os-release уже содержит нужную информацию.
Теперь проверим архитектуру: dpkg --print-architecture
На большинстве обычных VPS результат будет: amd64
Это привычная 64-битная архитектура x86, которую используют серверы на процессорах Intel и AMD.
На ARM-серверах можно встретить: arm64
Это другая 64-битная архитектура, рассчитанная на ARM-процессоры.
Для установки Docker оба варианта подходят: Docker Engine поддерживает Ubuntu как на amd64, так и на arm64.
Разница становится заметна уже при запуске контейнеров. Docker скачивает готовые образы приложений, и такой образ должен быть собран под архитектуру вашего сервера. Например, образ только для amd64 нельзя просто взять и обычным способом запустить на arm64.
Причина в том, что программы внутри образа уже собраны под конкретный набор процессорных инструкций. Процессор другой архитектуры может просто не понимать эти инструкции, поэтому контейнер не запустится корректно без совместимой сборки.
С популярными образами вроде Nginx, PostgreSQL или Redis обычно проблем нет — они выпускаются сразу для нескольких архитектур.
Ну да ладно. Далее можно сразу посмотреть ядро, свободное место и объем памяти:
uname -r
df -h /
free -hТаким образом еще до установки мы понимаем, на какой системе работаем и хватит ли серверу ресурсов хотя бы для планируемого набора контейнеров.
С базовой проверкой разобрались. Теперь остается решить, откуда именно устанавливать Docker.
На первый взгляд кажется, что это неважно: Ubuntu умеет ставить программы через APT — встроенный менеджер пакетов, который скачивает их из специальных хранилищ, или репозиториев. Docker там тоже есть, значит, можно просто взять первый попавшийся пакет и идти дальше.
Но здесь есть нюанс.
Почему ставим Docker именно из официального репозитория
Как говорили в прошлой главе, на Ubuntu Docker можно встретить и в стандартных репозиториях системы. Из-за этого возникает логичный вопрос: зачем вообще подключать дополнительный источник пакетов?
Причина в том, что пакет из репозитория дистрибутива и официальный Docker Engine — не обязательно одно и то же с точки зрения источника и цикла обновлений.
Дистрибутивы могут поставлять собственные пакеты Docker, которые собираются и сопровождаются их мейнтейнерами. Проще говоря, это могут быть другие сборки, отличающиеся от официальных релизов Docker, не содержащие нужных плагинов или отстающие на несколько версий.
Теперь о том как подключить APT-репозиторий Docker и уже оттуда установить:
docker-ce
docker-ce-cli
containerd.io
docker-buildx-plugin
docker-compose-plugin
Так Docker Engine, CLI, Buildx и Compose устанавливаются из одного официального источника и затем могут обновляться через привычный APT.
Есть и более короткий путь — официальный установщик с get.docker.com. Он способен установить Docker буквально за несколько команд, но мы рекомендуем сделать всё по стандарту, во избежание проблем.
Вот снова визуализированная схема чтобы было проще и нагляднее:

Но перед подключением репозитория нужно убедиться, что на VPS не осталось пакетов, способных конфликтовать с официальной установкой. Этим и займемся следующим шагом.
Убираем то, что может помешать установке
Прежде чем добавлять новый источник пакетов, стоит проверить, не осталось ли в системе старых или альтернативных компонентов Docker.
Это особенно актуально для VPS, который уже использовался раньше. На свежей Ubuntu список окажется пустым. Так что если вы только-только арендовали VPS и только-только поставили Ubuntu – эту главу можно пропустить.
Какие старые Docker-пакеты могут конфликтовать
Docker — это не один-единственный пакет. В его работе участвуют сразу несколько компонентов: сам движок, клиент командной строки, Compose, container runtime и дополнительные плагины.
Если Docker уже ставили раньше другим способом, часть этих компонентов могла остаться в системе.
Перед установкой официальная документация рекомендует проверить следующие пакеты:
- docker.io
- docker-compose
- docker-compose-v2
- docker-doc
- docker-buildx
- podman-docker
- containerd
- runc
Пугаться этого списка не нужно. Нам не требуется подробно разбирать каждый пакет — важно понять общую идею: старые компоненты могут пересекаться с теми, которые установит официальный Docker Engine.
Хороший пример — containerd.
Это один из компонентов, который непосредственно участвует в запуске контейнеров. При установке Docker из официального репозитория мы получим пакет: containerd.io
В нем уже находится совместимая с Docker версия containerd.
Но если на сервере раньше был установлен отдельный пакет containerd, то APT может обнаружить два разных варианта одного компонента. В результате появляются конфликты зависимостей, несовместимые версии или установка просто останавливается с ошибкой.
Поэтому перед установкой проще убрать старые пересекающиеся пакеты.
Как проверить и удалить конфликтующие пакеты
Сначала посмотрим, есть ли что-нибудь из перечисленного на сервере:
dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runcНа свежей системе большая часть пакетов или вообще весь список будет отсутствовать.
Если конфликтующие компоненты обнаружились, удалить их можно командой:
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)Для новичков отдельно разберу что за что отвечает, чтобы было хоть немного понятно что вообще вбиваем в командную строку.
Первая часть: dpkg --get-selections ...
Она находит установленные пакеты из нашего списка.
Вторая: cut -f1
Эта оставляет только их имена.
После этого результат передается: sudo apt remove
Это позволяет APT удалить найденные конфликтующие пакеты.
То есть длинная команда делает довольно простую вещь:

После удаления можно проверить остатки: dpkg -l | grep -E 'docker|containerd|runc'
Если конфликтующих пакетов больше нет, система готова к установке официального Docker Engine.
Но здесь возникает логичный вопрос: если удалить старые пакеты Docker, не исчезнут ли вместе с ними контейнеры и данные?
Что будет с уже существующими контейнерами и данными
К счастью, удаление самого программного пакета и удаление данных Docker — это разные операции.
Docker хранит большую часть своих рабочих данных в каталоге: /var/lib/docker/
Там могут находиться:
- Контейнеры;
- Образы;
- Volumes;
- Внутренние сети;
- Служебные данные Docker.
Обычная команда sudo apt remove … удаляет установленные пакеты, но не стирает автоматически весь каталог /var/lib/docker.
Но если нужно почистить полностью контейнеры, то можно выполнить:
sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd
После проверки можно переходить непосредственно к подключению официального репозитория Docker.
Подключаем официальный репозиторий Docker

Ставим необходимые системные пакеты
Сначала обновим индекс пакетов: sudo apt update
Теперь установим несколько утилит, которые понадобятся для безопасного подключения репозитория: sudo apt install -y ca-certificates curl
Здесь всё довольно просто.
Ca-certificates позволяет системе доверять HTTPS-соединениям с известными центрами сертификации.
Curl понадобится, чтобы скачать официальный ключ Docker.
Создадим каталог для ключей APT: sudo install -m 0755 -d /etc/apt/keyrings
Именно сюда Ubuntu будет складывать ключ, которым впоследствии проверит пакеты Docker.
Добавляем GPG-ключ Docker
Теперь скачиваем официальный GPG-ключ:
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.ascИ разрешаем APT читать файл: sudo chmod a+r /etc/apt/keyrings/docker.asc
Зачем вообще нужен этот ключ?
Когда Ubuntu скачивает пакет из внешнего репозитория, ей нужно убедиться, что пакет действительно опубликован Docker и не был подменён по дороге.
Упрощённо это выглядит так:

То есть GPG-ключ здесь работает как способ проверить происхождение пакетов.
Если ключ отсутствует или подпись не совпадает, APT предупредит, что не может подтвердить доверие к репозиторию.
Подключаем Docker APT repository
Теперь можно добавить сам репозиторий Docker.
Выполним:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Команда выглядит внушительно, но делает довольно простую вещь.
Она автоматически подставляет:
- Архитектуру сервера, например amd64 или arm64, о которых мы говорили ранее;
- Кодовое имя текущей Ubuntu;
- Адрес официального репозитория Docker;
- Путь к GPG-ключу, которым APT должен проверять пакеты.
Например, для Ubuntu 24.04 на обычном x86-64 VPS результат по смыслу будет примерно таким: deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable
То есть мы говорим APT: «Для этой версии Ubuntu и этой архитектуры используй стабильный официальный репозиторий Docker и проверяй его пакеты указанным ключом».
После добавления источника снова обновим индекс: sudo apt update
Теперь APT загрузит сведения о пакетах не только из стандартных репозиториев Ubuntu, но и из репозитория Docker.
Проверяем, что Ubuntu видит нужные пакеты
Перед установкой полезно убедиться, что новый источник действительно подключился.
Проверим пакет Docker Engine: apt-cache policy docker-ce
В выводе должна появиться доступная версия и адрес репозитория: https://download.docker.com/linux/ubuntu
А строка Candidate должна содержать версию, которую APT предлагает установить.
Похожим образом можно проверить Compose plugin: apt-cache policy docker-compose-plugin
Если пакеты видны, значит Ubuntu успешно:
- Прочитала GPG-ключ;
- Подключила официальный репозиторий;
- Скачала его индекс;
- Нашла подходящие версии пакетов для нашей системы.
Если вместо этого APT сообщает об ошибке подписи, неизвестном репозитории или не находит docker-ce, не стоит сразу переходить к установке. Сначала лучше проверить путь к ключу, содержимое docker.list, кодовое имя Ubuntu и повторно выполнить: sudo apt update
Когда docker-ce и docker-compose-plugin появились среди доступных пакетов, подготовка закончена. Теперь можно устанавливать сам Docker Engine.
Устанавливаем Docker Engine
Что именно устанавливается вместе с Docker
Из официального репозитория мы установим сразу несколько пакетов:
- Docker-ce
- Docker-ce-cli
- Containerd.io
- Docker-buildx-plugin
- Docker-compose-plugin
Разберем их по очереди. Часть из них уже мелькала ранее в статье.
Docker-ce — сам Docker Engine, то есть основная часть системы, которая управляет контейнерами.
Docker-ce-cli — клиент командной строки. Именно благодаря ему работают знакомые команды:
docker ps
docker run
docker imagesContainerd.io — container runtime, который участвует непосредственно в запуске и управлении контейнерами на более низком уровне.
Docker-buildx-plugin расширяет возможности сборки Docker images.
А docker-compose-plugin добавляет современную команду: docker compose
То есть после одной установки мы сразу получаем и Docker Engine, и Compose, который понадобится нам чуть позже.
Ставим Docker Engine и containerd
Установим весь набор одной командой:
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginAPT покажет список зависимостей, скачает пакеты из подключенного репозитория Docker и установит их в систему.
После завершения установки Docker обычно запускается автоматически через systemd.
Выглядит оно как-то так:

Docker daemon — это фоновый процесс, который принимает команды клиента Docker и выполняет основную работу: создает контейнеры, скачивает образы, настраивает сети и управляет volumes.
Когда вы вводите docker run nginx сама команда не запускает контейнер напрямую. Docker CLI передает запрос daemon, а уже тот выполняет необходимые действия.
Проверяем установленную версию
Сначала проверим сам Docker: docker --version
В ответ увидим установленную версию примерно в таком формате: Docker version 28.x.x, build ...
Точные цифры будут зависеть от актуального релиза на момент установки.
Более подробную информацию можно получить командой: docker version
Она показывает данные сразу о двух сторонах:
- Client
- Server
Client — это Docker CLI, которым мы пользуемся в терминале.
Server — Docker Engine, работающий в фоне.
Если раздел Server отображается корректно, значит клиент смог связаться с Docker daemon.
На этом же этапе можно проверить Compose: docker compose version
Если плагин установлен правильно, команда вернет его версию.
Убеждаемся, что Docker daemon запущен

Теперь проверим состояние сервиса: sudo systemctl status docker --no-pager
Нас интересует строка: Active: active (running)
Она означает, что Docker daemon запущен и готов принимать команды.
Можно сделать и более короткую проверку: sudo systemctl is-active docker
Ожидаемый ответ: active
А чтобы узнать, включен ли Docker в автоматический запуск: sudo systemctl is-enabled docker
Обычно после установки результат будет: enabled
К автозапуску мы еще вернемся отдельно, потому что запуск самого Docker daemon и автоматический запуск конкретных контейнеров — две разные вещи.
Если же сервис неожиданно находится в состоянии failed, первым делом стоит посмотреть журнал: sudo journalctl -u docker --no-pager -n 50
Там обычно можно увидеть причину, по которой daemon не смог стартовать.
Если же статус показывает active (running), то установка Docker Engine прошла успешно.
Запускаем первый контейнер
Docker Engine установлен и daemon работает. Теперь проверим всю цепочку — попросим Docker скачать готовый образ, создать из него контейнер и запустить его.
Для этого идеально подходит hello-world: маленький тестовый образ, который практически ничего не делает, кроме проверки того, что Docker установлен и способен нормально работать.
Что происходит при docker run
Начнем с самой важной команды: docker run
Она создает и запускает контейнер из указанного образа.
Например: docker run nginx
Но за одной короткой командой скрывается сразу несколько действий.
Docker:
- Проверяет, есть ли нужный образ на сервере;
- Если образа нет — скачивает его из registry;
- Создает новый контейнер;
- Подготавливает его файловую систему;
- Настраивает сеть;
- Запускает процесс, заданный внутри образа.
Упрощенно:

Важно различать image и container.
Docker image, или образ, — это не изображение и не скриншот. Это готовая заготовка для контейнера: в ней уже лежат нужные файлы приложения, библиотеки, зависимости и базовые настройки.
Например, образ Nginx содержит всё необходимое, чтобы Docker мог быстро создать контейнер с работающим веб-сервером.
Container — это уже конкретный экземпляр, созданный из такого образа.
Проще говоря:
- Image = шаблон
- Container = запущенная копия этого шаблона
Из одного image можно создать несколько отдельных контейнеров:

Все они основаны на одном образе, но являются отдельными экземплярами.
Запускаем hello-world

Идём дальше. Для первой проверки выполним: sudo docker run hello-world
Пока используем sudo. Работу Docker от обычного пользователя настроим чуть позже.
Если образ hello-world еще не находится на VPS, Docker сначала сообщит: Unable to find image 'hello-world:latest' locally
Это не ошибка.
Наоборот, Docker говорит: «локально такого образа нет, попробую скачать его».
После этого он получит образ и запустит контейнер.
В успешном выводе появится сообщение: Hello from Docker!
А ниже Docker сам кратко объяснит, какую цепочку действий только что выполнил.
Если вы увидели Hello from Docker!, значит сразу несколько компонентов работают правильно:
- Docker CLI принимает команды;
- Клиент связывается с Docker daemon;
- Daemon может обращаться к registry;
- Образ успешно скачивается;
- Container runtime способен создать и запустить контейнер.
То есть Docker установлен и действительно умеет создавать контейнеры.
Но есть один нюанс: после выполнения команды hello-world контейнер почти сразу завершает работу. Поэтому теперь проверим, где он оказался и как Docker показывает уже созданные контейнеры.
Проверяем созданный контейнер
После выполнения hello-world может возникнуть небольшой сюрприз.
Попробуем: sudo docker ps
Список может оказаться пустым. Это нормально.
Команда docker ps показывает только работающие контейнеры. А hello-world выводит сообщение и сразу завершает свою работу.
Чтобы увидеть в том числе остановленные контейнеры, добавим параметр -a: sudo docker ps -a
Теперь в списке должен появиться контейнер на основе образа hello-world а его статус будет примерно таким: Exited (0)
Код 0 означает, что процесс завершился успешно.
Посмотреть скачанные images можно отдельно: sudo docker images
В списке появится: hello-world
Получается, контейнер уже остановился, но образ остался на VPS и может использоваться снова.
Если еще раз выполнить: sudo docker run hello-world
Docker уже не придется заново скачивать image — он возьмет локальную копию и создаст из нее новый контейнер.
Добавляем Docker Compose

Окей, с одиночным контейнером разобрались. Теперь проверим второй инструмент, который мы будем использовать дальше — Docker Compose.
Его назначение мы уже разбирали в начале статьи: Compose помогает управлять сразу несколькими связанными контейнерами через один compose.yaml, вместо того чтобы запускать каждый длинной командой docker run.
Проверяем Docker Compose

Отдельно устанавливать Compose в нашем случае уже не нужно. Вместе с Docker Engine мы установили пакет: docker-compose-plugin
Именно он добавляет современный Docker Compose в Docker CLI.
Проверим версию: docker compose version
В ответ должна появиться строка примерно такого вида: Docker Compose version v2.x.x
Если пакет по какой-то причине был пропущен, его можно установить отдельно:
sudo apt update sudo apt install -y docker-compose-plugin
После этого снова выполните: docker compose version
Docker Compose или docker-compose — в чем разница
Иногда можно встретить команду: docker-compose
С дефисом.
Это синтаксис старого Docker Compose V1, который распространялся как отдельный инструмент.
Современный Compose V2 работает как плагин Docker CLI, поэтому команда теперь выглядит так: docker compose
То есть Compose стал частью общей структуры команд Docker:
- docker run
- docker ps
- docker images
- docker compose
Поэтому дальше в статье будем использовать именно современный вариант: docker compose
Собираем первый мини-проект через Docker Compose
Теорию про Compose мы уже разобрали, сам плагин проверили — пора наконец использовать его по назначению.
Для первого примера не будем сразу собирать связку из приложения, базы данных и Redis. Возьмем Nginx: он быстро запускается, не требует сложной настройки и хорошо показывает сам принцип работы compose.yaml.
В результате у нас получится небольшой проект, который можно поднять одной командой, проверить в браузере, остановить и снова запустить без повторной настройки контейнера.
Создаем каталог проекта
Сначала создадим отдельный каталог в /opt, где часто размещают сторонние приложения и сервисы на сервере:
sudo mkdir -p /opt/docker-nginx sudo chown -R $USER:$USER /opt/docker-nginx cd /opt/docker-nginx
Вторая команда передает текущему пользователю права на каталог, чтобы дальше можно было создавать и редактировать файлы проекта без постоянного sudo.
Здесь будут лежать файлы нашего мини-проекта.
Проверить текущий каталог можно командой: pwd
Ожидаемый путь будет таким: /opt/docker-nginx
Сам Docker не требует хранить Compose-проекты именно в /opt. Каталог можно выбрать другой — важно лишь, чтобы конфигурация конкретного проекта лежала отдельно и не смешивалась с остальными файлами. Для серверных приложений /opt/<имя-проекта> — один из распространённых и более аккуратных вариантов, чем размещение проекта непосредственно в домашнем каталоге пользователя.
Описываем сервис в compose.yaml
Теперь создадим главный файл проекта: nano compose.yaml
Добавим в него:
services:
web:
image: nginx:stable
ports:
- "8080:80"
restart: unless-stoppedРазберем конфигурацию по частям:
| Параметр | Что означает | Зачем нужен |
| services | Список сервисов проекта | Внутри него описываются контейнеры, которые должен запустить Compose |
| web | Имя текущего сервиса | Имя произвольное. В более крупном проекте рядом могут находиться app, db, redis и другие сервисы |
| image: nginx:stable | Образ, из которого создается контейнер | В нашем случае используется готовый стабильный образ Nginx |
| ports: "8080:80" | Связывает порт 8080 VPS с портом 80 контейнера | Позволяет обращаться к Nginx извне через VPS:8080 |
| restart: unless-stopped | Политика автоматического перезапуска | Контейнер поднимется после перезапуска Docker или VPS, если до этого его не остановили вручную |
На параметре ports стоит остановиться чуть подробнее.
Запрос к VPS:8080 будет передан: Nginx container:80
Контейнер работает в собственной сетевой среде, поэтому одного запущенного Nginx недостаточно, чтобы он автоматически стал доступен извне. Нужный порт мы публикуем явно.
К restart: unless-stopped тоже еще вернемся отдельно, когда будем разбирать автозапуск Docker и контейнеров после перезагрузки сервера.
Запускаем Nginx одной командой

Сохраняем compose.yaml и запускаем проект: docker compose up -d
Параметр -d означает detached mode — контейнер продолжит работать в фоне, а терминал сразу вернется к приглашению командной строки.
При первом запуске Compose скачает образ Nginx, если его еще нет на VPS, создаст необходимые ресурсы и запустит контейнер.
Посмотрим состояние проекта: docker compose ps
В выводе должен появиться сервис web со статусом Up и опубликованным портом: 0.0.0.0:8080->80/tcp
Можно проверить контейнер и обычной Docker-командой: docker ps
Разница лишь в том, что docker compose ps показывает контейнеры текущего Compose-проекта, а docker ps — все запущенные контейнеры на сервере.
Проверяем приложение в браузере
Сначала проверим Nginx прямо с VPS: curl http://127.0.0.1:8080
Если контейнер работает, в ответ придет HTML стандартной страницы Nginx.
Теперь можно открыть в браузере: http://PUBLIC_IP:8080
Где PUBLIC_IP — внешний IP вашего VPS.
Должна появиться стандартная страница: Welcome to nginx!
Если локальный curl работает, а страница с компьютера не открывается, проблема, скорее всего, уже не в Docker. Проверьте firewall VPS, Security Group облачного провайдера и доступность TCP-порта 8080.
Останавливаем и снова поднимаем проект
Теперь проверим одно из главных удобств Compose.
Остановить и удалить контейнеры текущего проекта можно командой: docker compose down
Проверим: docker compose ps
Запущенных сервисов уже не будет.
При этом наш compose.yaml никуда не исчез. Вся конфигурация проекта осталась в файле, поэтому повторный запуск сводится к одной команде: docker compose up -d
Compose снова прочитает конфигурацию и восстановит описанный в ней сервис.
Если нужно просто остановить контейнеры, не удаляя их, можно использовать: docker compose stop
А затем: docker compose start
Разница простая:
- Stop/start → останавливает и запускает уже созданные контейнеры
- Down/up → удаляет и заново создает ресурсы проекта
Думаю, что вы согласитесь, что это неплохо. Вместо того чтобы вспоминать параметры предыдущего docker run, мы храним желаемую конфигурацию в одном файле и можем воспроизвести ее в любой момент.
Наш первый Compose-проект готов. Но пока для работы с Docker мы либо использовали sudo, либо зависим от текущих прав пользователя. Следующим шагом настроим нормальную работу с Docker от обычной учетной записи.
Убираем sudo из повседневной работы
Это можно назвать своего рода оптимизацией рабочего процесса. Многие команды, как и писал ранее, приходится запускать через sudo.
Например:
- sudo docker ps
- sudo docker images
- sudo docker compose up -d
Для разовой проверки это не проблема. Но если вы работаете с Docker постоянно, писать sudo перед каждой командой неудобно.
Docker позволяет дать обычному пользователю доступ к daemon напрямую. Для этого используется системная группа docker.
Почему Docker изначально требует повышенных прав
Docker daemon управляет контейнерами, сетями, volumes и другими системными ресурсами.
По умолчанию его сокет находится здесь: /var/run/docker.sock
И доступ к нему имеют root и пользователи, которым разрешено работать с Docker.
Когда обычный пользователь без нужных прав выполняет docker ps можно получить ошибку вроде: permission denied while trying to connect to the Docker daemon socket
Она сигнализирует о том, что Docker CLI установлен, daemon работает, но текущему пользователю запрещено к нему обращаться.
Поэтому раньше мы использовали: sudo docker ps
Sudo временно выполняет команду с повышенными правами, и доступ к Docker daemon появляется.
Но вместо постоянного использования sudo можно один раз выдать нужные права пользователю.
Добавляем пользователя в группу docker
Во время установки Docker обычно создается системная группа: docker
Проверим: getent group docker
Если группа существует, увидим строку примерно такого вида: docker:x:999:
Теперь добавим текущего пользователя: sudo usermod -aG docker $USER
Чтобы было понятнее, снова разберем команду с помощью таблицы:
| Часть | Что делает |
| usermod | Изменяет параметры существующего пользователя |
| -a | Добавляет новое значение, не удаляя существующие группы |
| -G docker | Добавляет пользователя в дополнительную группу docker |
| $USER | Подставляет имя текущего пользователя |
Параметр -a здесь особенно важен. Если использовать -G без -a, можно случайно заменить список дополнительных групп пользователя вместо того, чтобы добавить новую.
Проверить текущие группы можно командой: groups
Однако сразу после usermod группа docker может еще не появиться в текущей SSH-сессии.
Тут можно особо не переживать так как это нормально. Права уже изменены, но текущий сеанс о них пока не знает.
Применяем новые права
Самый простой вариант — выйти из SSH командой exit и подключиться к VPS заново.
После нового входа система перечитает список групп пользователя.
Если переподключаться не хочется, можно открыть новую shell-сессию с группой docker: newgrp docker
После этого снова проверим: groups
В списке должна появиться: docker
Теперь можно проверить, действительно ли доступ к daemon работает без повышения прав.
Проверяем docker ps без sudo
Выполним: docker ps
На этот раз sudo не используется.
Если всё настроено правильно, Docker выведет список работающих контейнеров.
У нас там должен находиться Nginx из предыдущей главы, поэтому результат будет примерно таким:
| CONTAINER ID | IMAGE | STATUS | PORTS |
| ... | nginx:stable | Up ... | 0.0.0.0:8080->80/tcp |
Заодно можно проверить Compose: docker compose ps
Как видно на скриншоте, который я прилепил для вас, он тоже должен работать без sudo.
Если вместо списка контейнеров снова появляется permission denied, сначала проверьте группы текущего пользователя: groups
Если docker в списке нет, скорее всего, новые права еще не применились к текущей SSH-сессии. Переподключитесь к серверу или выполните: newgrp docker
После этого снова попробуйте: docker ps
Если группа docker уже присутствует, то продолжаем копать более тщательнее. По порядку. Начнём с проверки прав на Docker socket: ls -l /var/run/docker.sock
Обычно результат выглядит примерно так: srw-rw---- 1 root docker ... /var/run/docker.sock
Здесь важно, чтобы socket принадлежал группе docker, а у самой группы были права на чтение и запись.
Если права выглядят корректно, но ошибка остается, убедитесь, что Docker daemon вообще работает: sudo systemctl status docker --no-pager
При необходимости перезапустите сервис: sudo systemctl restart docker
И потом повторите: docker ps
Если же /var/run/docker.sock имеет необычного владельца или группу, перезапуск Docker обычно пересоздает socket с корректными параметрами.
Важно. Не стоит пытаться решить проблему командой вроде: sudo chmod 666 /var/run/docker.sock
Пусть она действительно может убрать permission denied, но это нам не надо так как одновременно откроется управление Docker всем локальным пользователям сервера.
И вот здесь мы подходим к важному моменту: доступ к Docker daemon сам по себе дает очень широкие возможности. Именно поэтому мы добавляли пользователя в группу docker, а не просто открывали socket для всех подряд.
Разберемся, почему к этой группе нужно относиться почти так же внимательно, как к root-доступу.
Почему доступ к группе docker почти равен root-доступу
Теперь команды можно выполнять без sudo.
Но здесь есть важный нюанс.
Пользователь из группы docker получает право напрямую управлять Docker daemon. А Docker daemon, в свою очередь, умеет запускать контейнеры с очень широким доступом к системе.
Например, контейнеру можно разрешить видеть каталог самого VPS:
- /home
- /etc
- /var
Или даже подключить внутрь контейнера системные директории хоста.
Получается следующая цепочка:

То есть пользователь, который формально не является root, через Docker может получить доступ к данным и возможностям, которые обычному аккаунту недоступны.
Именно поэтому членство в группе docker по уровню возможностей фактически близко к root-доступу. Это как дать дворецкому доступ к своему дому. Фактически у него другая роль и он не является владельцем, а формально может копаться в вашем грязном белье.
Отсюда несколько простых правил:
- Не добавляйте в группу docker случайных пользователей;
- Не выдавайте такой доступ аккаунтам, которым он не нужен;
- Не воспринимайте отсутствие sudo как дополнительную защиту;
- На сервере с несколькими пользователями заранее решите, кому действительно нужен Docker.
Для личного VPS или сервера, где работает один администратор, такой подход вполне нормален.
А вот на общей системе доступ к группе docker лучше выдавать только тем, кому он действительно нужен.
Теперь команды Docker можно выполнять без постоянного sudo.
Следующий логичный вопрос: что произойдет после перезагрузки VPS? Запустится ли сам Docker и вернутся ли вместе с ним наши контейнеры? Этим и займемся дальше.
Настраиваем Docker на переживание перезагрузки
Итак. Продолжаем. В прошлой главе мы закончили на вполне логичном вопросе: что произойдет, если VPS перезагрузится?
Сам Docker может подняться автоматически, но это еще не гарантирует, что вместе с ним вернутся все контейнеры. Здесь важно разделять две вещи:
- Автозапуск Docker daemon;
- Автозапуск конкретных контейнеров.
По сути, сначала должен проснуться «управляющий», а уже потом — сервисы, которыми он управляет.
Как Docker связан с systemd
На Ubuntu Docker работает как системный сервис.
За его запуском и состоянием следит systemd — стандартная система управления сервисами Linux.
Именно через нее мы уже проверяли Docker командой: sudo systemctl status docker
Если очень упрощенно, схема выглядит так:

То есть systemd не управляет каждым контейнером напрямую. Его задача — запустить сам Docker daemon.
А уже Docker решает, какие контейнеры нужно вернуть в работу.
Можно представить это как открытие магазина после отключения электричества: systemd сначала включает само здание и оборудование, а Docker уже решает, какие «отделы» внутри должны начать работу автоматически.
Включаем автозапуск Docker daemon
Проверим, включен ли Docker в автозагрузку: systemctl is-enabled docker
Если в ответ получаем enabled, то всё уже настроено.
После следующей загрузки Ubuntu systemd автоматически запустит Docker daemon.
Если вместо этого отображается disabled, то включим автозапуск: sudo systemctl enable docker
Можно сразу включить и запустить сервис одной командой: sudo systemctl enable --now docker
Параметр --now означает: не только добавить сервис в автозагрузку, но и запустить его прямо сейчас.
Проверим результат:
systemctl is-enabled docker systemctl is-active docker
Ожидаем:
enabled
activeНа этом сам Docker готов пережить перезагрузку.
Но с контейнерами всё чуть интереснее.
Почему контейнер не всегда стартует вместе с Docker
Допустим, у нас есть работающий контейнер Nginx.
Мы перезагрузили VPS, Docker daemon успешно вернулся в состояние active, но контейнер остался остановленным.
Почему?
Потому что Docker не предполагает, что каждый когда-либо созданный контейнер обязательно нужно запускать после reboot.
На одном сервере могут находиться:
- Рабочие сервисы;
- Временные тестовые контейнеры;
- Остановленные старые проекты;
- Контейнеры, которые администратор специально выключил.
Если бы Docker автоматически запускал вообще всё подряд, после перезагрузки можно было бы получить весьма неожиданный набор работающих сервисов или сбои из-за конфликта портов и других ресурсов.
Поэтому поведение задается отдельно для каждого контейнера с помощью restart policy.
И одну такую настройку мы уже использовали в нашем compose.yaml: restart: unless-stopped
Теперь разберемся, что она означает и какие есть альтернативы.
Разбираемся с restart policy
Restart policy говорит Docker, при каких условиях контейнер нужно запускать заново.
Основные варианты:
| Политика | Что происходит |
| no | Автоматический перезапуск отключен |
| on-failure | Контейнер перезапускается, если завершился с ошибкой |
| always | Docker старается запускать контейнер снова после остановки и перезапуска daemon |
| unless-stopped | Контейнер перезапускается автоматически, если пользователь ранее не остановил его вручную |
Для обычного веб-сервиса часто удобен вариант: restart: unless-stopped
Именно его мы указали для Nginx.
По логике оно звучит примерно так: «Если сервис упал или сервер перезагрузился — подними его обратно. Но если я сам его остановил, значит, у меня были на то причины».
Схема для примера:

А если перед этим вручную выполнить: docker compose stop
Docker запомнит, что контейнер был остановлен намеренно, и unless-stopped не станет возвращать его в работу только потому, что VPS перезагрузился.
У always поведение более настойчивое:
Docker будет стремиться вернуть контейнер после запуска daemon независимо от предыдущего состояния, за исключением нюансов ручной остановки до перезапуска самого daemon.
Для production-сервисов чаще всего выбирают unless-stopped или always, а on-failure полезен для задач, которые нужно перезапускать только после аварийного завершения.
Проверяем автозапуск после reboot

Сначала убедимся, что наш Nginx работает: docker compose ps
Или: docker ps
Контейнер должен находиться в состоянии: Up
Теперь перезагрузим VPS: sudo reboot
SSH-соединение сразу оборвется — это нормально.
Подождите, пока сервер снова загрузится, и подключитесь к нему по SSH.
Первым делом проверим Docker: systemctl is-active docker
Ожидаемый результат: active
Теперь посмотрим контейнер: docker ps
Если политика restart: unless-stopped сработала, Nginx снова будет находиться в состоянии: Up
И наконец проверим сам сервис: curl http://127.0.0.1:8080
Если приходит стандартная страница Nginx, значит вся цепочка восстановилась автоматически:

Теперь наш Docker-хост уже не требует ручного вмешательства после обычного reboot.
Но чтобы нормально работать с ним каждый день, мало уметь запускать контейнеры. Нужно еще быстро смотреть их состояние, читать логи, заходить внутрь контейнера и удалять ненужные ресурсы.
Этим базовым набором команд и займемся дальше.
Команды Docker, которые реально нужны каждый день
Как посмотреть контейнеры
Начнем с уже знакомой нам команды: docker ps
Она показывает только работающие контейнеры.
В выводе обычно видны:
- ID контейнера;
- Image;
- Команда запуска;
- Время работы;
- Опубликованные порты;
- Имя контейнера.
Если нужен список вообще всех контейнеров, включая остановленные: docker ps -a
Это особенно полезно, когда контейнер «куда-то исчез» после ошибки. Часто он никуда не пропал, а просто завершился и поэтому не отображается в обычном docker ps.
Если нужен только ID контейнеров: docker ps -q
А для более компактного просмотра можно использовать:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"Так удобнее, когда на сервере уже работает несколько сервисов.
Проверяем скачанные образы
Контейнеры создаются из images, поэтому периодически полезно смотреть, какие образы уже скачаны на VPS.
Для этого используем docker images или более современный вариант: docker image ls
Обе команды показывают плюс-минус одно и то же:
| REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
| nginx | stable | ... | ... | ... |
| hello-world | latest | ... | ... | ... |
Здесь можно увидеть:
- название image;
- тег;
- ID;
- дату создания;
- размер.
Если вы несколько раз обновляли проект или экспериментировали с разными версиями, старые images могут постепенно занимать место на диске.
Поэтому docker image ls — одна из тех команд, к которым стоит возвращаться время от времени.
Читаем логи и ищем ошибки
Если контейнер запущен, но приложение ведет себя странно, первым делом обычно смотрят логи.
Команда: docker logs CONTAINER_NAME
Например docker logs docker-nginx-web-1 покажет вывод приложения внутри контейнера.
Если логов много, удобно смотреть только последние строки: docker logs --tail 50 CONTAINER_NAME
А чтобы следить за ними в реальном времени: docker logs -f CONTAINER_NAME
Параметр -f работает примерно как tail -f: новые строки появляются прямо в терминале по мере работы приложения.
Выйти из такого режима можно сочетанием: Ctrl + C
Для диагностики это одна из самых полезных команд Docker.
Если приложение падает после запуска, часто именно в docker logs находится причина: ошибка конфигурации, отсутствующая переменная окружения, проблема подключения к базе данных или занятый порт.
Заходим внутрь работающего контейнера
Иногда нужно посмотреть файлы контейнера или выполнить команду непосредственно внутри него.
Для этого используется: docker exec
Например: docker exec -it CONTAINER_NAME bash
Параметры здесь такие:
- -i оставляет стандартный ввод открытым;
- -t создает интерактивный терминал.
В результате мы как будто открываем отдельную консоль внутри контейнера.
Приглашение командной строки изменится, и команды будут выполняться уже в его окружении.
Например ls покажет файловую систему контейнера, а не VPS.
Но не каждый image содержит bash.
Минималистичные образы часто используют только: sh
Тогда команда будет такой: docker exec -it CONTAINER_NAME sh
Выйти обратно на VPS можно командой: exit
Здесь важно помнить: контейнер — не отдельная полноценная VM. Заходить внутрь него для диагностики нормально, но постоянные ручные изменения лучше не использовать как основной способ настройки приложения. После пересоздания контейнера такие изменения могут исчезнуть.
Конфигурацию лучше хранить в Dockerfile, compose.yaml, volumes и других воспроизводимых источниках.
Останавливаем, запускаем и перезапускаем контейнеры
Для управления уже созданным контейнером есть три основные команды.
Остановить: docker stop CONTAINER_NAME
Запустить обратно: docker start CONTAINER_NAME
Перезапустить: docker restart CONTAINER_NAME
Например: docker restart docker-nginx-web-1
К слову, restart по сути выполняет остановку и новый запуск контейнера.
Если контейнер не реагирует на обычный stop, существует другой вариант: docker kill CONTAINER_NAME
Но использовать его без необходимости не стоит.
Docker stop сначала дает процессу время корректно завершить работу, тогда как docker kill прекращает его принудительно.
Это похоже на разницу между нормальным выключением компьютера и отключением питания из розетки.
Как удалить ненужный контейнер или образ
Остановленный контейнер продолжает существовать и занимает место.
Удалить его можно командой: docker rm CONTAINER_NAME
Если контейнер еще работает, Docker обычно не позволит удалить его обычным способом.
Сначала docker stop CONTAINER_NAME, а затем: docker rm CONTAINER_NAME
Тут, по аналогии с прошлой главой, есть и принудительный вариант: docker rm -f CONTAINER_NAME
Но применять его лучше только когда вы понимаете последствия.
-f принудительно останавливает работающий контейнер и сразу удаляет его. Это значит, что процесс внутри контейнера не получает нормального времени на завершение работы. В зависимости от приложения это может привести к незавершенным операциям, потерянным последним изменениям или повреждению данных.
Проще говоря:
- docker stop → даем приложению нормально завершиться
- docker rm → удаляем уже остановленный контейнер
- docker rm -f → резко останавливаем и сразу удаляем
Для тестового контейнера вроде hello-world это обычно не страшно. А вот с базой данных или работающим приложением лучше сначала использовать обычный stop.
Образ удаляется отдельно: docker rmi IMAGE_NAME
Например: docker rmi hello-world или docker image rm hello-world
Если от image все еще зависят существующие контейнеры, Docker предупредит об этом и не станет удалять его без дополнительных действий.
Периодически можно посмотреть неиспользуемые ресурсы: docker system df
Команда покажет, сколько места занимают:
- Images;
- Контейнеры;
- Local volumes;
- Build cache.
А команда docker system prune может очистить часть неиспользуемых ресурсов.
Но с ней уже стоит быть внимательнее: перед подтверждением обязательно посмотрите, что именно Docker собирается удалить.
Особенно осторожно относитесь к volumes. Там могут храниться реальные данные приложений.
Базовые команды Docker Compose
Когда проект управляется через Compose, работать удобнее не с отдельными контейнерами, а сразу с сервисами из compose.yaml.
Запустить проект: docker compose up -d
Посмотреть его состояние: docker compose ps
Посмотреть логи всех сервисов: docker compose logs
Следить за логами: docker compose logs -f
Остановить контейнеры без удаления: docker compose stop
Запустить их обратно: docker compose start
Перезапустить: docker compose restart
А полностью остановить проект и удалить созданные Compose-контейнеры и сети: docker compose down
Если после изменения compose.yaml нужно применить новую конфигурацию: docker compose up -d
Compose сравнит текущее состояние с описанием проекта и при необходимости пересоздаст изменившиеся сервисы.
Для повседневной работы можно держать в голове совсем короткую шпаргалку:
| Команда | Что делает |
| docker ps | Показывает, какие контейнеры сейчас работают |
| docker logs | Показывает логи контейнера |
| docker exec | Открывает shell внутри работающего контейнера |
| docker stop/start | Запускает/Останавливает контейнер |
| docker restart | Перезапускает контейнер |
| docker images | Показывает скачанные Docker images |
| docker compose up -d | Запускает Compose-проект в фоне |
| docker compose ps | Показывает состояние сервисов Compose-проекта |
| docker compose logs | Показывает логи сервисов в реальном времени |
| docker compose down | Останавливает проект и удаляет созданные Compose-контейнеры и сети |
Этого набора уже достаточно для большинства базовых задач.
Обновляем Docker без лишнего риска
С базовыми командами разобрались. Теперь мы умеем посмотреть состояние контейнеров, открыть логи, перезапустить сервис и убрать ненужные ресурсы.
Но Docker, как и любая другая часть системы, тоже время от времени нужно обновлять.
И здесь подход «увидел обновление — сразу установил» не всегда лучший, особенно если на VPS уже работает реальный проект. Новая версия Docker Engine может потребовать перезапуска daemon, а вместе с ним затронуть работающие контейнеры.
Проверяем доступные обновления
Для начала обновим локальный индекс пакетов: sudo apt update
Эта команда пока ничего не устанавливает. Она лишь получает свежую информацию о версиях пакетов из подключенных репозиториев, включая официальный репозиторий Docker.
Теперь можно посмотреть, есть ли обновления среди компонентов Docker:
apt list --upgradable 2>/dev/null | grep -E 'docker|containerd'Если подходящих строк нет, значит сейчас обновлять нечего.
Дополнительно можно посмотреть установленную и доступную версии основных пакетов:
apt-cache policy docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginДля каждого пакета нас особенно интересуют две строки:
- Installed:
- Candidate:
Первая — версия, которая сейчас установлена на VPS. Вторая — версия, которую APT предложит установить из подключенных репозиториев.
Например, если они отличаются:
- Installed: 5:28.x.x...
- Candidate: 5:29.x.x...
Значит для Docker Engine доступно обновление.
Перед дальнейшими действиями полезно также посмотреть, что сейчас работает: docker ps
Если на сервере уже находится production-приложение, заодно стоит убедиться, что вы понимаете, какие контейнеры могут быть затронуты возможным перезапуском Docker.
Теперь мы знаем, что именно собираемся обновлять. Следующий вопрос не менее важный: что желательно сохранить до того, как мы начнем менять работающую систему.
Что сохранить перед обновлением
Само обновление Docker обычно не удаляет ваши контейнеры, images или volumes.
Но это не означает, что резервная копия больше не нужна.
Проблема может находиться не в самом процессе установки, а в том, что после обновления приложение неожиданно перестанет запускаться или новая версия проявит несовместимость с текущей конфигурацией.
Поэтому перед обновлением стоит сохранить как минимум то, из чего проект можно нормально восстановить.
В нашем примере это прежде всего: compose.yaml
Если в проекте используются дополнительные файлы, сохраните и их:
- .env
- Dockerfile
- конфигурации Nginx
- файлы приложения
Например, Compose-файл можно просто скопировать: cp compose.yaml compose.yaml.backup
Но конфигурация — только половина истории.
Если контейнер работает с реальными постоянными данными, например PostgreSQL, перед обновлением должна существовать актуальная резервная копия самих данных.
То есть логика здесь такая:
- Конфигурация проекта → позволит заново создать контейнеры
- Backup данных → позволит восстановить их содержимое
Важно. Одно не заменяет другое.
Полезно также зафиксировать текущие версии Docker:
docker --version
docker compose versionИ состояние контейнеров: docker ps
Если после обновления что-то изменится, у нас будет точка отсчета для диагностики.
Когда необходимое сохранено, можно переходить к самому обновлению.
Обновляем Docker Engine и Compose
Поскольку Docker мы устанавливали из официального APT-репозитория, обновлять его можно через тот же пакетный менеджер.
Еще раз обновим индекс: sudo apt update
А затем обновим именно компоненты Docker:
sudo apt install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginТак мы не запускаем обновление всей Ubuntu сразу, а меняем только нужные нам Docker-пакеты. Точечно.
Напомню, что здесь обновляются:
| Пакет | За что отвечает |
| docker-ce | Docker Engine |
| docker-ce-cli | Docker CLI |
| containerd.io | Container runtime |
| docker-buildx-plugin | Buildx |
| docker-compose-plugin | Docker Compose |
APT покажет, какие версии собирается установить, и попросит подтверждение, если команда запущена без -y.
Во время обновления Docker Engine сервис docker может быть перезапущен. Поэтому лучше проводить такую операцию в период, когда возможная кратковременная недоступность приложения не создаст проблем.
После завершения снова проверим версии:
docker --version
docker compose versionПравда есть но. Новые номера версий сами по себе еще не означают, что обновление полностью прошло успешно. Нам важно убедиться, что после него продолжает работать сам daemon и все нужные контейнеры.
Проверяем контейнеры после обновления
Начнем с Docker daemon: systemctl is-active docker
Ожидаем: active
Если нужен более подробный статус: sudo systemctl status docker --no-pager
Теперь смотрим контейнеры: docker ps
Наш Nginx должен снова находиться в состоянии Up.
Здесь нам как раз помогает настройка restart: unless-stopped которую мы добавили ранее. Если во время обновления Docker daemon перезапускался, такая политика позволяет нужному контейнеру вернуться в работу автоматически.
Но проверять стоит не только сам факт запуска.
Приложение может числиться как Up, но при этом работать неправильно. Поэтому отдельно убедимся, что Nginx действительно отвечает: curl http://127.0.0.1:8080
Если приходит HTML стандартной страницы Nginx, вся цепочка работает:

Для настоящего проекта вместо одной проверки curl стоит проверить его основные функции: открыть сайт, выполнить запрос к API или убедиться, что приложение подключается к базе данных.
И только после этого обновление можно считать действительно завершенным.
А если один из этих этапов не прошел — переходим к диагностике.
Что делать, если после обновления возникли проблемы
Первым делом не стоит сразу удалять контейнеры, images или переустанавливать Docker.
Сначала нужно понять, на каком уровне произошел сбой. Может решение куда проще, чем вы думаете.
Если не работает сам Docker daemon, проверим его состояние: sudo systemctl status docker --no-pager
А затем журнал: sudo journalctl -u docker --no-pager -n 100
Если Docker работает, но конкретный контейнер остановился: docker ps -a
Команда покажет и завершившиеся контейнеры.
После этого смотрим его логи: docker logs CONTAINER_NAME
Для Compose-проекта:
docker compose ps
docker compose logsПолучается своеобразная лестница диагностики:

Так гораздо проще найти причину.
Если проблема появилась именно после обновления, дополнительно сравните новую версию Docker с той, которую зафиксировали перед установкой. Для production-систем также стоит проверить release notes соответствующего релиза и наличие изменений, способных повлиять на текущую конфигурацию.
Итак. С безопасным обновлением разобрались. Но проблемы с Docker возникают не только после смены версии: daemon может оказаться недоступен, пользователь — потерять доступ к socket, репозиторий — вернуть ошибку, а работающий контейнер — неожиданно оказаться недоступным из браузера.
Поэтому напоследок разберем наиболее частые неисправности и посмотрим, с чего начинать диагностику в каждом случае.
Если Docker не заработал: разбираем частые ошибки
Docker daemon недоступен
Одна из самых характерных ошибок выглядит примерно так:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock
Is the docker daemon running?В этом случае Docker CLI установлен и команда docker существует, но клиент не может связаться с daemon.
Начнем с очевидного: systemctl is-active docker
Если получаем inactive или failed проверим подробный статус: sudo systemctl status docker --no-pager
Попробуем запустить сервис: sudo systemctl start docker
И снова проверим: docker ps
Если Docker не запускается, переходим к журналу systemd: sudo journalctl -u docker --no-pager -n 100
Именно там обычно находится реальная причина: ошибка конфигурации, проблема с зависимостями или другой системный сбой.
Здесь важно не путать две ситуации:
- Cannot connect to the Docker daemon — клиент не может нормально обратиться к daemon;
- Permission denied — daemon может работать, но конкретному пользователю запрещен доступ.
Со второй как раз разберемся дальше.
Permission denied при работе с docker.sock
Эту ошибку мы уже встречали в главе про работу без sudo, поэтому повторять всю настройку группы docker не будем.
Если команда docker ps возвращает permission denied while trying to connect to the Docker daemon socket, то сначала проверим, входит ли текущий пользователь в группу docker: groups
Если docker отсутствует, добавляем пользователя: sudo usermod -aG docker $USER
После этого нужно либо переподключиться по SSH, либо выполнить: newgrp docker
Если группа уже присутствует, посмотрим права socket: ls -l /var/run/docker.sock
Обычно ожидаем что-то похожее на: srw-rw---- 1 root docker ... /var/run/docker.sock
То есть socket принадлежит группе docker, а участники этой группы могут обращаться к нему.
Если права выглядят нормально, но ошибка сохраняется, проверим сам daemon: systemctl is-active docker
И при необходимости: sudo systemctl restart docker
После чего повторим: docker ps
Docker или Compose не найдены
Иногда проблема еще проще.
Если терминал отвечает docker: command not found значит shell не нашел Docker CLI.
Сначала проверим, установлен ли пакет: dpkg -l | grep docker-ce-cli
А затем: which docker
Если Docker устанавливался по нашей инструкции, команда обычно должна находиться в стандартном системном пути.
Если пакет отсутствует, снова установим необходимый набор:
sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Отдельная ситуация — Docker работает, но команда docker compose version не распознается.
Проверим Compose plugin: dpkg -l | grep docker-compose-plugin
Если его нет:
sudo apt update sudo apt install docker-compose-plugin
После этого: docker compose version
Еще одна частая причина путаницы — попытка использовать docker-compose с дефисом.
Как мы уже разбирали ранее, это старый синтаксис Compose V1. В нашей конфигурации используется современный Compose V2: docker compose, без дефиса.
Проблемы с пакетами, GPG-ключом и репозиторием
Если ошибка возникает еще на этапе sudo apt update или при установке docker-ce, проблема обычно находится не в самом Docker daemon, а в APT.
Например, APT может сообщить что:
- Не удалось проверить подпись репозитория;
- GPG-ключ недоступен;
- Репозиторий не содержит Release-файл;
- Пакет docker-ce не найден;
- Обнаружен конфликт зависимостей.
В таком случае лучше не продолжать установку вслепую.
Сначала проверим файл репозитория: cat /etc/apt/sources.list.d/docker.list
Затем наличие GPG-ключа: ls -l /etc/apt/keyrings/docker.asc
И повторим: sudo apt update
Если Docker-пакеты после этого все еще не находятся, проверим: apt-cache policy docker-ce
В выводе должен присутствовать официальный адрес: download.docker.com
Если его нет, значит APT не использует репозиторий Docker.
Также стоит вспомнить раздел про конфликтующие пакеты. Старые docker.io, containerd, runc или альтернативные компоненты могут мешать установке официальных пакетов.
Проверить их можно так: dpkg -l | grep -E 'docker|containerd|runc'
Но лучше не начинать удалять всё подряд. Сначала смотрите конкретную ошибку APT и уже по ней определяйте конфликтующий пакет.
Контейнер запущен, но сервис недоступен
Это особенно коварная ситуация.
Выполняем: docker ps
Контейнер находится в состоянии Up, а сайт в браузере все равно не открывается.
В таком случае сам факт Up говорит только о том, что основной процесс контейнера работает. Он не гарантирует, что приложение доступно снаружи.
Начнем с проверки сервиса прямо с VPS: curl http://127.0.0.1:8080
Если локально Nginx отвечает, значит контейнер и приложение, скорее всего, работают.
Тогда проверяем публикацию порта: docker ps
Для нашего примера должна присутствовать запись вроде: 0.0.0.0:8080->80/tcp
Она означает, что порт 8080 VPS связан с портом 80 контейнера.
Если порт опубликован и локальный curl работает, а браузер с внешнего компьютера нет, причина обычно находится уже вне контейнера:
- Firewall на VPS;
- Security Group у облачного провайдера;
- Закрытый внешний порт;
- Неверный IP или порт в адресе.
Если же не отвечает даже curl http://127.0.0.1:8080, то смотрим логи: docker logs CONTAINER_NAME (или для Compose: docker compose logs)
Получается простая схема:

Идём дальше.
Docker или контейнеры не запускаются после reboot
Последняя типовая проблема связана с перезагрузкой VPS.
Здесь снова важно разделить два уровня, которые мы уже обсуждали:

Сначала проверяем сам Docker:
systemctl is-enabled docker systemctl is-active docker
В норме ожидаем:
enabled
activeЕсли Docker не включен в автозагрузку: sudo systemctl enable --now docker
Если daemon после reboot не стартовал:
sudo systemctl status docker --no-pager sudo journalctl -u docker --no-pager -n 100
Но допустим, Docker уже активен, а контейнера по прежнему нет.
Проверим все контейнеры: docker ps -a
Если нужный контейнер существует, но находится в состоянии Exited, проблема может быть в restart policy.
Для Compose-проекта проверим конфигурацию: restart: unless-stopped
И состояние: docker compose ps
Если контейнер должен был стартовать автоматически, но снова завершился, смотрим его логи: docker compose logs
Или: docker logs CONTAINER_NAME
То есть после reboot диагностика идет в том же порядке:

На этом большая часть типовых проблем уже сводится к простой и понятной последовательности действий.
Финальная проверка
Мы прошли установку Docker Engine, проверили Compose, запустили тестовые контейнеры, настроили работу без sudo, автозапуск после reboot и разобрали основные команды.
Перед завершением убедимся, что всё действительно работает так, как должно:
| Что проверяем | Команда |
| Docker daemon запущен | systemctl is-active docker |
| Docker включен в автозагрузку | systemctl is-enabled docker |
| Docker CLI доступен | docker --version |
| Compose установлен | docker compose version |
| Контейнеры видны без sudo | docker ps |
Для нашего тестового Compose-проекта отдельно проверим сам сервис:
| Что проверяем | Ожидаемый результат |
| docker compose ps | Nginx находится в состоянии Up |
| curl http://127.0.0.1:8080 | Сервер возвращает HTML страницы Nginx |
| Перезагрузка VPS | Docker и Nginx запускаются снова автоматически |
Если все эти проверки проходят успешно, базовая настройка Docker завершена.
Заключение

На этом наш стенд готов к нормальной работе.
Мы установили Docker Engine и Compose из официального репозитория, запустили первый контейнер и Compose-проект, настроили работу без постоянного sudo, разобрались с автозапуском, обновлениями, базовыми командами и типовыми ошибками.
Этого уже достаточно, чтобы использовать Ubuntu VPS как основу для контейнерных проектов. Дальше всё зависит от конкретной задачи: вместо тестового Nginx можно запускать приложение, подключать базу данных, Redis, reverse proxy и другие сервисы, описывая их в одном Compose-проекте.
Спасибо за внимание!
FAQ
Можно ли установить Docker на Ubuntu командой apt install docker.io?
Можно, но в этом руководстве мы используем официальный APT-репозиторий Docker. Так Docker Engine, CLI, Buildx и Compose устанавливаются из одного официального источника и затем обновляются через APT. Официальная документация Docker также рекомендует перед такой установкой удалить конфликтующие пакеты дистрибутива.
Чем docker compose отличается от docker-compose?
docker compose — современный Docker Compose, работающий как плагин Docker CLI. Вариант docker-compose относится к отдельной standalone-версии и сейчас считается legacy-подходом. Для новой установки на Linux лучше использовать Compose plugin.Обязательно ли каждый раз использовать sudo?
Нет. Пользователя можно добавить в системную группу docker, после чего команды Docker будут работать без постоянного sudo.
Но такое удобство дает пользователю практически root-level privileges, поэтому добавлять в группу docker стоит только доверенные учетные записи.
Запускается ли Docker автоматически после перезагрузки Ubuntu?
На Ubuntu Docker service обычно включается в автозагрузку после установки. Проверить это можно командой: systemctl is-enabled docker
При необходимости автозапуск включается через: sudo systemctl enable docker
При этом запуск Docker daemon и автоматический запуск конкретных контейнеров — разные вещи. Для контейнеров отдельно настраивается restart policy.
Почему контейнер находится в состоянии Up, но сайт не открывается?
Статус Up означает, что основной процесс внутри контейнера работает. Он не гарантирует доступность приложения извне.
Проверьте опубликованные порты через: docker ps
затем попробуйте обратиться к сервису непосредственно с VPS. Если локально он отвечает, а извне нет, стоит проверить firewall, Security Group и доступность нужного TCP-порта.
Как проверить, что Docker установлен правильно?
Минимальная проверка выглядит так:
docker --version docker compose version systemctl is-active docker docker run hello-world
Если Docker daemon находится в состоянии active, Compose показывает версию, а hello-world успешно завершается с приветственным сообщением, базовая установка работает корректно.



