Docker позволяет запускать программы в изолированных контейнерах, где уже собрано всё, что нужно для их работы. Многие популярные сервисы, от n8n и Nextcloud до игровых серверов и ботов, сегодня ставятся одной командой именно через Docker. В этой статье мы установим Docker и Docker Compose на сервер, запустим первый контейнер, поднимем сайт из двух контейнеров и разберём настройки, о которых обычно вспоминают слишком поздно. Все команды мы проверили на тестовом сервере с Debian 12, версия Docker на момент проверки 29.9.2, Docker Compose 5.6.1.
Что такое Docker и зачем он на сервере
Чтобы понять, какую задачу решает Docker, представим обычную установку программы на сервер. Допустим, нужно запустить веб-приложение, написанное на PHP, которому требуется определённая версия PHP, набор расширений и веб-сервер Apache. Всё это ставится через менеджер пакетов прямо в систему, и пока на сервере работает одно приложение, трудностей не возникает. Однако стоит добавить второе приложение, которому нужна другая версия PHP, как пакеты начинают конфликтовать между собой. Обновление системы может незаметно поменять версию библиотеки, от которой зависело одно из приложений, а перенос на другой сервер превращается в долгое повторение всех шагов установки по памяти.
Docker устраняет эту проблему тем, что упаковывает приложение вместе со всем его окружением в образ. Образ представляет собой неизменяемый набор файлов, в котором уже лежат нужная версия интерпретатора, библиотеки и настройки по умолчанию. Его собирают разработчики программы и публикуют в общем каталоге Docker Hub или в собственном реестре. Когда вы запускаете образ, Docker создаёт из него контейнер, то есть работающий процесс, который видит только свою файловую систему, свои сетевые интерфейсы и свой список процессов. При этом контейнер не является виртуальной машиной, ведь у него нет собственного ядра. Все контейнеры используют ядро Linux самого сервера, а изоляцию обеспечивают механизмы ядра, которые разграничивают процессы, сеть и файлы. Именно поэтому контейнер запускается за доли секунды и почти не тратит памяти сверх того, что нужно самой программе.
Из такого устройства вытекает важное следствие. Всё, что программа записывает внутри контейнера, исчезает вместе с ним, когда контейнер удаляют, например при обновлении на новую версию образа. Чтобы данные переживали пересоздание контейнера, их хранят в томах. Том находится на диске сервера и подключается внутрь контейнера по заданному пути, поэтому база данных или загруженные файлы остаются на месте, а сам контейнер можно спокойно выбросить и создать заново. Похожим образом устроена и сеть. По умолчанию контейнер недоступен снаружи, и чтобы открыть, например, веб-сайт из контейнера, нужно опубликовать порт, то есть сообщить Docker, что входящие соединения на определённый порт сервера следует передавать в контейнер.
Реальные сервисы редко состоят из одного контейнера. Сайту на WordPress нужна база данных, системе мониторинга нужны хранилище и веб-интерфейс, и каждый компонент удобнее держать в своём контейнере. Запускать их вручную длинными командами неудобно, поэтому существует Docker Compose. Он описывает все контейнеры приложения, их тома, порты и настройки в одном файле docker-compose.yml, после чего вся связка запускается, обновляется и останавливается одной командой. Контейнеры одного файла Compose автоматически попадают в общую сеть и находят друг друга по именам сервисов, так что WordPress обращается к базе просто по имени db, не зная её адреса.
Что понадобится
- Сервер с Ubuntu 24.04 или Debian 12. Сам Docker на тестовом сервере занял около 190 МБ памяти, поэтому он работает на любом нашем тарифе, а сколько памяти нужно в итоге, зависит от того, какие контейнеры вы запустите. Если сервера ещё нет, закажите его по инструкции.
- Подключение по SSH, например через Termius. Команды ниже выполняются от имени
root, поэтомуsudoв них не нужен.
1. Установите Docker
Ставить Docker из стандартных репозиториев Ubuntu и Debian не стоит, там часто лежит устаревшая версия без плагина Compose. Мы используем официальный репозиторий Docker, а добавить его можно двумя способами. Результат у них одинаковый, выберите любой.
Способ 1. Официальный скрипт одной командой
Этот способ самый быстрый. Скрипт сам определит систему, подключит репозиторий Docker, установит все пакеты и запустит службу.
# Скачиваем и запускаем официальный скрипт установки
curl -fsSL https://get.docker.com | shУстановка займёт около минуты. В конце скрипт выведет версию клиента и сервера Docker и строку INFO: Docker daemon enabled and started, это значит, что служба запущена и будет стартовать вместе с сервером.

Скрипт подключил репозиторий Docker и установил пакеты
Скрипт нужен только один раз
Сами разработчики Docker не советуют запускать его повторно для обновления. После установки Docker обновляется обычным apt upgrade, об этом ниже в разделе про обновление.
Способ 2. Подключить репозиторий вручную
Этот способ рекомендует официальная документация Docker для рабочих серверов. Вы выполняете те же шаги, что и скрипт, но видите каждый из них. Команды ниже для Debian.
# Ключ репозитория Docker
apt update
apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Сам репозиторий
tee /etc/apt/sources.list.d/docker.sources <<EOF
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
EOF
# Установка Docker и плагина Compose
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginНа Ubuntu команды те же, только в двух местах замените linux/debian на linux/ubuntu, в адресе ключа и в строке URIs.
2. Проверьте установку
Убедитесь, что Docker и Compose установлены, и запустите проверочный контейнер.
# Версии Docker и Compose
docker --version && docker compose version
# Проверочный контейнер
docker run hello-worldDocker не найдёт образ hello-world на сервере, скачает его из Docker Hub и запустит. Контейнер выведет приветствие Hello from Docker! и сразу завершится. Если вы видите этот текст, всё работает.

Docker и Compose установлены, проверочный контейнер отработал
3. Запустите первый контейнер
Для примера запустим веб-сервер nginx и откроем его страницу в браузере.
docker run -d --name web --restart unless-stopped -p 8080:80 nginxРазберём, что означает каждая часть команды.
-dзапускает контейнер в фоне, чтобы терминал остался свободным.--name webдаёт контейнеру понятное имя. Без него Docker придумает случайное.--restart unless-stoppedзаставляет Docker поднимать контейнер после перезагрузки сервера. Мы проверили, что без этого параметра контейнер после перезапуска Docker так и остаётся выключенным.-p 8080:80публикует порт. Соединения на порт 8080 сервера уходят на порт 80 внутри контейнера.nginxэто имя образа в Docker Hub.
Откройте в браузере адрес http://IP-адрес-сервера:8080. Должна появиться страница Welcome to nginx!.

nginx работает в контейнере и отвечает на порту 8080
Теперь можно посмотреть на контейнер и попробовать основные команды.
# Список работающих контейнеров
docker ps
# Логи контейнера, выход по Ctrl+C
docker logs -f web
# Остановить и снова запустить
docker stop web
docker start web
# Удалить контейнер вместе с остановкой
docker rm -f webУдалите контейнер web перед следующим шагом, иначе порт 8080 будет занят.
4. Запустите приложение через Docker Compose
Соберём сайт на WordPress с базой данных MariaDB. Это два контейнера, которые должны находить друг друга, хранить данные и запускаться вместе, поэтому на этом примере хорошо видно, зачем нужен Compose.
Создайте папку проекта и файл
.envсо случайным паролем для базы.bashmkdir -p /opt/wordpress && cd /opt/wordpress echo "DB_PASSWORD=$(openssl rand -hex 16)" > .envСоздайте файл
docker-compose.yml.bashcat > docker-compose.yml <<'EOF' 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: EOFЗапустите оба контейнера и проверьте их состояние.
bashdocker compose up -d docker compose ps
Compose создаст общую сеть, два тома и два контейнера. В столбце STATUS у обоих должно быть Up.

Сеть, тома и оба контейнера созданы одной командой
Откройте http://IP-адрес-сервера:8080, и вы увидите первый шаг установки WordPress с выбором языка. Раз эта страница открылась, WordPress уже подключился к базе. Пароль к ней Compose взял из файла .env, а адрес базы WordPress нашёл по имени сервиса db.

WordPress работает и уже видит свою базу данных
Это учебный пример
Для настоящего сайта нужен домен и HTTPS. Не оставляйте установщик WordPress открытым на порту 8080, иначе завершить установку и стать администратором сможет любой, кто первым откроет страницу. Чтобы убрать пример, выполните в папке проекта docker compose down -v.
Остальные команды Compose выполняются в папке, где лежит docker-compose.yml.
# Логи всех контейнеров проекта
docker compose logs -f
# Остановить и удалить контейнеры, тома с данными остаются
docker compose down
# То же самое, но вместе с томами, данные будут удалены
docker compose down -vПорты и брандмауэр
Это самая частая ловушка при работе с Docker на сервере. Если на сервере включён брандмауэр UFW, кажется естественным, что закрытый в нём порт недоступен снаружи. Однако Docker публикует порты собственными правилами в iptables, и пакеты к контейнеру проходят раньше, чем до них доходит очередь правил UFW. Мы проверили это на тестовом сервере. В UFW были разрешены только SSH и ещё несколько портов, а порт 8080 нет, и всё же страница nginx из контейнера открывалась с другого компьютера без каких-либо ограничений. Об этой особенности предупреждает и официальная документация Docker.
Поэтому служебные интерфейсы, которые не должны быть видны всему интернету, лучше публиковать только на внутреннем адресе сервера 127.0.0.1.
# Порт доступен только с самого сервера
docker run -d --name web-local -p 127.0.0.1:8081:80 nginxВ файле Compose это записывается так же, "127.0.0.1:8081:80". Такой контейнер отвечает на самом сервере, а снаружи соединение на порт 8081 не устанавливается, что мы тоже проверили. Открыть его у себя можно через SSH-туннель или через веб-сервер с HTTPS, как в статье про n8n.
Не публикуйте базы данных наружу
Строку ports для MySQL, MariaDB, PostgreSQL или Redis добавляйте, только если точно понимаете, зачем она нужна. Контейнерам одного проекта Compose порты для связи друг с другом не нужны, они и так находят друг друга по имени.
Где хранятся данные
Тома, которые создаёт Compose, называются по имени папки проекта, например wordpress_db_data, и лежат на диске сервера в каталоге /var/lib/docker/volumes/. Команда docker compose down удаляет контейнеры, но тома оставляет, поэтому после нового docker compose up -d сайт возвращается со всеми данными. Тома исчезают только после docker compose down -v или docker volume rm.
# Список томов
docker volume ls
# Где том лежит на диске
docker volume inspect wordpress_wp_data --format '{{.Mountpoint}}'Тома нужно включать в резервные копии так же, как обычные файлы. Для баз данных надёжнее делать дамп средствами самой базы, а не копировать файлы работающей базы.
Обновление контейнеров и самого Docker
Контейнер не обновляется сам. Чтобы перейти на новую версию программы, нужно скачать свежий образ и пересоздать контейнер, а данные в томах при этом сохранятся.
# Внутри папки проекта Compose
docker compose pull
docker compose up -dCompose пересоздаст только те контейнеры, для которых вышел новый образ. Сам Docker обновляется вместе с системой, потому что его пакеты пришли из репозитория.
apt update && apt upgrade -yВо время обновления Docker перезапускается, и контейнеры ненадолго останавливаются. Те, у которых задан restart: unless-stopped или always, поднимутся сами.
Место на диске и логи
Старые образы после обновлений никуда не деваются и постепенно занимают диск. Посмотреть, сколько места заняли Docker и его данные, и убрать лишнее можно так.
# Сколько занимают образы, контейнеры и тома
docker system df
# Удалить остановленные контейнеры, неиспользуемые сети и образы без тегов
docker system prune
# Удалить все образы, которые не использует ни один контейнер
docker image prune -aВторая причина нехватки места это логи. По умолчанию Docker пишет всё, что контейнер выводит в консоль, в файл без ограничения размера, и у болтливого контейнера этот файл может вырасти до гигабайтов. Ограничение задаётся в файле /etc/docker/daemon.json.
cat > /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
systemctl restart dockerТеперь каждый контейнер хранит не больше трёх файлов логов по 10 МБ. Учтите, что настройка действует только на новые контейнеры. На тестовом сервере уже созданные контейнеры сохранили прежние параметры, поэтому существующие проекты нужно пересоздать командой docker compose up -d --force-recreate.
Веб-панель Portainer
Если управлять контейнерами удобнее мышкой, поставьте Portainer. Это веб-панель, в которой видны все контейнеры, образы, тома и логи, а контейнеры можно запускать, останавливать и обновлять кнопками.
docker volume create portainer_data
docker run -d -p 9443:9443 --name portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:ltsОткройте https://IP-адрес-сервера:9443. Браузер предупредит о небезопасном сертификате, потому что Portainer использует самоподписанный, это ожидаемо. Подтвердите переход, и откроется форма создания администратора. Форма ждёт не дольше 5 минут. Если не успеть, Portainer заблокирует её с надписью Your Portainer instance timed out for security purposes, и тогда нужно перезапустить контейнер командой docker restart portainer и взять новый токен.

Первый вход в Portainer, нужен пароль и токен из логов
Придумайте имя администратора и пароль не короче 12 символов.
В поле Setup token вставьте токен из логов Portainer. Его показывает команда ниже, скопируйте значение после
setup_token=.bashdocker logs portainer 2>&1 | grep setup_token | tail -1Нажмите Create user.
Панель видит весь Docker
Через Portainer можно управлять любым контейнером на сервере, поэтому он равнозначен доступу root. Используйте длинный пароль, а если панель нужна только вам, публикуйте её на 127.0.0.1:9443 и открывайте через SSH-туннель.
Полезные команды
docker ps -a # все контейнеры, включая остановленные
docker images # скачанные образы
docker logs --tail 50 web # последние 50 строк лога
docker exec -it web sh # открыть консоль внутри контейнера, выход exit
docker stats # нагрузка контейнеров на процессор и память
docker restart web # перезапустить контейнер
docker pull nginx # скачать свежую версию образа
docker rm -f web # удалить контейнер
docker rmi nginx # удалить образЧастые вопросы
docker-compose: command not found
Старый Compose первой версии запускался командой через дефис, docker-compose. Сейчас Compose устанавливается как плагин Docker, и команда пишется через пробел, docker compose. Если в чужой инструкции встречается docker-compose up -d, просто замените дефис на пробел.
permission denied while trying to connect to the docker API
Команду запустил обычный пользователь, у которого нет прав на Docker. Выполните её от имени root или добавьте пользователя в группу docker и переподключитесь по SSH.
usermod -aG docker имя_пользователяПомните, что участник группы docker фактически получает права root на сервере.
Bind for 0.0.0.0:8080 failed: port is already allocated
Порт уже занят другим контейнером или программой. Посмотрите, кто его использует, и либо остановите этот контейнер, либо опубликуйте новый на другом порту, например -p 8082:80.
docker ps --format '{{.Names}} {{.Ports}}'
ss -ltnp | grep 8080Cannot connect to the Docker daemon. Is the docker daemon running?
Служба Docker остановлена. Запустите её и включите автозапуск.
systemctl enable --now docker
systemctl status dockerКонтейнер не поднялся после перезагрузки сервера
У контейнера не задана политика перезапуска. Добавьте --restart unless-stopped в команду docker run или строку restart: unless-stopped в сервис Compose и пересоздайте контейнер. Для уже созданного контейнера политику можно поменять на ходу.
docker update --restart unless-stopped webКак полностью удалить Docker
Удалите пакеты, а затем каталоги с образами, контейнерами и томами. Вместе с ними удалятся и все данные контейнеров.
apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras docker-model-plugin
rm -rf /var/lib/docker /var/lib/containerd
rm -f /etc/apt/sources.list.d/docker.list /etc/apt/sources.list.d/docker.sources /etc/apt/keyrings/docker.ascНаши ресурсы
Telegram-канал: https://t.me/f3cloud_news
Официальный сайт: https://f3cloud.com