Docker основы

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

Docker используют для локальной разработки, тестирования, сборки воспроизводимых окружений, запуска микросервисов и автоматизации CI/CD.

Основные понятия:

Содержание


Контейнеры и виртуальные машины

Контейнеры и виртуальные машины изолируют приложения, но работают на разных уровнях.

Виртуальная машина

Виртуальная машина запускает полноценную гостевую операционную систему с собственным ядром:

Физический компьютер
└── Гипервизор
    ├── Виртуальная машина
    │   ├── Гостевая ОС
    │   └── Приложение
    └── Виртуальная машина
        ├── Гостевая ОС
        └── Приложение

Контейнер

Контейнер изолирует процессы, файловую систему, сеть и ресурсы, но использует ядро операционной системы хоста:

Физический компьютер
└── Операционная система
    └── Docker Engine
        ├── Контейнер
        │   └── Приложение
        └── Контейнер
            └── Приложение

Сравнение

Характеристика Контейнер Виртуальная машина
Уровень изоляции Процессы и ресурсы ОС Полноценная гостевая ОС
Ядро Использует ядро хоста Собственное ядро
Размер Обычно меньше Обычно больше
Запуск Очень быстрый Медленнее
Потребление ресурсов Ниже Выше
Типичное применение Сервисы, CI/CD, разработка Полная изоляция ОС

ВМ удобнее, когда нужна отдельная операционная система или собственное ядро. Docker удобнее для быстрого запуска большого количества сервисов и воспроизводимых окружений.


Образы и слои

Образ — неизменяемый шаблон, из которого создаются контейнеры. Один образ можно использовать для запуска нескольких контейнеров:

docker run -d --name web-1 nginx:alpine
docker run -d --name web-2 nginx:alpine

Теги образов

Обычно образ записывают как имя:тег:

nginx:1.27-alpine
python:3.12-slim

Если тег не указан, используется latest. Это не обязательно самая новая версия, а только имя тега. Для воспроизводимости лучше указывать конкретную версию.

docker pull nginx:alpine
docker image ls

Слои

Образ состоит из последовательности слоёв:

Слой 3: файлы приложения
Слой 2: установленные зависимости
Слой 1: базовый образ
----------------------
Образ

Слои кэшируются и переиспользуются. Если базовый образ и зависимости не изменились, Docker может не выполнять соответствующие шаги повторно.

При запуске контейнер получает тонкий записываемый слой поверх слоёв образа. Это механизм copy-on-write. Изменение файла из образа приводит к его копированию в слой контейнера.

Записываемый слой контейнера не следует использовать как постоянное хранилище: после удаления контейнера данные могут исчезнуть. Для данных применяют volumes или bind mounts.

Полезные команды:

docker image inspect nginx:alpine
docker history nginx:alpine
docker image rm nginx:alpine
docker system prune

docker system prune удаляет неиспользуемые объекты, поэтому перед выполнением нужно проверить, какие данные больше не нужны.


Dockerfile и инструкции

Dockerfile — текстовый файл с инструкциями для сборки образа.

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]

FROM

Задаёт базовый образ:

FROM node:22-alpine

alpine и slim могут уменьшить образ, но иногда требуют дополнительной настройки системных зависимостей.

WORKDIR

Устанавливает рабочую директорию:

WORKDIR /app

Если каталога нет, Docker создаст его. Обычно это лучше, чем использовать RUN cd /app.

COPY и ADD

COPY копирует файлы из контекста сборки:

COPY package.json package-lock.json ./
COPY src ./src

ADD умеет больше, например распаковывать локальные архивы. Для обычного копирования предпочтительнее COPY, поскольку его поведение проще.

RUN

Выполняет команду во время сборки:

RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

Установку и очистку часто объединяют в одной инструкции, чтобы временные файлы не оставались в отдельном слое.

ENV и ARG

ENV задаёт переменную окружения для образа и контейнера:

ENV APP_ENV=production
ENV PYTHONDONTWRITEBYTECODE=1

ARG доступен во время сборки:

ARG APP_VERSION=dev
RUN echo "Building ${APP_VERSION}"

Передача значения:

docker build --build-arg APP_VERSION=1.2.0 -t my-app:1.2.0 .

Пароли и токены нельзя хранить в ENV, ARG и Dockerfile: они могут попасть в метаданные или слои образа.

EXPOSE

Документирует порт приложения внутри контейнера:

EXPOSE 8000

EXPOSE не публикует порт на хосте. Для публикации используют docker run -p или Compose.

CMD и ENTRYPOINT

CMD задаёт команду по умолчанию:

CMD ["python", "app.py"]

Её можно переопределить:

docker run --rm my-app python healthcheck.py

ENTRYPOINT задаёт основной исполняемый файл:

ENTRYPOINT ["python"]
CMD ["app.py"]

В этом случае docker run my-app other.py запустит python other.py.

Для приложений обычно используют exec-формат в виде JSON-массива:

CMD ["node", "server.js"]

USER

Запускает приложение не от root:

RUN useradd --create-home --shell /usr/sbin/nologin appuser
USER appuser

HEALTHCHECK

Описывает проверку работоспособности:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8000/health || exit 1

Multi-stage build

Позволяет не включать инструменты разработки в финальный образ:

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

FROM nginx:alpine
COPY --from=build /src/dist /usr/share/nginx/html

.dockerignore

.git
node_modules
__pycache__
*.log
.env
Dockerfile
README.md

.dockerignore уменьшает контекст, ускоряет сборку и помогает не передавать секреты.


docker build и docker run

docker build

Сборка образа:

docker build -t my-app:1.0.0 .

Другой Dockerfile:

docker build -f Dockerfile.dev -t my-app:dev .

Без кэша:

docker build --no-cache -t my-app:clean .

Контекстом сборки является последний аргумент команды. Не следует указывать слишком большую родительскую директорию.

docker run

docker run nginx:alpine
docker run -d --name web nginx:alpine

Публикация порта выполняется в формате порт_хоста:порт_контейнера:

docker run -d \
  --name web \
  -p 8080:80 \
  nginx:alpine

Порт 80 внутри контейнера доступен на порту 8080 хоста. Ограничить публикацию localhost можно так:

docker run -p 127.0.0.1:8080:80 nginx:alpine

Переменные окружения:

docker run -d \
  --name api \
  -e APP_ENV=production \
  -e LOG_LEVEL=info \
  my-api:1.0.0

Из файла:

docker run --env-file .env my-api:1.0.0

Автоматический перезапуск:

docker run -d \
  --restart unless-stopped \
  --name api \
  my-api:1.0.0

Основные политики:

Политика Поведение
no Не перезапускать автоматически
always Перезапускать всегда
on-failure Перезапускать после ошибки процесса
unless-stopped Перезапускать, если контейнер не остановлен вручную

Интерактивный режим:

docker run --rm -it alpine sh

Ограничение ресурсов:

docker run -d --memory=512m --cpus=1.0 --name api my-api:1.0.0

docker ps, stop, start и rm

Запущенные контейнеры:

docker ps

Все контейнеры, включая остановленные:

docker ps -a

Остановка и запуск:

docker stop web
docker start web
docker restart web

Принудительная остановка:

docker kill web

Удаление остановленного контейнера:

docker rm web

Принудительное удаление:

docker rm -f web

Временный контейнер можно запускать с --rm:

docker run --rm alpine echo "temporary container"

Информация о контейнере и использование ресурсов:

docker inspect web
docker stats
docker stats web

docker inspect возвращает настройки, состояние, сети, монтирования и переменные окружения контейнера.


docker logs

Рекомендуется писать логи приложения в stdout и stderr. Тогда Docker сможет их собирать и показывать.

docker logs web
docker logs -f web
docker logs --tail 100 web
docker logs -t web

Логи за период:

docker logs --since 10m web
docker logs --since 2026-01-01T10:00:00 web

docker logs показывает только стандартный вывод процесса. Если приложение пишет в собственный файл внутри контейнера, эта команда его не покажет. Для production-систем нужно отдельно настроить ротацию и централизованное хранение логов.


docker exec

docker exec запускает дополнительный процесс внутри работающего контейнера.

docker exec -it web sh
docker exec -it web bash
docker exec web ls -la /usr/share/nginx/html
docker exec -u root -it web sh
docker exec -e DEBUG=1 web printenv DEBUG

Команда работает, пока основной процесс контейнера запущен. Ручные изменения внутри контейнера не становятся частью образа и исчезнут после его удаления. Воспроизводимые изменения нужно вносить в Dockerfile или конфигурацию запуска.


Volumes и bind mounts

Контейнеры обычно пересоздаются, поэтому важные данные нельзя хранить только в записываемом слое контейнера. Docker предоставляет named volumes и bind mounts.

Named volumes

docker volume create app-data

docker run -d \
  --name database \
  -v app-data:/var/lib/postgresql/data \
  postgres:16

docker volume ls
docker volume inspect app-data
docker volume rm app-data

Формат — имя_тома:путь_в_контейнере. Удаление тома может привести к потере данных, поэтому перед удалением нужно проверить, какие контейнеры его используют.

Bind mounts

Bind mount связывает путь хоста с путём в контейнере:

docker run --rm \
  -v "$PWD":/app \
  -w /app \
  node:22-alpine npm test

Явная форма через --mount:

docker run --rm \
  --mount type=bind,source="$PWD",target=/app \
  -w /app \
  node:22-alpine npm test

Только для чтения:

docker run --rm \
  -v "$PWD/config":/app/config:ro \
  my-app:dev

Сравнение

Свойство Named volume Bind mount
Где хранится В каталоге Docker В указанном пути хоста
Управление Docker Пользователь и ОС хоста
Типичное применение Базы данных, постоянные данные Исходники и конфигурация разработки
Переносимость Выше внутри Docker Зависит от структуры хоста
Права доступа Docker и ОС Напрямую зависят от хоста

Для базы данных обычно используют named volume. Для разработки, когда изменения исходников должны сразу попадать в контейнер, удобен bind mount.


Docker networks

Сети Docker позволяют контейнерам обмениваться данными. В одной пользовательской сети контейнеры могут обращаться друг к другу по имени.

docker network ls
docker network inspect bridge
docker network create app-net

Запуск сервисов в одной сети:

docker run -d --name db --network app-net postgres:16
docker run -d --name api --network app-net my-api:1.0.0

API обращается к базе по имени db:

postgresql://db:5432/app

IP-адрес контейнера использовать не следует: после пересоздания он может измениться.

Подключение и отключение контейнера:

docker network connect app-net existing-container
docker network disconnect app-net existing-container

Основные драйверы:

Драйвер Назначение
bridge Сеть контейнеров на одном Docker-хосте
host Использование сетевого стека хоста
none Отключение сетевого доступа
overlay Сеть между узлами в кластерных сценариях

Порт может быть доступен внутри сети, но не опубликован наружу. Публикация выполняется через -p:

docker run -d --name api --network app-net -p 8080:8000 my-api:1.0.0

Базу данных обычно не публикуют на внешний интерфейс, если к ней обращается только API-контейнер.


Docker Compose базово

Docker Compose описывает приложение из нескольких контейнеров в YAML-файле. Современная команда — docker compose.

Минимальный пример

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
docker compose up -d
docker compose ps
docker compose logs -f web
docker compose down

Приложение и база данных

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      APP_ENV: production
      DATABASE_URL: postgresql://app:app_password@db:5432/app
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app_password
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Внутри Compose-сети api обращается к PostgreSQL по имени db, а не по localhost. localhost внутри контейнера указывает на этот же контейнер.

Команды Compose

docker compose up -d --build
docker compose build api
docker compose exec api sh
docker compose logs -f api
docker compose stop
docker compose down

Удаление вместе с томами:

docker compose down -v

down -v следует использовать осторожно: команда может удалить данные баз данных в Compose volumes.

Bind mount

services:
  api:
    build: .
    volumes:
      - .:/app
      - /app/node_modules

Такой вариант типичен для разработки. В production обычно используют неизменяемый образ и не монтируют весь исходный код хоста.

.env

POSTGRES_DB=app
POSTGRES_USER=app
POSTGRES_PASSWORD=change-me

Использование:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Файл с реальными секретами не следует коммитить в репозиторий.

Healthcheck

Запуск контейнера не означает, что сервис готов принимать запросы. Можно добавить проверку:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: app_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10

  api:
    build: .
    depends_on:
      db:
        condition: service_healthy

Даже при healthcheck приложение должно уметь повторять подключение к зависимостям, потому что сеть или база могут временно быть недоступны.


Практический пример

Структура проекта:

my-api/
├── app.py
├── Dockerfile
├── .dockerignore
└── compose.yaml

Файл app.py:

import os
from http.server import BaseHTTPRequestHandler, HTTPServer


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"ok" if self.path == "/health" else (
            f"Hello from {os.getenv('APP_ENV', 'development')}\n".encode()
        )
        self.send_response(200)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


server = HTTPServer(("0.0.0.0", 8000), Handler)
print("Server started on port 8000", flush=True)
server.serve_forever()

Файл Dockerfile:

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app
COPY app.py .

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"

CMD ["python", "app.py"]

Файл .dockerignore:

.git
.env
__pycache__
*.pyc
*.log

Сборка, запуск и проверка:

docker build -t my-api:1.0 .
docker run -d --name my-api -p 8000:8000 -e APP_ENV=production my-api:1.0
curl http://localhost:8000/
curl http://localhost:8000/health
docker ps
docker logs my-api

Compose-файл:

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      APP_ENV: development
docker compose up --build
docker compose down

Рекомендации

Делайте образы небольшими

Используйте подходящий базовый образ, .dockerignore и multi-stage build. Не включайте в production-образ кэши, исходники и инструменты, которые не нужны во время запуска.

Фиксируйте версии

FROM python:3.12.5-slim

При необходимости образ можно зафиксировать по digest. Обновлять базовые образы следует осознанно, чтобы получать исправления безопасности.

Не храните секреты в образе

Пароли, токены и ключи не должны находиться в Dockerfile, ENV, ARG или исходном коде. Используйте переменные окружения, secrets-механизмы платформы или внешнее хранилище.

Разделяйте код, конфигурацию и данные

Пишите логи в стандартный вывод

Так их можно читать через docker logs и передавать в централизованную систему логирования.

Не исправляйте контейнер вручную

Контейнер обычно пересоздают из образа. Изменения, выполненные через docker exec, исчезнут после удаления контейнера. Воспроизводимая настройка должна быть описана в Dockerfile или Compose.

Используйте healthcheck

Проверка должна показывать реальную готовность сервиса и не создавать лишнюю нагрузку.


Краткая шпаргалка

# Образы
docker pull nginx:alpine
docker build -t my-app:1.0 .
docker image ls
docker image inspect my-app:1.0

# Контейнеры
docker run -d --name app -p 8080:8000 my-app:1.0
docker ps
docker ps -a
docker stop app
docker start app
docker rm app

# Диагностика
docker logs -f app
docker exec -it app sh
docker inspect app
docker stats app

# Томы
docker volume create app-data
docker volume ls
docker volume inspect app-data

# Сети
docker network create app-net
docker network ls
docker network inspect app-net

# Compose
docker compose up -d --build
docker compose ps
docker compose logs -f
docker compose exec api sh
docker compose down

Docker позволяет описывать окружение приложения как код: Dockerfile определяет образ, Compose — взаимодействие сервисов, volumes — хранение данных, а networks — связь между контейнерами. Это делает сборку и запуск приложения более воспроизводимыми.