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.