Введение
Сборка Qt-приложения зависит от конкретной версии Qt, компилятора, и системных библиотек — Docker даёт способ зафиксировать это окружение в воспроизводимом, переносимом виде, устраняя классическую проблему «у меня на машине работает», когда сборка успешна локально, но не воспроизводится в другом окружении (CI-сервере, машине другого разработчика).
Концепция
Docker-образ описывается через Dockerfile — последовательность инструкций, определяющих базовую систему и устанавливаемые в неё зависимости (компилятор, Qt, дополнительные библиотеки) — собранный из этого Dockerfile образ затем может быть запущен идентично на любой машине с установленным Docker, независимо от реальной операционной системы хоста, давая гарантированно одинаковое окружение сборки. Для CI это означает, что сборочное окружение определяется тем же Dockerfile, что хранится в репозитории проекта (статья 444) и версионируется вместе с остальным кодом, а не зависит от ручной, недокументированной настройки конкретного CI-сервера.
Пример кода
# Dockerfile — сборочное окружение для Qt6/C++ проекта на основе Ubuntu
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y
build-essential
cmake
qt6-base-dev
qt6-declarative-dev
&& rm -rf /var/lib/apt/lists/* # очистка кеша пакетов — уменьшает итоговый размер образа
WORKDIR /app
COPY . .
RUN cmake -B build -S . && cmake --build build
# Локальная сборка образа и запуск контейнера для тестирования сборки
docker build -t my-qt-app-builder .
docker run --rm my-qt-app-builder ./build/MyQtApp --version
# Многоэтапная сборка (multi-stage build) — итоговый образ не содержит инструментов сборки,
# только готовый, скомпилированный исполняемый файл — значительно компактнее
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y build-essential cmake qt6-base-dev
WORKDIR /app
COPY . .
RUN cmake -B build -S . && cmake --build build
FROM ubuntu:22.04 AS runtime
RUN apt-get update && apt-get install -y libqt6core6 libqt6widgets6 # только runtime-библиотеки, без -dev
COPY --from=builder /app/build/MyQtApp /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/MyQtApp"]
Пояснения к коду
RUN apt-get install ... && rm -rf /var/lib/apt/lists/* в одной команде (а не как отдельные RUN) — практичная Docker-оптимизация: каждая инструкция RUN создаёт отдельный, неизменяемый «слой» образа, и очистка кеша пакетов в той же команде, что и установка, гарантирует, что этот временный кеш не попадёт в финальный слой образа, тогда как разделение на отдельные RUN команды оставило бы кеш в одном из промежуточных слоёв, увеличивая итоговый размер образа даже после «удаления» в последующем слое. Многоэтапная сборка (AS builder/AS runtime) — практичный паттерн, разделяющий полное окружение сборки (с компилятором и dev-пакетами, значительно большее по размеру) от минимального окружения выполнения (только runtime-библиотеки, нужные для запуска уже скомпилированного приложения), что даёт значительно более компактный финальный образ, предназначенный для реального развёртывания, а не для сборки.
Подводные камни
- Использование образа с тегом
latestвместо явно зафиксированной версии базового образа (FROM ubuntu:latestвместоFROM ubuntu:22.04) — без явной фиксации версии содержимое «latest» может измениться со временем (новый релиз дистрибутива, переопределение тега), что нарушает основной принцип воспроизводимости, ради которого изначально применяется контейнеризация; версия базового образа должна быть зафиксирована явно, аналогично фиксации версий зависимостей в пакетных менеджерах (основной цикл статей про CMake/vcpkg). - Игнорирование многоэтапной сборки для production-образов, оставляющее в финальном образе полный набор инструментов разработки (компилятор, заголовочные dev-файлы), значительно увеличивающий размер образа и потенциальную поверхность атаки (больше установленного ПО — больше потенциальных уязвимостей) для образа, предназначенного для реального production-развёртывания, а не только для сборки.
- Чрезмерно большой контекст сборки (передача в Docker build всей директории проекта через
COPY . .без файла.dockerignore, исключающего ненужные для сборки файлы — каталоги сборки, файлы системы контроля версий, локальные IDE-настройки) — это замедляет процесс сборки образа и может непреднамеренно включить в образ чувствительные или ненужные файлы; явный.dockerignore, аналогичный по назначению.gitignore, должен исключать такие файлы из контекста сборки. - Использование Docker исключительно для CI/тестирования, без понимания, что для embedded-целей (Raspberry Pi/STM32-циклы) кросс-архитектурная сборка через Docker требует дополнительной настройки (QEMU-эмуляция архитектуры или явная настройка кросс-компиляции внутри контейнера, аналогично статье 342) — обычный Docker-образ, собранный для x86_64-архитектуры хоста CI-сервера, не даёт автоматически готового бинарного файла для ARM-архитектуры Raspberry Pi без дополнительных, явных шагов кросс-компиляции внутри самого Docker-процесса сборки.