Как установить и настроить Docker на Ubuntu 24.04/26.04 LTS

Валерий Волков

Время прочтения 35 минут

В этом руководстве мы установим 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 images

Containerd.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-plugin

APT покажет список зависимостей, скачает пакеты из подключенного репозитория 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:

  1. Проверяет, есть ли нужный образ на сервере;
  2. Если образа нет — скачивает его из registry;
  3. Создает новый контейнер;
  4. Подготавливает его файловую систему;
  5. Настраивает сеть;
  6. Запускает процесс, заданный внутри образа.

Упрощенно:

Важно различать 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 успешно завершается с приветственным сообщением, базовая установка работает корректно.

Список источников

  1. Docker Docs — Install Docker Engine on Ubuntu
  2. Docker Docs — Linux post-installation steps for Docker Engine
  3. Docker Docs — Install the Docker Compose plugin
  4. Docker Docs — Start containers automatically

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами