Урок по треку Инструменты 7 мин

Docker для разработчика

Образы и контейнеры, Dockerfile и слои, multi-stage сборка, тома, сети и Compose.

Docker упаковывает приложение вместе с окружением (системные библиотеки, рантайм, зависимости) в образ. Из образа запускается контейнер, и он ведёт себя одинаково на ноутбуке, в CI и на сервере.

Образ и контейнер

  • Образ (image): неизменяемый шаблон из слоёв. Как класс.
  • Контейнер: запущенный экземпляр образа со своим тонким слоем для записи. Как объект. Из одного образа можно запустить сколько угодно контейнеров.

Контейнер это не виртуальная машина. Он использует ядро хоста и изолирован средствами ядра Linux (namespaces, cgroups), поэтому стартует за доли секунды и почти не тратит лишней памяти. На macOS и Windows Docker Desktop запускает лёгкую Linux-VM, в которой живут контейнеры.

Dockerfile и слои

FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/main.js"]

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

Поэтому сначала копируют только package.json и lock-файл и ставят зависимости, а исходники копируют потом. Правка кода не трогает слой npm ci, и сборка идёт за секунды. Если сделать COPY . . первым, любая правка запустит установку зависимостей заново.

Удаление файла в следующем слое не уменьшает образ: файл остался в предыдущем слое. Временные файлы удаляют в той же инструкции RUN, где создали.

.dockerignore исключает файлы из контекста сборки: node_modules, .git, .env. Так сборка быстрее, а секреты не попадают в образ.

Multi-stage сборка

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/main.js"]

В итоговый образ попадает только последняя стадия: без компиляторов, dev-зависимостей и исходников. Образ меньше, а поверхность атаки уже. USER node запускает процесс не от root.

CMD, ENTRYPOINT и сигналы

  • CMD задаёт команду по умолчанию, её легко заменить: docker run image sh.
  • ENTRYPOINT задаёт исполняемый файл, а CMD тогда становится его аргументами по умолчанию.
  • Exec-форма ["node", "main.js"] запускает процесс напрямую, он получает PID 1 и сигналы. Shell-форма node main.js запускает /bin/sh -c, и SIGTERM от docker stop может не дойти до Node. Через 10 секунд Docker пришлёт SIGKILL, и graceful shutdown не случится.

ARG и ENV

ARG доступен только во время сборки. ENV сохраняется в образе и виден в контейнере. Секреты не кладут ни туда, ни туда: они останутся в истории образа. Их передают при запуске (переменные окружения, файлы секретов) или через RUN --mount=type=secret при сборке.

Порты, тома, сети

  • EXPOSE 3000 только документирует порт. Открывает его наружу -p 8080:3000: порт хоста 8080 ведёт в порт контейнера 3000.
  • Файлы, записанные в контейнер, пропадут вместе с ним. Данные базы хранят в томе (-v pgdata:/var/lib/postgresql/data), которым управляет Docker, или в bind mount (-v ./src:/app/src), который монтирует папку хоста, это удобно для разработки.
  • Внутри сети Docker контейнеры находят друг друга по имени сервиса: postgres://db:5432, а не localhost. localhost внутри контейнера это сам контейнер.

Docker Compose

services:
  api:
    build: .
    ports: ["3000:3000"]
    environment:
      DATABASE_URL: postgres://app:app@db:5432/app
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "app"]
volumes:
  pgdata:

depends_on без условия ждёт только запуска контейнера, а не готовности базы. Для готовности нужен healthcheck и condition: service_healthy. docker compose down удаляет контейнеры и сеть, но не именованные тома; down -v удаляет и тома, то есть данные.

Частые ошибки

  • COPY . . до установки зависимостей.
  • Нет .dockerignore, в образ уезжают node_modules хоста и .env.
  • Тег latest в проде: непонятно, что именно запущено.
  • Обращение к базе по localhost из соседнего контейнера.
  • Запуск от root и shell-форма CMD.

Что спросят на собесе

  • Чем образ отличается от контейнера и контейнер от виртуальной машины.
  • Как устроен кеш слоёв и как написать Dockerfile, чтобы он работал.
  • Зачем multi-stage сборка.
  • Как сохранить данные базы между перезапусками.
  • Чем CMD отличается от ENTRYPOINT.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.