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-appdocker 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.