Top.Mail.Ru

Docker и Docker Compose на VPS с Ubuntu: установка и первый запуск

1
Docker и Docker Compose на VPS с Ubuntu: установка и первый запуск

Docker на VPS ставят из официального репозитория Docker, а не из пакета docker.io, который лежит в Ubuntu: версия там свежее, а Compose приходит готовым плагином. Установка — это несколько команд, которые можно копировать подряд, ниже они разобраны по шагам. А дальше идёт то, из-за чего новички обычно теряют вечер: первый проект на Compose, который переживает перезагрузку, порты, которые Docker открывает в обход файрвола, журналы без ограничения размера и диск, который незаметно заполняется.

Команды из статьи мы выполняли на Ubuntu 24.04 (Docker 29.9.2 и Compose 5.6.1 на 30 октября 2026 года). В документации Docker та же схема указана для Ubuntu 22.04 и 26.04, но на них мы не пробовали.

Зачем Docker на VPS

Docker запускает программу вместе со всем, что ей нужно, в изолированной среде — контейнере. Контейнер создаётся из образа: это готовый шаблон с программой и её библиотеками. Контейнер не мешает соседям: у него своя файловая система и свои версии библиотек, а убрать его можно одной командой, не оставив мусора в системе. Поэтому в Docker удобно держать то, что капризно ставится обычным способом: n8n, Nextcloud, базы данных, ботов.

Docker Compose — надстройка над Docker. В одном файле вы описываете, какие контейнеры нужны, как они связаны и где хранят данные, а потом поднимаете всё одной командой. Без Compose для каждого контейнера пришлось бы помнить длинную строку с десятком флагов.

Самому Docker много памяти не нужно. Диск расходуется быстрее: образ n8n весит 1,81 ГБ, образ Nextcloud 2,13 ГБ, и на небольшом диске это заметно. Ниже есть раздел про то, как смотреть и чистить занятое место.

Что понадобится

  • VPS с 64-битной Ubuntu 22.04, 24.04 или 26.04. Подойдёт любой тариф VPS UFO.Hosting. Для пары небольших контейнеров хватит и скромной конфигурации, а если планируете несколько сервисов, берите от 2 ГБ памяти. Как выбрать тариф, описано в отдельной статье.
  • Доступ по SSH. Если ещё не подключались, начните со статьи про SSH.
  • Права sudo или вход под root. В командах ниже стоит sudo, под root его можно убрать.

Установка Docker из официального репозитория

Репозиторий — это сервер, с которого apt берёт пакеты. Сначала уберите старые версии Docker, если они уже были. На новом сервере шаг можно пропустить, а если каких-то пакетов нет, apt просто сообщит, что удалять нечего:

sudo apt remove docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc

Теперь подключаем репозиторий Docker. Два пакета, которые мы ставим первыми, нужны, чтобы безопасно скачать ключ по HTTPS. Ключ позволяет apt убедиться, что пакеты выпущены именно Docker и не подменены по дороге:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Команда install -m 0755 -d создаёт каталог для ключей с нужными правами, а chmod a+r разрешает apt этот ключ читать. Дальше описание самого репозитория. Версию Ubuntu и архитектуру процессора команда подставит сама, править в ней ничего не нужно, а строка Signed-By привязывает репозиторий к скачанному ключу:

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

Если в выводе apt update есть строка с download.docker.com, репозиторий подключён. Ставим Docker:

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Что здесь ставится. docker-ce — сам Docker, фоновая служба (её называют демоном). docker-ce-cli — команда docker, которую вы вводите в терминале. containerd.io — движок, который запускает контейнеры. docker-compose-plugin — Compose. Пакет docker-buildx-plugin нужен для сборки собственных образов и в этой статье не пригодится. Служба docker после установки уже включена в автозапуск и работает:

systemctl is-enabled docker
enabled
systemctl is-active docker
active
docker --version
Docker version 29.9.2, build a82e528
docker compose version
Docker Compose version v5.6.1

Пишите docker compose через пробел. Команда docker-compose через дефис — старая отдельная программа первой версии, её поддержку прекратили в 2023 году. Если инструкция из интернета просит поставить именно её, ищите другую.

Последняя проверка — запустить контейнер, который сам сообщает, что всё работает:

sudo docker run --rm hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.

Docker скачал образ, запустил контейнер, получил от него текст и удалил контейнер (за это отвечает --rm). Сам образ остался на диске и пригодится при следующем запуске.

Docker без sudo: чем это опасно

Если запустить docker ps под обычным пользователем, будет отказ:

permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

Лечится добавлением пользователя в группу docker: sudo usermod -aG docker ваш_пользователь. Ключ -aG добавляет пользователя в группу, не удаляя из остальных. После этого нужно выйти и войти по SSH заново, потому что группы читаются только при входе. Делайте так осознанно: участник группы docker фактически равен root. Он может запустить контейнер, подключить в него всю файловую систему сервера и менять там что угодно. На личном сервере это нормально. А давать эту группу другим людям или программам стоит так же осторожно, как пароль root.

Первый проект на Compose

Возьмём самый простой пример: nginx отдаёт одну страницу из папки. Создайте каталог проекта и файл страницы:

mkdir -p ~/hello/site && cd ~/hello
echo '<!doctype html><meta charset="utf-8"><h1>Привет из контейнера</h1>' > site/index.html

Теперь файл compose.yaml (создайте его любым редактором, например nano compose.yaml). Так сегодня называют файл Compose, старое имя docker-compose.yml тоже работает.

services:
  web:
    image: nginx:1.28-alpine
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./site:/usr/share/nginx/html:ro

По строкам:

  • services — список контейнеров проекта. У нас один, он называется web.
  • image: nginx:1.28-alpine — из какого образа его создать. После двоеточия стоит тег, метка версии. Её лучше указывать явно: с тегом latest при очередном обновлении можно незаметно получить новую крупную версию с несовместимыми изменениями. Слово alpine означает облегчённую сборку образа.
  • restart: unless-stopped — поднимать контейнер заново, если он упал или сервер перезагрузился, кроме случая, когда вы сами его остановили. Без этой строки после перезагрузки сервера контейнер сам не запустится.
  • ports — какой порт сервера перенаправить в контейнер. Запись 127.0.0.1:8080:80 значит: порт 80 внутри контейнера доступен только на самом сервере, на порту 8080. Почему не просто 8080:80, объясняет раздел про файрвол ниже.
  • volumes — какую папку сервера показать контейнеру. Наша страница лежит в ./site, а nginx ищет файлы в /usr/share/nginx/html. Приписка :ro разрешает только читать. Так подключают и обычные папки, и тома (volumes) — хранилища данных вне контейнера, которые остаются, когда контейнер удалён. Именованные тома мы встретим в следующих статьях цикла.

Проверим файл и запустим:

docker compose config --quiet && echo OK
OK
docker compose up -d
 Container hello-web-1 Created
 Container hello-web-1 Started

Ключ -d оставляет контейнер работать в фоне. Без него Compose займёт терминал и остановит всё, когда вы закроете окно. Команда config --quiet ничего не запускает, а только проверяет файл, и молчит, если ошибок нет.

docker compose ps
NAME          IMAGE               COMMAND                  SERVICE   CREATED         STATUS         PORTS
hello-web-1   nginx:1.28-alpine   "/docker-entrypoint.…"   web       2 seconds ago   Up 2 seconds   127.0.0.1:8080->80/tcp

curl -s localhost:8080
<!doctype html><meta charset="utf-8"><h1>Привет из контейнера</h1>

Страница отвечает. Из браузера на вашем компьютере её не открыть: порт привязан к 127.0.0.1, поэтому проверяем с самого сервера. Откуда имя hello-web-1? Compose собирает его из названия папки проекта (hello), названия сервиса (web) и номера экземпляра. Название папки поэтому важно: два проекта в одноимённых папках будут мешать друг другу.

Перезагрузка, обновление, остановка

Чтобы убедиться, что restart: unless-stopped работает, перезагрузите сервер (sudo reboot) и после входа выполните docker compose ps в папке проекта. Контейнер должен снова быть в статусе Up. Сама служба Docker при загрузке стартует сама, это видно выше по is-enabled.

Повседневных команд немного. Выполняйте их в папке, где лежит compose.yaml:

Команда Что делает
docker compose ps Показывает контейнеры проекта и их статус.
docker compose logs -f web Выводит журнал контейнера web и следит за новыми строками. Выход — Ctrl+C.
docker compose restart web Перезапускает один контейнер.
docker compose exec web sh Открывает оболочку внутри работающего контейнера.
docker compose pull, затем docker compose up -d Обновление: сначала скачивает свежие образы, потом пересоздаёт те контейнеры, у которых образ изменился. Остальные не трогает.
docker compose down Останавливает и удаляет контейнеры и сеть проекта. Образы и данные в томах остаются.

У down есть опасная версия. Обычный docker compose down оставляет именованный том с данными на месте, а docker compose down -v удаляет и его. На тестовом томе мы это проверили: после down том остался в списке, после down -v исчез. Если в нём лежала база данных, вернуть её будет нечем. Пишите -v только тогда, когда хотите начать с нуля.

Больше команд самого Docker собрано в гайде по основным командам.

Файрвол и порты: чего не ждёшь от Docker

Здесь легко попасться и опытному администратору. Допустим, вы включили ufw и разрешили в нём только SSH. Логика подсказывает, что снаружи закрыто всё остальное. С Docker это не так: когда контейнер публикует порт, Docker сам добавляет правила в iptables, сетевой фильтр ядра, и они срабатывают раньше правил ufw. В документации Docker сказано прямо: для ufw трафик к опубликованным портам обходит его настройки.

Проверили на сервере. В ufw разрешён только порт 22:

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)

Контейнер с nginx опубликован как "80:80". Запрос с другого сервера (ключ -w печатает код ответа, а 000 значит, что соединения не было совсем):

curl -s -o /dev/null -w "http=%{http_code}\n" http://IP_СЕРВЕРА/
http=200

Порт 80 в ufw не разрешён, а сайт открыт всему интернету. Теперь тот же контейнер, но в compose.yaml порт записан как "127.0.0.1:80:80":

curl -s -o /dev/null -w "http=%{http_code}\n" http://IP_СЕРВЕРА/
http=000

Снаружи порт закрыт, а на самом сервере curl http://127.0.0.1/ возвращает 200. Для проверки мы брали порт 80: на нашем тестовом сервере нестандартные порты вроде 8080 снаружи не открывались сетевым фильтром, и это сбивало картину. У вас фильтр может отличаться, на сам обход ufw это не влияет.

Итог: порт, который не должен быть виден из интернета (база данных, панель, внутренний сервис), публикуйте только на 127.0.0.1. А то, что должно быть открыто, пропускайте через обратный прокси. Это программа, которая принимает запросы из интернета на портах 80 и 443 и передаёт их нужным контейнерам. Именно так устроены следующие статьи цикла, про n8n и Nextcloud.

Оговорка про версии. По документации Docker, до версии 28.0.0 порт, опубликованный на 127.0.0.1, могли достать другие машины из того же сетевого сегмента. В Docker 29, который ставит эта статья, такого уже нет. Если у вас docker.io из Ubuntu, проверьте версию командой docker --version.

Свои правила для контейнеров можно писать и отдельно: в документации Docker для этого предусмотрена цепочка DOCKER-USER. Но проще и надёжнее начать с привязки к 127.0.0.1. А настройку ufw всё равно стоит сделать: он по-прежнему защищает сам сервер, просто не контейнеры.

Журналы: настройте ротацию сразу

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

docker info --format '{{.LoggingDriver}}'
json-file

Драйвер json-file без ограничения размера на небольшом диске — плохой выбор. Создайте файл /etc/docker/daemon.json (по умолчанию его нет), например командой sudo nano /etc/docker/daemon.json:

{
  "log-driver": "local",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

Драйвер local хранит журнал в компактном виде, а max-size и max-file ограничивают его тремя файлами по 10 МБ. Применить:

sudo systemctl restart docker
docker compose up -d --force-recreate

После перезапуска Docker настройка действует для новых контейнеров. У уже существующих остаётся старая, поэтому мы их пересоздаём ключом --force-recreate (команду запускайте в папке проекта). Проверим результат:

docker inspect -f '{{json .HostConfig.LogConfig}}' hello-web-1
{"Type":"local","Config":{"max-file":"3","max-size":"10m"}}

Есть оговорка: файлы в формате local руками не прочитать. Это не мешает, docker compose logs работает как обычно.

Место на диске

Образы, остановленные контейнеры и тома копятся. Посмотреть, что занимает место:

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          2         1         93.42MB   25.87kB (0%)
Containers      1         1         81.92kB   0B (0%)
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B

В столбце RECLAIMABLE написано, сколько можно освободить. Чистят так:

  • docker system prune убирает остановленные контейнеры, неиспользуемые сети и «висячие» образы без имени. Команда просит подтверждения, запущенные контейнеры и тома не трогает. Остановленный контейнер, который вы собирались запустить снова, она всё же удалит, поэтому читайте список перед согласием.
  • docker system prune -a дополнительно убирает все образы, которые не нужны ни одному контейнеру. Нужные потом скачаются заново.
  • Ключ --volumes добавляет удаление томов, на которые не ссылается ни один контейнер, то есть данных. Пользуйтесь им, только если точно знаете, что в этих томах ничего ценного нет.

Если что-то пошло не так

Порт занят. Текст ошибки зависит от того, кто его занял. Если это другой контейнер, Docker пишет так:

docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint clash (...): Bind for 127.0.0.1:80 failed: port is already allocated

А если порт держит программа на самом сервере, например nginx или Apache, ответ другой:

docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint clash2 (...): failed to bind host port 0.0.0.0:8099/tcp: address already in use

Узнать, кто занял порт, можно командой sudo ss -tlnp | grep ':80 '. Она показывает программы, которые слушают TCP-порты. Дальше остановите нарушителя или выберите другой порт.

Предупреждение про version. Во многих старых инструкциях compose.yaml начинается со строки version: "3.8". Современный Compose пишет об этом так:

level=warning msg="/opt/vol/compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"

Это не ошибка. Строку можно просто удалить, на работу она не влияет.

Если Docker отвечает «permission denied», вернитесь к разделу про sudo и группу docker.

Частые вопросы

Нужен ли отдельный пакет docker-compose?

Нет. Compose ставится плагином вместе с Docker (пакет docker-compose-plugin) и запускается командой docker compose через пробел.

Можно ли поставить Docker командой apt install docker.io?

Можно, это рабочий вариант. На Ubuntu 24.04 в нём Docker 29.1.3 против 29.9.2 у Docker, а Compose идёт отдельным пакетом docker-compose-v2 версии 2.40.3 (в репозитории Docker сегодня 5.6.1). Для учебного сервера разницу вы не заметите, но обновления будете получать позже. Мы берём репозиторий Docker: Docker и Compose обновляются одной командой apt upgrade.

Контейнер упал, что смотреть?

Сначала docker compose ps -a, чтобы увидеть статус, потом docker compose logs --tail 50 имя_сервиса, чтобы прочитать последние строки журнала. Причина почти всегда написана в конце.

Что дальше

Когда Docker стоит, а порты под контролем, на нём можно запускать готовые сервисы. В следующих статьях цикла мы ставим n8n с HTTPS и автозапуском и собственное облако на Nextcloud. Данные из контейнеров стоит сразу включить в резервные копии.

Похожее

Все статьи
Стеклянное хранилище в форме облака с папками и файлами, тарелка лучом открывает замок

Nextcloud на VPS: своё облако через Docker Compose

Nextcloud — это собственное облако: файлы с синхронизацией на телефон и компьютер, календарь, контакты, общий доступ по ссылке. На VPS его поднимают через Docker Compose из пяти контейнеров (подробности ниже): Nextcloud, база PostgreSQL, кэш Redis, контейнер для фоновых задач и…

Схема автоматизации на полу дата-центра: светящиеся узлы с иконками соединены линиями, тарелка запускает цепочку

n8n на VPS: установка через Docker Compose, HTTPS и автозапуск

n8n на своём VPS поднимается через Docker Compose за один вечер: два контейнера (сам n8n и обратный прокси Caddy, который сам выпускает HTTPS-сертификат), три небольших файла и одна команда запуска. Ниже — готовые файлы, объяснение каждой переменной, первый сценарий с…