Докеризация приложения открывает путь к удобному управлению инфраструктурой: один раз собрав образ, вы получаете стабильный результат как на локальном компьютере, так и на удалённом VPS.
Такой подход избавляет от проблем с зависимостями и различиями в окружении, упрощая масштабирование и развёртывание. В этой статье мы разберём, как подготовить приложение, написать корректный Dockerfile и собрать рабочий образ.
Докеризация кастомного приложения: создание Dockerfile, сборка образа, запуск контейнера
Создание и запуск Docker-образа пользовательского приложения
Разработка приложения — это только половина задачи. Вторая половина — обеспечить его стабильную работу в любом окружении: на вашем ноутбуке, на тестовом сервере или в продакшен-среде. Традиционно это приводит к проблемам: разные версии языков программирования, отсутствующие системные библиотеки, конфликты зависимостей или неправильно настроенные переменные окружения.
Docker решает эти проблемы путём упаковки приложения и всего его окружения в изолированный контейнер, включающий в себя код, зависимости, системные инструменты и настройки — всё, что нужно для запуска приложения. Это гарантирует, что приложение будет работать идентично на любой системе, где установлен Docker.
Подготовка приложения к докеризации
Перед тем как упаковывать приложение в Docker, нужно правильно подготовить его код и структуру. Это поможет избежать проблем при сборке и запуске контейнера.
Фиксация зависимостей
Все внешние зависимости нужно явно указать в файлах — так в контейнере будут установлены те же версии, что и на вашей машине. Для Python-приложений выполните команду:
pip freeze > requirements.txt
Она создаст текстовый файл со списком всех необходимых пакетов.
Для Node.js — файл package.json должен содержать все пакеты. Убедитесь, что он актуален.
Удаление состояния из приложения
Контейнеры предназначены для работы без сохранения состояния внутри себя. Все данные, которые должны сохраняться между перезапусками, нужно выносить наружу: используйте внешние базы данных, сохраняйте файлы на смонтированных томах (volumes) или в облачных хранилищах.
Конфигурация через переменные окружения
Жёстко прописанные настройки (например, URL базы данных или секретные ключи) затрудняют перенос приложения между средами. Вместо этого:
- используйте переменные окружения для всех изменяемых параметров;
- в коде обращайтесь к ним через os.getenv('KEY') (Python) или process.env.KEY (Node.js);
- укажите дефолтные значения для разработки, но позволяйте их переопределять.
Создание файла .dockerignore
Этот файл исключает ненужные файлы и директории из сборки образа, что уменьшает его итоговый размер. Он работает по тем же принципам, что и .gitignore в Git. Добавьте в него:
# Виртуальные среды и зависимости
venv/
node_modules/
# Файлы систем и редакторов
.git/
.gitignore
.idea/
.vscode/
# Логи и временные данные
*.log
*.tmp
# Локальные конфиги
config/local.yaml
.env
Создаём Dockerfile
Dockerfile — это файл с набором инструкций по сборке образа. Инструкции принято писать в верхнем регистре.
Выбор базового образа
Первая инструкция — FROM, указывающая на базовый образ; на нём будет строиться создаваемый образ. Всегда указывайте конкретную версию тега, а не latest. Это обеспечивает воспроизводимость сборок и предотвращает неожиданные изменения из-за случайных обновлений этого образа.
Например:
- python:3.10-slim — минимальный образ с Python 3.10,
- node:18-alpine — образ с Node.js 18 на базе Alpine,
- golang:1.21-bullseye — Debian Bullseye с Go 1.21.
Для компилируемых языков (Go, Rust) используйте многоэтапную (multi-stage) сборку: первый этап для сборки приложения, второй — для создания финального образа. Это уменьшит размер образа и повысит безопасность, исключив инструменты сборки из финального контейнера.
Копирование файлов отдельными слоями
Слои Docker кешируются. Изменение одного слоя инвалидирует все последующие. Поэтому первыми копируйте файлы зависимостей (requirements.txt, package.json), затем устанавливайте зависимости и после копируйте весь остальной код.
Установка зависимостей
Пользуйтесь командами, которые позволяют кешировать зависимости. Python:
RUN pip install --no-cache-dir -r requirements.txt
Node.js:
RUN npm ci --only=production
Для Go (если используются модули):
RUN go mod download
Настройка переменных окружения
Используйте инструкцию ENV для переменных окружения:
ENV PYTHONUNBUFFERED=1 \
PORT=8000 \
APP_ENV=production
Определение точки входа
CMD определяет команду по умолчанию. Рекомендуется exec-форма:
CMD ["python", "app.py"]
Если приложение на Node.js:
CMD ["node", "server.js"]
Также можно использовать связку ENTRYPOINT и CMD: ENTRYPOINT определяет базовую неизменяемую команду, а CMD — аргументы по умолчанию для этой команды:
ENTRYPOINT ["python"]
CMD ["app.py"]
CMD можно переопределить при запуске контейнера, добавив новые параметры после имени образа в docker run.
Пример Dockerfile для Python-приложения
Пример Dockerfile:
# Базовый образ
FROM python:3.10-slim
# Системные зависимости
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Рабочая директория
WORKDIR /app
# Копирование зависимостей отдельно
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копирование кода
COPY . .
# Переменные окружения
ENV PYTHONUNBUFFERED=1 \
PORT=8000
# Открытие порта
EXPOSE 8000
# Запуск приложения
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
Собираем Docker-образ и настраиваем имя
Сборка образа — процесс преобразования Dockerfile и файлов приложения в готовый к использованию образ.
Сборка образа с именем и тегом
Основная команда:
docker build -t my-app:1.0 .
Разберем компоненты команды:
- docker build — собрать образ;
- -t my-app:1.0 — флаг -t (tag) задаёт имя и тег образа;
- . — указание на текущую директорию, здесь Docker будет искать Dockerfile и контекст сборки.
Тегирование образов
Теги служат для идентификации разных версий образов. Рекомендуемая схема:
- my-app:1.0 — конкретная версия (мажорная/минорная);
- my-app:1.0.1 — с указанием патча;
- my-app:latest — последняя собранная версия.
Можно указывать несколько тегов одновременно:
docker build -t my-app:1.2.3 -t my-app:latest
Управление версиями образов
Правильное версионирование помогает:
- откатываться к предыдущим версиям при проблемах,
- однозначно идентифицировать образ в разных средах,
- автоматизировать процесс развертывания.
Рекомендации по версионированию:
- используйте семантическое версионирование,
- автоматизируйте присвоение тегов в CI/CD пайплайнах,
- избегайте перезаписи тегов уже развернутых версий.
Image ID и его использование
Каждому образу присваивается уникальный SHA-256 хеш. Чтобы просмотреть список образов (команда выведет их ID в сокращённом виде):
docker images
Вывод без усечения идентификатора (с полным ID):
docker images --no-trunc
Image ID полезен для:
- однозначной идентификации образа независимо от тега,
- автоматизации в скриптах и пайплайнах,
- ссылки на конкретную неизменяемую версию.
Примеры использования:
- запуск контейнера по ID образа:
docker run sha256:abc123...
или предварительно протегировать:
docker tag sha256:abc123 my-app:backup
docker run my-app:backup
- копирование образа по ID:
docker tag abc123 my-app:backup
- удаление образа по ID:
docker rmi abc123
Проверка собранных образов
Просмотр списка образов:
docker images
Фильтрация по имени:
docker images "my-app*"
Проверка деталей образа:
docker inspect my-app:1.0
Запуск контейнера с нужными параметрами
Прежде чем запускать контейнер, настройте параметры для корректной работы приложения.
Проброс портов: связываем хост и контейнер
Синтаксис:
docker run -p <порт_хоста>:<порт_контейнера>
Примеры:
- проброс порта 8080 хоста на порт 3000 контейнера:
docker run -p 8080:3000 my-app:1.0
- случайный порт хоста на порт 3000 контейнера:
docker run -p 3000 my-app:1.0
- специфичный IP-адрес хоста:
docker run -p 127.0.0.1:8080:3000 my-app:1.0
Важно: приложение внутри контейнера должно слушать на 0.0.0.0, а не 127.0.0.1, чтобы принимать подключения извне.
Подключение томов для данных и конфигурации
Синтаксис:
docker run -v <путь_на_хосте>:<путь_в_контейнере>
Примеры:
- монтирование директории:
docker run -v /home/user/data:/app/data my-app:1.0
- монтирование файла:
docker run -v /home/user/config.yaml:/app/config.yaml my-app:1.0
- именованный том:
docker run -v mydata:/app/data my-app:1.0
- только для чтения:
docker run -v /home/user/config:/app/config:ro my-app:1.0
Передача переменных окружения
Синтаксис:
docker run -e <KEY>=<VALUE>
Примеры:
- одиночная переменная:
docker run -e DEBUG=true my-app:1.0
- несколько переменных:
docker run -e DB_HOST=localhost -e DB_PORT=5432 my-app:1.0
- передача файла с переменными:
docker run --env-file .env my-app:1.0
Полный пример запуска
Запускаем контейнер в фоновом режиме (detached):
docker run -d \
--name my-app \ # имя контейнера
-p 8000:8000 \ # пробрасываем порт 8000 хоста на порт 8000 контейнера
-v ./data:/app/data \ # монтируем директорию с данными
-e DEBUG=false \ # отключаем debug-режим
-e DB_URL=postgresql://user:pass@db:5432/mydb \ # URL базы данных
--restart unless-stopped \ # автоматический перезапуск при падении
my-app:1.0 # имя и версия образа
Дополнительные полезные флаги
Ограничение ресурсов:
docker run -d \
--memory=512m \ # ограничение памяти
--cpus=1 \ # ограничение CPU
my-app:1.0
Пользовательские настройки сети:
docker run -d \
--network=my-bridge \ # подключение к указанной сети
--ip=172.20.0.10 \ # статический IP в сети
my-app:1.0
Health check:
docker run -d \
--health-cmd="curl -f http://localhost:8000/health || exit 1" \
--health-interval=30s \
my-app:1.0
Проверяем, что всё работает
Проверка запущенного контейнера
После запуска убедитесь, что контейнер работает корректно. Просмотр списка запущенных контейнеров:
docker ps
Просмотр логов:
docker logs my-app
Проверка статуса health check:
docker inspect --format='{{.State.Health.Status}}' my-app
Подключение к контейнеру для диагностики:
docker exec -it my-app bash
Анализ проблем
Если приложение не работает, просмотрите последние события контейнера:
docker events --since 1h
Проверьте использования диска:
docker system df
Удалите неиспользуемые ресурсы:
docker system prune
Перезапустите:
docker restart my-app
Просканируйте образ на уязвимости с помощью docker scan:
docker scan my-app:1.0
Интегрируйте эту проверку в процесс сборки для автоматического выявления проблем безопасности.
Заключение
Dockerfile является основным и наиболее гибким инструментом для создания Docker-образов, но это не единственный способ упаковки приложений в контейнеры. Альтернативные способы создания образов:
- Buildpacks — технология, автоматически определяющая тип приложения и создающая образ без написания Dockerfile. Cloud Native Buildpacks анализируют исходный код, определяют зависимости и создают production-ready образы.
- BuildKit и Docker Buildx — современные системы сборки, поддерживающие расширенные сценарии, включая мультиплатформенные сборки и использование внешних контекстов сборки.
- Импорт из существующих артефактов — Docker может создавать образы из tar-архивов или экспортированных контейнеров с помощью команд docker import и docker commit.
- Сторонние инструменты — Jib для Java-приложений или Ko для Go-приложений позволяют создавать контейнеры без написания Dockerfile.
Читайте в блоге:
- Как установить .deb-пакет на Ubuntu 24.04: все способы
- Удалённый рабочий стол на Ubuntu через VNC: пошаговая настройка
- Настройка NGINX на Ubuntu 24.04 LTS: от первого запуска до HTTPS