Docker

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

Image, container и registry

ОбъектЧто этоЖизненный цикл
Imageнеизменяемые слои файловой системы и конфигурация запускасобирается, тегируется, публикуется
Containerэкземпляр image с writable layer и процессамисоздаётся, запускается, останавливается, удаляется
Volumeданные вне writable layer контейнераживёт независимо от контейнера
Registryхранилище imagesотдаёт и принимает слои

Тег — изменяемое имя, digest — идентификатор конкретного содержимого. Для воспроизводимого развёртывания лучше фиксировать версию или digest, а не latest.

Базовый цикл

docker build -t demo-app:1.0 .
docker image inspect demo-app:1.0
docker run --name demo-app -p 8080:8080 -d demo-app:1.0
docker ps
docker logs --tail 100 -f demo-app
docker inspect demo-app
docker stop demo-app
docker rm demo-app

docker run эквивалентен docker create и затем docker start. Повторный run создаёт новый контейнер, а не запускает старый. См. Структура команды docker run и Что будет если один контейнер уже ранее был создан.

Dockerfile

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/app.jar app.jar
USER 10001
ENTRYPOINT ["java", "-jar", "app.jar"]

Каждая инструкция может создать слой. Build context — каталог, отправляемый builder; лишние файлы исключаю через .dockerignore. Multi-stage build не переносит компилятор и исходники в runtime image:

FROM maven:3-eclipse-temurin-21 AS build
WORKDIR /src
COPY . .
RUN mvn -q -DskipTests package
 
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Тесты лучше выполнить отдельным шагом CI, а пропуск в packaging не должен означать пропуск проверки.

Сеть и данные

  • -p 8080:8080 публикует hostPort:containerPort.
  • EXPOSE документирует порт image, но сам ничего не публикует.
  • В user-defined bridge контейнеры находят друг друга по имени.
  • Для постоянных данных использую volume; writable layer контейнера считаю временным.

См. Bridge Network Мостовая сеть Docker, Docker Volume, Запуск БД внутри контейнера.

Диагностика

docker ps -a
docker logs --timestamps demo-app
docker inspect demo-app
docker stats --no-stream
docker exec demo-app ps
docker network inspect demo-net
docker volume inspect demo-data

Сначала выясняю exit code, команду и environment из inspect, затем читаю logs. exec работает только для запущенного контейнера и не должен быть способом ручной “починки” production: изменение исчезнет после пересоздания.

Архитектура CPU

Image может содержать варианты для linux/amd64 и linux/arm64. Если нужного manifest нет, получу ошибку формата или медленную эмуляцию. В реальном случае я добавлял профили, которые выбирали совместимые базовые образы для ARM и x86. См. Кейс Docker-сборка для ARM и x86.

Типичные сбои

  • Приложение слушает 127.0.0.1 внутри контейнера и недоступно через published port.
  • Данные БД лежат в writable layer и исчезают с контейнером.
  • Image запускается от root без необходимости.
  • Секрет попал в ARG, слой image или историю сборки.
  • В image использован mutable latest, и откат уже не воспроизводит прежний бинарник.
  • Контейнер убит по памяти, а ошибку ищут только в application logs.

Вопросы для собеседования

Чем image отличается от container? Image — неизменяемый шаблон; container — запущенный или остановленный экземпляр с writable layer.

Почему удаление container не обязано удалить данные? Named volume имеет отдельный жизненный цикл.

Что делает -p 8080:80? Публикует порт 80 контейнера на порту 8080 хоста.

Почему контейнер остановился сразу после запуска? Завершился его PID 1; фоновые процессы внутри не удерживают контейнер сами по себе.

Зачем multi-stage build? Отделить сборочное окружение от компактного runtime image.