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

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 # удалить (освободите порт перед следующим шагом)

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.