Docker для начинающих: разворачиваем первое приложение на VPS
Пошагово разбираем, что такое Docker и контейнеризация, устанавливаем Docker на VPS и разворачиваем первое многоконтейнерное веб-приложение через Docker Compose.
Что такое контейнеризация и зачем она нужна
Docker — это платформа для контейнеризации приложений: упаковки кода вместе со всеми его зависимостями (нужной версией языка программирования, библиотеками, системными пакетами) в единый переносимый образ, который запускается одинаково на любом сервере с установленным Docker, независимо от особенностей конкретной операционной системы хоста.
Ключевое отличие от виртуальной машины — контейнеры используют ядро операционной системы хоста напрямую, изолируя только процессы, файловую систему и сеть на уровне механизмов самого ядра Linux (namespaces и cgroups). Это делает контейнеры значительно легче: запуск занимает секунды вместо минут, а накладные расходы по памяти минимальны по сравнению с полноценной виртуальной машиной, которой требуется загрузка отдельного ядра и эмуляция виртуального оборудования при каждом старте.
Build once, run anywhere — идея Docker в том, что если приложение работает в контейнере на ноутбуке разработчика, оно точно так же будет работать в контейнере на production-сервере, без сюрпризов «а у меня всё работало».
Основной принцип контейнеризации, популяризированный Docker
Эта статья проведёт через полный путь: от установки Docker на чистый VPS до разворачивания реального многоконтейнерного веб-приложения (Nginx, PHP-FPM и MySQL) через Docker Compose, с объяснением ключевых концепций на каждом шаге.
Установка Docker на Ubuntu/Debian
Официальный способ установки — через репозиторий Docker, а не через пакет docker.io из стандартных репозиториев дистрибутива, который часто содержит устаревшую версию. Ниже — актуальный на момент написания статьи способ установки для Ubuntu.
apt update
apt install ca-certificates curl gnupg -y
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | tee /etc/apt/sources.list.d/docker.list
apt update
apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
После установки стоит проверить, что служба запущена и добавлена в автозагрузку, а также убедиться в правильности установки командой проверки версии.
systemctl enable docker
docker --version
docker compose version
Первый контейнер: проверяем, что всё работает
Классическая проверка работоспособности установки — запуск официального тестового образа hello-world, который выводит приветственное сообщение и завершается, ничего не оставляя постоянно работающим на сервере.
docker run hello-world
Более практичный первый пример — запуск веб-сервера Nginx в контейнере с публикацией порта наружу, чтобы сразу увидеть результат в браузере.
docker run -d -p 8080:80 --name test-nginx nginx
Флаг -d запускает контейнер в фоновом режиме (detached), -p 8080:80 публикует внутренний порт контейнера 80 наружу на порт 8080 хоста, --name задаёт удобное имя контейнера вместо автоматически сгенерированного. После выполнения команды сайт-заглушка Nginx должен открыться по адресу IP-сервера с портом 8080 в браузере.
Образы и Dockerfile: как описать своё приложение
Образ (image) — это шаблон, из которого создаются контейнеры; он описывается файлом Dockerfile, который пошагово задаёт, из какого базового образа стартовать, какие файлы скопировать, какие команды выполнить при сборке и что запускать при старте контейнера.
FROM php:8.3-fpm
RUN apt-get update && apt-get install -y libzip-dev && docker-php-ext-install zip pdo_mysql
COPY . /var/www/html
WORKDIR /var/www/html
Директива FROM задаёт базовый образ (в данном случае официальный образ PHP с уже установленным PHP-FPM), RUN выполняет команды на этапе сборки образа (например, установку дополнительных расширений PHP), COPY копирует файлы приложения внутрь образа, WORKDIR задаёт рабочую директорию по умолчанию для последующих команд и при запуске контейнера. Каждая инструкция в Dockerfile создаёт отдельный слой (layer) образа, что позволяет Docker кэшировать неизменившиеся слои между пересборками и заметно ускорять повторную сборку образа при изменении только части кода.
docker build -t myapp:latest .
Volumes: как не терять данные при удалении контейнера
Контейнер по своей природе — эфемерная сущность: при удалении вся файловая система внутри него исчезает вместе с любыми данными, записанными во время работы. Для данных, которые должны переживать перезапуск или пересоздание контейнера (файлы загрузок, база данных), используются volumes — механизм подключения постоянного хранилища с хоста внутрь контейнера.
docker run -d -v /srv/mysql-data:/var/lib/mysql --name mysql-db mysql:8.0
Формат -v источник:назначение монтирует директорию /srv/mysql-data с хоста в путь /var/lib/mysql внутри контейнера, где MySQL хранит свои файлы данных — даже если контейнер будет удалён и создан заново, данные останутся на диске хоста и снова станут доступны новому контейнеру, подключённому к тому же volume.
Существует два основных вида volumes: bind mount (прямое монтирование конкретной директории хоста, как в примере выше) и именованный volume, которым управляет сам Docker без привязки к конкретному пути на хосте. Именованные volumes удобнее для данных вроде базы данных, где не важен конкретный путь на диске, тогда как bind mount незаменим для разработки, когда нужно видеть изменения файлов кода на хосте сразу внутри контейнера без пересборки образа.
Контейнеры должны быть эфемерными — их можно остановить и уничтожить, а затем собрать заново с минимальной настройкой и конфигурацией, если только не нужны постоянные данные, для которых существуют volumes.
Принцип из официальной документации Docker
Docker Compose: запуск нескольких контейнеров вместе
Реальное веб-приложение редко состоит из одного контейнера — обычно нужны как минимум веб-сервер, обработчик приложения и база данных, работающие вместе и умеющие обращаться друг к другу по сети. Docker Compose описывает такую связку одним YAML-файлом и управляет всеми контейнерами разом.
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./app:/var/www/html
depends_on:
- php
php:
build: .
volumes:
- ./app:/var/www/html
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
Директива depends_on задаёт порядок запуска контейнеров (хотя не гарантирует, что зависимый сервис уже полностью готов принимать соединения — для критичных зависимостей стоит добавлять проверки готовности отдельно). Именованный volume db-data, в отличие от прямого монтирования директории хоста, управляется самим Docker и не требует создания директории на хосте заранее.
docker compose up -d
docker compose ps
docker compose down
Контейнеры в одном файле docker-compose.yml автоматически оказываются в общей сети и могут обращаться друг к другу по имени сервиса как по имени хоста — например, PHP-контейнер подключается к базе данных по адресу db, а не по IP-адресу, который может измениться при пересоздании контейнера.
Переменные окружения, заданные в секции environment (как MYSQL_ROOT_PASSWORD в примере выше), — удобный способ передать конфигурацию в контейнер без изменения самого образа, но для реальных паролей и секретов в production разумнее использовать отдельный файл .env, не попадающий в систему контроля версий, или встроенный механизм Docker secrets, а не хранить пароли открытым текстом прямо в docker-compose.yml.
Управление контейнерами: логи, вход внутрь, перезапуск
Просмотр логов работающего контейнера — первое, с чего начинается диагностика проблемы; логи можно смотреть в реальном времени или ограниченным числом последних строк.
docker logs -f --tail 100 test-nginx
Для диагностики изнутри самого контейнера удобно получить интерактивный доступ к его командной оболочке — это работает так же, как обычное SSH-подключение, но внутри изолированного окружения контейнера.
docker exec -it test-nginx bash
docker restart test-nginx
docker stop test-nginx
docker rm test-nginx
Ограничение ресурсов контейнеров
Без явных ограничений контейнер потенциально может использовать все доступные ресурсы сервера — это особенно рискованно, если на одном VPS запущено несколько контейнеров разных проектов и один из них из-за ошибки в коде начинает бесконтрольно потреблять память или CPU.
docker run -d --memory=512m --cpus=1 --name limited-app myapp:latest
В Docker Compose аналогичные лимиты задаются в секции deploy.resources для каждого сервиса — это особенно важно на серверах с несколькими контейнерами разных проектов, где один переполненный процесс не должен иметь возможность уронить остальные сервисы через исчерпание общих ресурсов сервера.
services:
php:
build: .
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
Помимо жёстких лимитов, полезно настроить базовый мониторинг реального потребления ресурсов каждым контейнером — встроенная команда docker stats показывает потребление CPU, памяти и сети в реальном времени по всем работающим контейнерам одновременно, что удобно для быстрой диагностики, какой именно контейнер создаёт повышенную нагрузку на сервер.
docker stats
Docker и firewall: важный нюанс безопасности
При публикации порта контейнера через -p Docker самостоятельно добавляет правила прямо в iptables, которые могут работать в обход обычной логики ufw — правило ufw deny на порт контейнера не гарантированно его заблокирует, если порт был явно опубликован наружу через -p 0.0.0.0.
Практическая рекомендация — публиковать порты баз данных и других не предназначенных для прямого доступа из интернета сервисов только на localhost через -p 127.0.0.1:3306:3306, а не на все интерфейсы, и наружу открывать через firewall и обратный прокси только реально нужные веб-порты. Подробный разбор этой особенности — в статье «Firewall на Linux: ufw и iptables простыми словами».
Для более строгого контроля в новых версиях Docker можно настроить демон так, чтобы он не создавал собственные правила iptables вовсе, а уважал правила, добавленные администратором в специальную цепочку DOCKER-USER, — это требует более глубокого понимания сетевой части Docker, но даёт полный контроль над тем, что реально доступно снаружи, независимо от того, как именно опубликованы порты в самих контейнерах.
iptables -I DOCKER-USER -s 203.0.113.0/24 -j ACCEPT
iptables -I DOCKER-USER -j DROP
Сети Docker: как контейнеры находят друг друга
По умолчанию Docker создаёт для каждого проекта, описанного через Compose, отдельную изолированную сеть — все контейнеры внутри одного docker-compose.yml автоматически попадают в эту сеть и видят друг друга по имени сервиса, тогда как контейнеры из другого проекта в неё не входят и напрямую недоступны, если явно не настроено иначе.
docker network ls
docker network inspect myproject_default
Это встроенное сетевое разделение — удобный уровень изоляции по умолчанию: два независимых проекта на одном сервере, каждый со своей базой данных с одинаковым именем сервиса db, не будут конфликтовать друг с другом, поскольку находятся в разных изолированных сетях Docker.
Для сценариев, где нужно явное взаимодействие между контейнерами из разных Compose-проектов (например, общий обратный прокси для нескольких сайтов), создаётся отдельная именованная внешняя сеть, к которой затем подключаются нужные сервисы из разных файлов docker-compose.yml через директиву external.
Реестры образов и версионирование через теги
Docker Hub — крупнейший публичный реестр образов, откуда по умолчанию скачиваются образы вроде nginx, mysql или php при первом запуске команды docker run или docker compose up. Помимо Docker Hub, существуют приватные реестры (например, собственный реестр в GitLab или отдельный сервис вроде Amazon ECR) для образов, которые не должны быть публично доступны.
docker pull php:8.3-fpm
docker pull mysql:8.0
docker images
Тег образа после двоеточия (например, 8.3-fpm или 8.0) задаёт конкретную версию — использование тега latest без явного указания версии удобно для быстрого старта, но рискованно для production: обновление базового образа может незаметно принести breaking changes при следующей пересборке. Разумная практика — фиксировать конкретные версии образов в production-конфигурации и обновлять их осознанно, тестируя перед выкладкой.
Типичные ошибки начинающих с Docker
Хранение важных данных внутри файловой системы контейнера без volume — при следующем docker compose down или пересборке образа эти данные исчезают безвозвратно, и восстановить их без резервной копии невозможно.
Запуск процессов внутри контейнера от имени root без необходимости — если в контейнере окажется уязвимость, позволяющая выполнение произвольного кода, root внутри контейнера снижает барьер к дальнейшей эскалации привилегий по сравнению с непривилегированным пользователем.
Публикация портов баз данных и служебных интерфейсов на все интерфейсы сервера вместо localhost — типичная ошибка, которая делает внутренние сервисы случайно доступными из интернета в обход ожиданий администратора.
Игнорирование ограничений ресурсов на многоконтейнерном сервере — без лимитов памяти и CPU один переполненный контейнер способен исчерпать ресурсы, необходимые остальным сервисам на том же сервере.
Использование образов из непроверенных источников без проверки их происхождения — образ Docker может содержать вредоносный код так же, как любое другое стороннее программное обеспечение; для production стоит использовать официальные образы или собирать собственные из проверенных базовых образов.
Чек-лист разворачивания первого приложения в Docker
Шаг 1. Установите Docker и Docker Compose из официального репозитория, проверьте версии и автозапуск службы.
Шаг 2. Опишите приложение через Dockerfile, начиная с подходящего официального базового образа, а не с нуля.
Шаг 3. Соберите многоконтейнерную связку в docker-compose.yml, вынеся постоянные данные (база данных, загрузки) в именованные volumes.
Шаг 4. Опубликуйте наружу только реально нужные порты (обычно 80 и 443), оставив служебные порты баз данных доступными только с localhost.
Шаг 5. Задайте лимиты памяти и CPU для контейнеров, особенно если на сервере предполагается запуск нескольких независимых проектов.
Шаг 6. Запустите связку командой docker compose up -d, проверьте логи каждого сервиса и убедитесь, что приложение отвечает на запросы ожидаемым образом.
Итог: контейнеризация как стандарт современного деплоя
Docker решает конкретную практическую проблему — переносимость и воспроизводимость окружения приложения между разработкой, тестированием и production-сервером. Освоив базовые концепции (образы, контейнеры, volumes, Compose), можно разворачивать сложные многокомпонентные приложения так же просто, как раньше разворачивался один сайт на голом сервере.
Как и любой инструмент, Docker требует внимания к деталям безопасности — правильной публикации портов, ограничения ресурсов, использования проверенных образов. Если разворачивание приложения в Docker на VPS кажется сложной задачей — оставьте заявку внизу страницы, поможем настроить контейнеризацию под конкретный стек вашего проекта.
Следующий логичный шаг после уверенного владения Docker Compose на одном сервере — знакомство с оркестраторами вроде Docker Swarm или Kubernetes, которые решают задачи масштабирования на несколько серверов, автоматического восстановления упавших контейнеров и балансировки нагрузки между репликами приложения. Для подавляющего большинства проектов, помещающихся на один VPS, эта сложность не нужна и оправдана только при реальном росте нагрузки за пределы возможностей одного сервера.
Практический совет для тех, кто только начинает: не пытайтесь контейнеризировать сразу весь существующий проект целиком — начните с одного нового небольшого сервиса или тестового окружения, освойте на нём основные концепции Docker, и только затем переносите в контейнеры более критичные части инфраструктуры, когда уверенность в инструменте уже накоплена на практике.
Почему стоит выбрать нас
Быстрый запуск
Подберём конфигурацию и развернём проект в день обращения.
Надёжность
NVMe-диски, защита от DDoS и резервные копии по расписанию.
Поддержка 24/7
Дежурные инженеры на связи в любое время суток.
Настройка под ключ
Установим CMS, настроим веб-сервер, SSL и почту за вас.
Перенос без простоя
Аккуратно перенесём сайт и базу данных на новый сервер.
Прозрачные цены
Актуальные тарифы и честный подбор без навязывания.
Вопросы и ответы
Чем контейнер отличается от виртуальной машины?
Виртуальная машина эмулирует полноценное аппаратное окружение вместе с отдельным ядром операционной системы, тогда как контейнер использует ядро хост-системы напрямую, изолируя только процессы, файловую систему и сеть — это делает контейнеры значительно легче и быстрее в запуске.
Нужно ли устанавливать Docker на продакшн-сервер или он только для разработки?
Docker одинаково активно используется и в разработке, и на production-серверах — контейнеризация упрощает деплой, обеспечивает идентичность окружения между разработкой и продакшном и облегчает масштабирование приложения.
Что произойдёт с данными в контейнере после его удаления?
Все данные внутри контейнера, не вынесенные в volume, будут безвозвратно потеряны при удалении контейнера — именно поэтому базы данных и другие stateful-сервисы всегда должны хранить данные в volume, а не в собственной файловой системе контейнера.
Можно ли запускать Docker вместе с обычными (не контейнеризированными) сервисами на одном сервере?
Да, это обычная практика — например, Nginx на хосте может проксировать запросы к сервисам, запущенным в Docker-контейнерах, слушающих порты только на localhost.
Почему правило ufw deny не всегда блокирует порт контейнера Docker?
Docker по умолчанию добавляет собственные правила напрямую в iptables при публикации портов через -p, что может обходить обычную логику ufw — подробнее разобрано в статье про firewall на Linux.
Что такое Docker Compose и зачем он нужен, если есть просто Docker?
Docker Compose позволяет описать многоконтейнерное приложение (например, веб-сервер, PHP и базу данных) одним конфигурационным файлом и управлять всеми контейнерами одной командой вместо запуска каждого docker run по отдельности вручную.
Сколько ресурсов сервера нужно для запуска Docker?
Сам Docker потребляет минимум ресурсов сверх того, что нужно самим контейнерам, — для небольшого приложения из нескольких контейнеров достаточно тех же 1-2 vCPU и 2 ГБ RAM, что и для аналогичного приложения без контейнеризации, с небольшим дополнительным запасом.
Что произойдёт, если контейнеры из разных проектов используют одинаковые имена сервисов?
Ничего страшного — Docker Compose создаёт отдельную изолированную сеть для каждого проекта, поэтому контейнер db в одном проекте и контейнер db в другом не будут конфликтовать друг с другом, если только явно не подключены к общей внешней сети.
Стоит ли всегда использовать тег latest для образов?
Для быстрого эксперимента — можно, но для production разумнее фиксировать конкретную версию образа: обновление базового образа под тегом latest может незаметно принести несовместимые изменения при следующей пересборке контейнера.
Как оплачивать услуги?
Оплата помесячная, для облачных серверов доступна почасовая тарификация. Принимаются карты, банковские переводы и электронные кошельки.
Есть ли круглосуточная поддержка?
Да, дежурные инженеры на связи 24/7.
Выдаёте ли закрывающие документы?
Да, предоставим необходимые документы для оплаты.
Как оставить заявку?
Заполните форму на странице или нажмите «Заказать» — мы свяжемся и поможем с подбором.