Развертывание Docker-приложения: от Dockerfile до запуска контейнера | AdminWiki

Развертывание Docker-приложения: от Dockerfile до запуска контейнера

06 сентября 2026 12 мин. чтения
Содержание статьи

Базовое развертывание Docker-приложения состоит из четырех действий: подготовить Dockerfile, собрать Docker image, передать runtime-конфигурацию и создать Docker container командой docker run. Команды выполняют из каталога приложения, где лежат Dockerfile и файлы, указанные в контексте сборки.

Минимальный результат выглядит так: Docker Engine собирает образ с тегом, контейнер получает имя, переменные окружения, сеть и опубликованный порт. После docker run проверьте состояние контейнера, логи и ответ приложения. Успешное создание контейнера не подтверждает, что сервис готов принимать запросы.

Как запустить Docker-контейнер: минимальный рабочий сценарий

Ниже приведен шаблон для приложения, которое слушает порт 8000 внутри контейнера и отвечает на endpoint /health. Замените имя образа, внутренний порт, env-файл и endpoint на параметры своего проекта.

docker build -t my-app:1.0 .
docker network create app-net
docker run \
  --name my-app \
  -d \
  --env-file .env.production \
  --network app-net \
  -p 8080:8000 \
  my-app:1.0
docker ps --filter "name=my-app"
curl -i 127.0.0.1:8080/health

Команда docker build создает Docker image. Параметр -t my-app:1.0 задает имя и тег версии. docker network create потребуется, когда приложение обращается к базе данных, кэшу или другому контейнеру. Если зависимостей нет, сеть можно не создавать и убрать --network app-net.

Параметр -p 8080:8000 связывает порт 8080 хоста с портом 8000 контейнера. Файл .env.production Docker читает на хосте во время запуска. Контейнер получает его значения как переменные окружения.

Что проверить до развертывания Docker-приложения

Перед сборкой зафиксируйте production-команду запуска, список обязательных переменных, внутренний порт, адреса зависимостей и способ хранения данных. Проверьте установленную версию Docker Engine и синтаксис runtime, который использует приложение. Команда, работающая в режиме разработки, часто не подходит для запуска на сервере.

Определить команду запуска и внутренний порт

Контейнер остается запущенным, пока работает его основной foreground-процесс. Если CMD запускает скрипт, который сразу завершается, Docker переведет контейнер в статус Exited. Для веб-приложения нужна команда, запускающая сервер без фонового режима.

Разделяйте два порта:

  • Порт контейнера, например 8000. На нем приложение слушает внутри своей сетевой среды.
  • Порт хоста, например 8080. По нему к сервису обращаются с сервера или из внешней сети.

Приложение внутри контейнера должно слушать 0.0.0.0, а не 127.0.0.1 или localhost. Docker публикует порт, но не меняет сетевую привязку процесса. Если сервер слушает только loopback-интерфейс контейнера, запросы через -p не дойдут до приложения.

Подготовить контекст сборки и .dockerignore

Последний аргумент команды docker build задает контекст сборки. В команде docker build -t my-app:1.0 . точка означает текущий каталог. Docker отправляет содержимое этого каталога демону сборки, поэтому лишние файлы замедляют процесс и могут попасть в слои образа.

Создайте файл .dockerignore рядом с Dockerfile:

.git
.gitignore
.env
.env.*
node_modules
venv
__pycache__
*.log
dist
build
coverage
*.pem
*.key

Скорректируйте список под стек проекта. Не исключайте манифесты зависимостей, файлы миграций, конфигурацию runtime и исходный код, которые нужны внутри контейнера. Секреты, локальные сборки, каталоги зависимостей и ключи не должны входить в контекст без явной необходимости.

Dockerfile для приложения: минимальный рабочий шаблон

Dockerfile описывает воспроизводимую сборку образа. Пример ниже рассчитан на простое Python-приложение с файлом app.py и зависимостями в requirements.txt. Для Node.js, Go, Java или другого runtime меняются базовый образ, шаг установки зависимостей и команда запуска, а логика слоев сохраняется.

FROM python:3.13-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]

Для production лучше закреплять версию базового образа точнее, чем только мажорный тег, если политика проекта требует строгой повторяемости. Проверьте совместимость Python, системных библиотек и пакетов приложения перед фиксацией версии.

Базовый образ и рабочая директория

Инструкция FROM задает runtime и стартовый слой образа. Официальный образ python:3.13-slim содержит Python и минимальный набор компонентов Debian. Полный образ обычно удобнее для диагностики, но увеличивает размер. Slim-вариант снижает объем, при этом отдельным пакетам могут потребоваться системные библиотеки для сборки.

WORKDIR /app задает рабочий каталог для следующих инструкций. После этого COPY requirements.txt ./ записывает файл в /app/requirements.txt, а команда CMD запускается из /app. Явная рабочая директория устраняет ошибки с относительными путями.

Установка зависимостей и копирование исходного кода

Сначала Dockerfile копирует манифест зависимостей, затем устанавливает пакеты, после чего добавляет исходный код. Docker использует кэш слоев: изменение app.py не заставит повторно выполнять pip install, пока requirements.txt не изменился.

В production-файл зависимостей не включайте тестовые фреймворки, отладчики и локальные инструменты, если приложение их не использует при запуске. Для сложных образов, multi-stage build и диагностики кэша используйте отдельное руководство по сборке Docker-образов и слоям Dockerfile.

EXPOSE и команда запуска приложения

EXPOSE 8000 документирует ожидаемый внутренний порт образа. Инструкция не публикует сервис на хосте и не заменяет параметр -p в docker run.

CMD ["python", "app.py"] задает основной процесс контейнера. Используйте exec-форму с массивом аргументов: Docker запускает процесс без дополнительной оболочки, а сигналы остановки доходят до приложения предсказуемее. Если образ предназначен для нескольких сценариев, базовую неизменяемую команду можно вынести в ENTRYPOINT, а аргументы оставить в CMD.

Не записывайте пароли, токены, API-ключи и приватные сертификаты в Dockerfile. Любое значение, добавленное инструкциями COPY, ENV или RUN, способно остаться в истории слоев образа.

Сборка Docker-образа: пример команды docker build

Соберите образ в каталоге, содержащем Dockerfile:

docker build -t my-app:1.0 .
docker image ls my-app

my-app - имя репозитория образа, 1.0 - тег версии, точка - контекст сборки. При успешной сборке команда docker image ls my-app покажет образ и его размер. Ошибки на этапе COPY обычно означают неправильный путь или исключение файла через .dockerignore. Ошибки RUN относятся к пакетам, системным библиотекам или команде установки.

Тегирование образа и повторная сборка

Тег latest не сообщает, какая версия кода и зависимостей находится внутри образа. Для развертывания используйте отдельный тег: номер релиза, идентификатор CI-сборки или короткий commit SHA. Например:

docker build -t my-app:1.1 .
docker image ls my-app

Повторная сборка создает новый образ, но не меняет уже работающий контейнер. Контейнер my-app, созданный из my-app:1.0, продолжит использовать старую конфигурацию и файловую систему до пересоздания.

Если Docker ошибочно использует старый слой во время поиска проблемы, соберите образ без кэша:

docker build --no-cache -t my-app:1.1-debug .

Постоянно отключать кэш не нужно. Он ускоряет обычные сборки и помогает быстрее выявлять изменения в зависимостях и исходном коде.

Проверить содержимое и точку запуска образа

До запуска контейнера проверьте метаданные образа:

docker image inspect my-app:1.0
docker image inspect --format '{{json .Config}}' my-app:1.0

В выводе ищите Cmd, Entrypoint, WorkingDir и ExposedPorts. Они должны совпадать с ожиданиями проекта. Для разовой диагностики можно переопределить команду и открыть оболочку:

docker run --rm -it --entrypoint sh my-app:1.0

Проверьте наличие файлов и доступность runtime внутри контейнера, затем выйдите из оболочки. Не заменяйте production-команду в Dockerfile отладочной оболочкой.

Запуск Docker-контейнера с переменными окружения

Один и тот же Docker image можно запускать в тестовой и production-среде с разной конфигурацией. Передавайте адрес базы данных, режим приложения, имя очереди и другие runtime-параметры в момент создания контейнера.

Переменные через -e и --env-file

Одна переменная подходит для короткого запуска:

docker run --rm -e APP_ENV=production my-app:1.0

Для постоянного сценария удобнее использовать env-файл:

APP_ENV=production
APP_PORT=8000
CACHE_HOST=cache
CACHE_PORT=6379

Запуск с файлом:

docker run --env-file .env.production my-app:1.0

Файл должен существовать на Docker-хосте, быть доступен пользователю, который запускает Docker, и содержать по одной паре ИМЯ=значение на строку. Не коммитьте production env-файл в репозиторий. Ограничьте права доступа на хосте, например через права чтения только для владельца.

docker inspect my-app содержит переданные переменные, поэтому просматривайте его вывод только в доверенной среде. Для проверки без печати значения используйте прикладную диагностическую команду или endpoint, который подтверждает наличие обязательной настройки и не раскрывает секрет.

Что не следует хранить в образе

Не включайте в образ пароли пользователей, токены доступа, ключи API, SSH-ключи, приватные сертификаты и резервные копии. Они могут попасть в registry, кэш CI, историю образа или вывод диагностических команд.

Переменные окружения подходят для базового сценария, но секреты все равно могут быть видны через Docker API, docker inspect и процесс внутри контейнера. В production используйте согласованное хранилище секретов платформы или системы оркестрации. Перед публикацией образа полезно пройти чек-лист аудита безопасности Docker-хоста и контейнеров.

Docker сеть контейнеров: порты и зависимости

Приложение, база данных, кэш и прокси должны обмениваться трафиком через понятную сетевую схему. Пользовательская Docker network дает контейнерам общий DNS и изолирует их от других контейнеров хоста.

Создать пользовательскую Docker-сеть

Создайте bridge-сеть и подключите к ней зависимость:

docker network create app-net
docker run -d --name cache --network app-net redis:7-alpine
docker run -d --name my-app --network app-net my-app:1.0

Внутри сети app-net приложение может подключаться к Redis по имени cache и порту 6379. В env-файле этому соответствует CACHE_HOST=cache. Не используйте для другого контейнера адрес localhost: внутри my-app этот адрес указывает на сам контейнер приложения.

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

Опубликовать порт приложения на хосте

Формат публикации порта: -p HOST_PORT:CONTAINER_PORT. Команда ниже делает внутренний порт 8000 доступным на порту 8080 Docker-хоста:

docker run -d --name my-app -p 8080:8000 my-app:1.0

При необходимости ограничьте доступ конкретным интерфейсом хоста:

docker run -d --name my-app -p 127.0.0.1:8080:8000 my-app:1.0

Такой вариант оставляет сервис доступным только локально на сервере. Для внешнего трафика обычно используют reverse proxy, firewall и отдельную публикацию нужных портов. На VPS или виртуальном сервере проверьте правила сетевого экрана и настройки облачной сети. Для размещения контейнеров подойдет облачная инфраструктура Timeweb Cloud, где можно подобрать сервер и изменить ресурсы по нагрузке приложения.

Проверить доступность зависимостей

Запущенный контейнер зависимости еще не гарантирует готовность сервиса. База данных может выполнять инициализацию, миграции или восстановление данных. Приложение должно корректно обрабатывать кратковременную недоступность и повторять подключение по своей политике retry.

Проверьте сеть и DNS-имя:

docker network inspect app-net
docker run --rm --network app-net busybox:1.36 nslookup cache

Убедитесь, что оба контейнера подключены к app-net, имя хоста совпадает с именем контейнера или сервиса, а порт в переменной окружения относится к внутренней сети. Не подставляйте в DATABASE_HOST или CACHE_HOST опубликованный порт хоста, когда обмен идет между контейнерами.

Запуск контейнера и проверка результата

Итоговая команда объединяет имя контейнера, режим работы, env-файл, сеть, публикацию порта и образ. Добавляйте --restart unless-stopped после успешной первичной проверки, когда поведение процесса и логов уже понятно.

Собрать итоговую команду docker run

docker run \
  --name my-app \
  -d \
  --env-file .env.production \
  --network app-net \
  -p 8080:8000 \
  --restart unless-stopped \
  my-app:1.0

--name my-app упрощает диагностику и сетевые обращения. -d запускает контейнер в фоне. --env-file передает конфигурацию. --network подключает сервис к пользовательской Docker network. -p публикует порт на хосте.

Подробный разбор флагов, интерактивного режима, mount-точек и политик перезапуска есть в руководстве по команде docker run.

Проверить ответ приложения после запуска

Сначала убедитесь, что Docker видит работающий контейнер:

docker ps --filter "name=my-app"
docker port my-app
docker logs --tail 100 my-app

Затем проверьте прикладной endpoint:

curl -i 127.0.0.1:8080/health

Ожидаемый результат зависит от приложения, но HTTP-сервис обычно возвращает статус 200 или другой явно задокументированный успешный код. Для production добавьте healthcheck, который проверяет готовность приложения и доступность критичных компонентов без изменений данных. Docker healthcheck показывает состояние healthy или unhealthy, но сам по себе не перезапускает контейнер.

Проверка логов Docker-контейнера и поиск ошибок

Диагностику начинайте с состояния контейнера, затем переходите к логам, команде запуска, портам, сети и зависимостям. Такой порядок отделяет ошибку Dockerfile от ошибки приложения и сокращает число повторных запусков без новых данных.

Контейнер сразу завершился

Статус Exited означает, что основной процесс остановился. Проверьте список контейнеров, последние строки логов, код завершения и фактическую конфигурацию:

docker ps -a --filter "name=my-app"
docker logs --tail 100 my-app
docker inspect --format '{{.State.ExitCode}}' my-app
docker inspect my-app

Код 0 часто указывает на завершение команды без ошибки. Ненулевой код обычно связан с исключением приложения, отсутствующим файлом, ошибкой миграции, неверной переменной окружения или отказом внешней зависимости. В выводе docker inspect проверьте Path, Args, Config.Cmd и State.Error.

Приложение запущено, но порт недоступен

Когда docker ps показывает статус Up, а запрос не проходит, проверьте соответствие внутреннего порта и публикации:

docker port my-app
docker inspect --format '{{json .NetworkSettings.Ports}}' my-app
curl -i 127.0.0.1:8080/health

Сверьте три значения: порт, который слушает приложение, значение после двоеточия в -p и значение EXPOSE. EXPOSE не влияет на сетевой трафик, но несоответствие в метаданных часто помогает заметить ошибку конфигурации. Проверьте привязку приложения к 0.0.0.0, занятость внешнего порта и правила firewall.

Проблема с базой данных или другим сервисом

Ошибки подключения к БД, кэшу или очереди проверяйте в двух контейнерах. Сначала убедитесь, что зависимость не завершилась:

docker ps -a --filter "name=cache"
docker logs --tail 100 cache
docker network inspect app-net

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

Проверить логи и состояние без раскрытия секретов

Для последних событий используйте ограниченный вывод:

docker logs --tail 100 my-app
docker logs -f my-app

Режим -f показывает новые сообщения в реальном времени. Остановить просмотр можно сочетанием Ctrl+C, это не останавливает контейнер. Перед передачей логов коллегам или в систему мониторинга проверьте, нет ли в stack trace токенов, строк подключения, заголовков авторизации и значений env-переменных.

Повторное развертывание новой версии и контрольный чек-лист

Новая сборка образа не обновляет существующий Docker container. Для новой версии нужен новый тег, пересоздание контейнера с прежними параметрами и проверка endpoint до удаления проверенного образа. Храните данные вне изменяемого слоя контейнера: в Docker volume, внешней базе данных или другом постоянном хранилище.

Обновить контейнер без потери конфигурации

Пример последовательности обновления:

docker build -t my-app:1.1 .
docker run --rm --env-file .env.production my-app:1.1
docker stop my-app
docker rm my-app
docker run \
  --name my-app \
  -d \
  --env-file .env.production \
  --network app-net \
  -p 8080:8000 \
  --restart unless-stopped \
  my-app:1.1
docker logs --tail 100 my-app

Перед остановкой старого контейнера сохраните точную команду запуска, имя сети, подключенные volumes и текущий тег. Для rollback держите предыдущий проверенный образ, например my-app:1.0. При проблеме остановите новую версию, удалите ее контейнер и создайте экземпляр из старого тега с той же конфигурацией.

Итоговый чек-лист перед передачей в эксплуатацию

  • Dockerfile содержит явную команду запуска и корректную рабочую директорию.
  • .dockerignore исключает секреты, логи, локальные зависимости и служебные каталоги.
  • Образ собран с понятным тегом версии, а не только с тегом latest.
  • Секреты не записаны в Dockerfile, репозиторий, образ и диагностические логи.
  • Env-файл хранится на хосте с ограниченными правами доступа.
  • Приложение и его зависимости подключены к нужной Docker network.
  • Контейнер обращается к зависимости по имени сервиса и внутреннему порту.
  • Параметр -p связывает правильные порты хоста и контейнера.
  • docker ps показывает статус Up, а docker logs не содержит критических ошибок.
  • Endpoint приложения отвечает через опубликованный порт.
  • Постоянные данные вынесены в volume или внешнее хранилище.
  • Предыдущий тег образа доступен для rollback.

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

Поделиться:
Сохранить гайд? В закладки браузера