gitaspen docs

Docker на сервере: установка, настройка, обслуживание

Инструкция доводит чистый сервер до состояния «Docker и Compose работают, приложения публикуются безопасно, диск не забивается логами». Отдельным разделом — полная переустановка, когда прежняя установка сломана.

7 минут

Инструкция доводит чистый сервер до состояния «Docker и Compose работают, приложения публикуются безопасно, диск не забивается логами». Отдельным разделом — полная переустановка, когда прежняя установка сломана.

Что нужно до начала: сервер Ubuntu 22.04/24.04 или Debian 12 с доступом sudo.

Место в цепочке

Откуда пришлиЭтот документКуда ведёт
чистый серверDocker, Compose, правила публикации портов, ротация логовзапуск приложения и его шлюза, затем домен и TLS

Что этот документ оставляет следующему звену: рабочий Docker и правило публикации портов — приложение и его шлюз публикуются на loopback, поэтому наружу их выставляет только nginx хоста, завершающий TLS. Нарушение этого правила делает настройку домена бессмысленной: сервис остаётся доступен по http://example.com:8000 в обход шлюза и шифрования.


Шаг 1. Установка

Ставится официальный репозиторий Docker: в стандартных репозиториях дистрибутива версия отстаёт, а пакет docker-compose из них — устаревшая отдельная утилита вместо плагина docker compose.

bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

sudo systemctl enable --now docker

Для Debian замените ubuntu на debian в обеих ссылках.

Проверка:
docker --version           # ожидается версия Docker Engine
docker compose version     # ожидается версия Compose v2 (команда из двух слов, без дефиса)
sudo docker run --rm hello-world

Ожидается: контейнер hello-world печатает приветствие и завершается. Ошибка permission denied при обращении к сокету — см. шаг 2.


Шаг 2. Запуск без sudo

Добавьте пользователя в группу docker, чтобы не писать sudo перед каждой командой:

bash
sudo usermod -aG docker "$USER"
newgrp docker      # применить в текущей сессии; иначе перелогиньтесь
Проверка:
docker ps          # ожидается таблица контейнеров без ошибки прав

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


Шаг 3. Ротация логов

По умолчанию Docker пишет логи контейнеров без ограничения размера, и на долго работающем сервере они занимают весь диск. Ограничение задаётся один раз для всех контейнеров:

bash
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

sudo systemctl restart docker
Проверка:
docker info --format '{{.LoggingDriver}}'    # ожидается: json-file

Настройка действует на контейнеры, созданные после перезапуска демона; уже запущенные нужно пересоздать.


Шаг 4. Публикация портов

Порт публикуется с явным адресом. Это главное правило эксплуатации: от него зависит, доступен ли сервис в обход шлюза и TLS.

yaml
services:
  gateway:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8000:80"     # доступно только с этой машины
      # - "8000:80"             # доступно всему интернету

Запись без адреса ("8000:80") публикует порт на всех интерфейсах. Правила ufw при этом не действуют: Docker добавляет свои правила в iptables в цепочку, которая обрабатывается раньше, поэтому порт остаётся открытым снаружи, даже если ufw его запрещает.

Проверка — с другой машины:
curl -m 5 -I http://example.com:8000     # ожидается таймаут или отказ соединения

Если ответ пришёл — сервис доступен напрямую, в обход шлюза. Исправьте публикацию и повторите.

Проверка на сервере:
sudo ss -ltnp | grep :8000               # ожидается адрес 127.0.0.1:8000
docker compose ps --format 'table {{.Service}}\t{{.Ports}}'

ports и expose — разные вещи

ДирективаЧто делаетКому доступен порт
ports: ["127.0.0.1:8000:80"]публикует порт на хостепроцессам этой машины
ports: ["8000:80"]публикует порт на всех интерфейсахвсему интернету
expose: ["8000"]ничего не публикует, только объявляет портконтейнерам в общих сетях

Внутри одной сети Compose сервисы обращаются друг к другу по имени сервиса и внутреннему порту — публикация для этого не нужна. Публикуют только то, что должно быть видно с хоста.

Отсюда правило для микросервиса: он не публикует порты вовсе, у него expose. Наружу его выводит шлюз, стоящий перед ним. Публикация порта микросервиса — распространённая ошибка: она открывает обход шлюза со всеми проверками, которые на шлюзе настроены.


Шаг 5. Автозапуск после перезагрузки

Сам Docker включён (systemctl enable), но контейнеры поднимутся, только если это задано политикой перезапуска:

yaml
services:
  gateway:
    restart: unless-stopped
Проверка:
sudo reboot
# после подключения:
docker compose ps      # ожидается: сервисы в состоянии running

Обслуживание

Место на диске. Неиспользуемые образы и слои копятся при каждой сборке:

bash
docker system df                    # что сколько занимает
docker image prune -f               # удалить образы без тегов
docker builder prune -f             # очистить кэш сборки

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

bash
docker system prune -af --volumes   # удаляет ВСЁ неиспользуемое, включая тома с данными

Выполняйте её только на машине, где нет данных, которые нужно сохранить, и после проверки docker volume ls.

Логи контейнера:
docker compose logs -f --tail=100 gateway

Переустановка со сносом

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

bash
docker ps -aq | xargs -r docker stop
docker system prune -af --volumes

sudo systemctl stop docker docker.socket
sudo apt-get purge -y docker docker-engine docker.io containerd runc \
  docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker /var/lib/containerd /etc/docker
sudo rm -f /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.gpg
sudo apt-get autoremove -y

Далее — с шага 1.


Изменение параметров демона

Параметры демона задаются в /etc/docker/daemon.json (шаг 3). Если нужно поменять параметры запуска службы, не редактируйте /lib/systemd/system/docker.service: этот файл принадлежит пакету и будет заменён при первом же обновлении Docker. Используйте drop-in:

bash
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/override.conf >/dev/null <<'EOF'
[Service]
LimitNOFILE=1048576
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker
Проверка:
systemctl show docker -p LimitNOFILE     # ожидается заданное значение

Типичные отказы

ПризнакПричинаЧто делать
permission denied на /var/run/docker.sockпользователь не в группе dockerшаг 2, затем перелогиниться
docker-compose: command not foundожидается старая отдельная утилитаиспользовать docker compose (плагин v2)
порт доступен снаружи, хотя ufw его запрещаетпорт опубликован без адресауказать 127.0.0.1: в публикации (шаг 4)
контейнеры не поднялись после перезагрузкинет политики перезапускадобавить restart: unless-stopped
«нет места на устройстве» при сборкенакопился кэш и старые образыdocker system df, затем image prune и builder prune
логи занимают десятки гигабайтне ограничен размер логовшаг 3 и пересоздать контейнеры
после обновления Docker пропали правки службыправился файл пакетаперенести правки в drop-in override.conf
контейнер не видит соседа по именисервисы в разных сетях Composeпривести к одной сети либо обращаться по имени сервиса внутри общей сети
Инструкция не помогла?

Откройте исходник документа по ссылке «Предложить правку» — там же видно, что и когда в нём менялось.