Установка Docker и Docker Compose на Ubuntu и Debian

Ставим Docker из официального репозитория, запускаем первый контейнер и WordPress через Compose, разбираемся с портами и UFW, ограничиваем логи. Проверено на Debian 12 и Ubuntu 24.04.

2 ноября 2026 г.·4 мин чтения·docker, compose, ubuntu, debian

Docker упаковывает приложение вместе со всем окружением в образ, из которого запускается контейнер — изолированный процесс со своей файловой системой и сетью. Две версии PHP на одном сервере, база одной версии для одного проекта и другой для второго, перенос приложения на новый сервер одной командой — всё это становится обыденностью. Это не виртуальная машина: ядро Linux общее, запуск занимает доли секунды.

Для экспериментов подойдёт любой VPS с Ubuntu или Debian; на tanaka.host сервер можно взять на день-два, пройти инструкцию и удалить без лишних трат.

Два следствия, которые нужно понимать с самого начала. Данные внутри контейнера исчезают при его удалении, поэтому всё ценное живёт в томах на диске сервера. Контейнер по умолчанию недоступен снаружи, поэтому порты публикуются явно. Docker Compose описывает набор контейнеров, томов и портов в одном YAML-файле; контейнеры одного проекта видят друг друга по именам сервисов.

Что нужно: Ubuntu 22.04/24.04 или Debian 12/13 и SSH под root или пользователем с sudo. Сам Docker занимает около 200 МБ памяти.

1. Установка

Не ставьте docker.io из стандартного репозитория: он отстаёт на несколько версий и не включает Compose. Нужен официальный репозиторий Docker.

Сначала уберите пакеты, которые могут конфликтовать (если их нет, команда просто ничего не сделает):

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)

Добавьте репозиторий. Команды для Debian; на Ubuntu замените linux/debian на linux/ubuntu в обеих строках:

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

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

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

Быстрая альтернатива для тестового сервера — официальный скрипт curl -fsSL https://get.docker.com | sh. Он делает то же самое, но вручную вы хотя бы видите, что именно добавилось в систему. Обновляется Docker в обоих случаях через apt upgrade, повторно скрипт запускать не нужно.

2. Проверка

docker --version && docker compose version
sudo docker run hello-world

Compose теперь — подкоманда docker compose (через пробел). Старый docker-compose с дефисом ставить не нужно.

Запуск без sudo

Сокет Docker принадлежит группе docker. Добавьте в неё своего пользователя и перезайдите:

sudo usermod -aG docker $USER
newgrp docker
docker run hello-world

Член группы docker фактически получает root на сервере (контейнер может смонтировать /). Относитесь к этому как к sudo без пароля.

3. Первый контейнер

docker run -d --name web --restart unless-stopped -p 8080:80 nginx
  • -d — в фоне;
  • --name web — имя вместо случайного;
  • --restart unless-stopped — подниматься после перезагрузки сервера. Без этого флага контейнер после reboot не запустится;
  • -p 8080:80 — порт 8080 сервера → порт 80 в контейнере.

Откройте http://IP_СЕРВЕРА:8080 — «Welcome to nginx!». Управление:

docker ps                 # запущенные
docker logs -f web        # логи
docker stop web
docker start web
docker rm -f web          # удалить (освободите порт перед следующим шагом)

docker ps: контейнер работает, порт опубликован

4. Приложение через Compose: WordPress + MariaDB

sudo mkdir -p /opt/wordpress && sudo chown $USER: /opt/wordpress && cd /opt/wordpress
echo "DB_PASSWORD=$(openssl rand -hex 16)" > .env
chmod 600 .env
nano compose.yaml
services:
  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:latest
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html

volumes:
  db_data:
  wp_data:
docker compose up -d
docker compose ps

У обоих сервисов статус Up. Откройте http://IP_СЕРВЕРА:8080 — установщик WordPress.

Это учебный пример. Для настоящего сайта нужен домен, обратный прокси с HTTPS и порт, опубликованный только на 127.0.0.1. И не оставляйте открытый установщик: кто первым его пройдёт, тот и администратор. Убрать всё: docker compose down -v.

docker compose logs -f        # логи всех сервисов
docker compose down           # остановить и удалить контейнеры, тома остаются
docker compose down -v        # и тома тоже — данные пропадут

Порты и брандмауэр

Главная ловушка Docker. Опубликованный порт добавляется прямо в iptables и срабатывает до правил UFW. Порт 8080 из примера выше открыт всему интернету, хотя в ufw status его нет.

Поэтому служебные интерфейсы публикуйте только на локальный адрес:

docker run -d --name web-local -p 127.0.0.1:8081:80 nginx

В Compose — "127.0.0.1:8081:80". Доступ к таким сервисам — через обратный прокси с HTTPS или SSH-туннель. Базы данных внутри Compose-проекта публиковать не нужно вовсе: WordPress обращается к db по внутренней сети.

Что реально слушает сервер: sudo ss -tlnp. Строки с 0.0.0.0: открыты миру.

Где лежат данные

Тома именуются по папке проекта: wordpress_db_data, wordpress_wp_data. Физически — в /var/lib/docker/volumes/.

docker volume ls
docker volume inspect wordpress_wp_data --format '{{.Mountpoint}}'

docker compose down тома не трогает, down -v и docker volume rm удаляют. Включайте тома в бэкап; базы — дампом через docker compose exec, а не копированием файлов.

Обновление

Образы:

cd /opt/wordpress
docker compose pull
docker compose up -d
docker image prune -f

Сам Docker — через apt upgrade. Контейнеры на пару секунд остановятся и поднимутся сами благодаря restart: unless-stopped.

Логи и место на диске

Без ограничений логи контейнеров растут до гигабайт. Задайте лимит для новых контейнеров:

sudo tee /etc/docker/daemon.json <<'EOF2'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF2
sudo systemctl restart docker

Уже существующие контейнеры пересоздайте: docker compose up -d --force-recreate.

Диск:

docker system df           # сколько занимают образы, контейнеры, тома
docker system prune        # удалить остановленные контейнеры и неиспользуемые сети
docker image prune -a      # удалить все образы, которыми не пользуется ни один контейнер

Частые ошибки

  • docker-compose: command not found — это старый Compose v1. Используйте docker compose.
  • permission denied while trying to connect to the docker API — пользователь не в группе docker или не перезашёл после добавления.
  • Bind for 0.0.0.0:8080 failed: port is already allocated — порт занят другим контейнером или программой: docker ps --format '{{.Names}} {{.Ports}}' и sudo ss -tlnp | grep 8080.
  • Cannot connect to the Docker daemon — служба не запущена: sudo systemctl enable --now docker, причины в journalctl -u docker -n 50.
  • Контейнер не поднялся после перезагрузки — нет политики перезапуска: docker update --restart unless-stopped ИМЯ.
  • NO_PUBKEY при apt update — ключ не скачался, повторите curl ключа из шага 1.
  • Conflicting values set for option Signed-By — репозиторий добавлен дважды (старый docker.list и новый docker.sources). Удалите /etc/apt/sources.list.d/docker.list.

Дальше — шпаргалка по Docker Compose и, если понадобится, полное удаление Docker.