Войти Регистрация

Docker простыми словами: образ, контейнер и compose на одном сервисе

Масляная живопись: на деревянном столе лист с чертежом, лупа, картонная бирка на бечёвке и две маленькие коробки с подписями. Иллюстрация IT Career Gym
Чертёж один, коробок по нему можно собрать сколько угодно

Коротко о статье

Знакомая сцена первой недели на новом проекте: README на две страницы, у вас Python 3.11 вместо нужного 3.12, база другой версии, и к вечеру пятницы проект так и не запустился. Docker придуман ровно для того, чтобы этот вечер не повторялся. Всё, что приложению нужно для запуска, описано в одном файле и собирается одной командой на любой машине, где стоит Docker.

Разберём три слова, на которых держится остальное: образ, контейнер, compose. Пример один, маленький API на Python, и его код можно взять в свой пет-проект как есть.

Три слова, без которых дальше не пойдёт

  • Образ (image). Шаблон только для чтения: минимальная файловая система, интерпретатор, зависимости, ваш код и команда запуска. Собирается из Dockerfile слоями, каждая инструкция RUN или COPY добавляет слой.
  • Контейнер (container). Процесс, запущенный из образа, с тонким слоем для записи поверх него. Из одного образа можно поднять десять одинаковых контейнеров. Удалили контейнер, и всё, что он записал вне тома, пропало.
  • Compose. Файл YAML, в котором описано, какие контейнеры поднять, с какими портами, переменными и томами. Вместо длинной строки docker run с десятком флагов вы пишете docker compose up.

Контейнер часто сравнивают с виртуальной машиной, и это сравнение скорее путает. Виртуальная машина везёт с собой целую операционную систему со своим ядром. Контейнер же обычный процесс хоста, которому изоляция показывает собственное дерево файлов, собственную сеть и лимиты памяти. Ядро у всех контейнеров общее. Поэтому он стартует за секунды, а на рабочем ноутбуке их спокойно живёт несколько десятков.

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

Сервис, на котором всё проверим

Маленький API заметок на FastAPI. Пока в нём один адрес, /health, больше для примера и не нужно. Структура папки:

notes-api/
├── main.py
├── requirements.txt
├── Dockerfile
├── .dockerignore
└── compose.yaml
# main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/health")
def health():
    return {"status": "ok"}

В requirements.txt две строки: fastapi==0.115.0 и uvicorn==0.30.6. Версии закрепляем намеренно: образ, собранный через полгода, должен получиться таким же, как сегодня.

Dockerfile построчно

# syntax=docker/dockerfile:1
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

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

COPY . .

RUN useradd --create-home appuser \
    && mkdir -p /app/data \
    && chown appuser /app/data
USER appuser

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

FROM python:3.12-slim. Базовый образ на Debian без лишнего. Alpine меньше по размеру, но для Python с ним чаще бывают сюрпризы при установке пакетов с C-расширениями, поэтому для первого проекта мы берём slim.

Порядок COPY. Сначала копируем только requirements.txt и ставим зависимости, потом остальной код. Docker кэширует слои: если вы поменяли одну строку в main.py, слой с pip install возьмётся из кэша, и пересборка займёт секунды. Перепутаете порядок, и каждая правка будет заново качать все пакеты.

USER appuser. Процесс внутри контейнера по умолчанию работает от root. Для учебного проекта это не катастрофа, но на собеседовании про это спросят, а в компании сканер образов всё равно заставит переделать. Заодно создаём папку /app/data и отдаём её пользователю, чтобы в неё можно было писать.

CMD в квадратных скобках. Это exec-форма: uvicorn запускается напрямую, без оболочки /bin/sh, и сигнал остановки от docker stop доходит до него сразу. В строковой форме контейнер часто ждёт 10 секунд таймаута и только потом убивается.

Рядом обязательно положите .dockerignore. Без него в контекст сборки уедут папка .git, виртуальное окружение и, что хуже, файл с паролями:

.git
.venv
__pycache__
*.pyc
.env
data/

Проверяем, что образ собирается и отвечает:

docker build -t notes-api:0.1 .
docker run --rm -p 8000:8000 notes-api:0.1
curl http://localhost:8000/health

Тот же запуск, но через compose

Строка docker run выше короткая, пока у сервиса нет переменных окружения, тома и проверки здоровья. Когда они появляются, флаги перестают помещаться в голове, и их переносят в файл:

services:
  api:
    build: .
    image: notes-api:0.1
    ports:
      - "8000:8000"
    environment:
      APP_ENV: dev
      LOG_LEVEL: info
    volumes:
      - notes-data:/app/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 30s
      timeout: 3s
      retries: 3

volumes:
  notes-data:

Пара деталей, на которых спотыкаются. Порт записан строкой в кавычках, как советует документация: YAML иначе может прочитать некоторые пары чисел через двоеточие как число. Проверка здоровья написана на чистом Python, потому что в slim-образе нет curl. Если urlopen получит ошибку, команда завершится с ненулевым кодом, и после трёх неудач подряд контейнер станет unhealthy.

В старых статьях файл начинается со строки version: "3.8". Сейчас это поле устарело: Compose его игнорирует и выводит предупреждение. Имя файла по умолчанию теперь compose.yaml, но docker-compose.yml по-прежнему читается.

docker compose up -d --build
docker compose ps
docker compose logs -f api
docker compose down

Одна команда, которую стоит запомнить отдельно: docker compose down -v. Флаг -v удалит и том notes-data вместе с данными. Обычный down том оставляет.

Шаблон compose для своего проекта

Замените значения в угловых скобках вместе с самими скобками. Секреты храните в .env, который лежит в .gitignore и .dockerignore, а в репозиторий кладите .env.example с пустыми значениями.

Шаблон compose.yaml

services:
  <имя_сервиса>:
    build: .
    image: <имя_образа>:<версия>
    ports:
      - "<порт_на_хосте>:<порт_в_контейнере>"
    env_file:
      - .env
    volumes:
      - <имя_тома>:<папка_с_данными_в_контейнере>
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "<команда, которая падает, если сервис не отвечает>"]
      interval: 30s
      timeout: 3s
      retries: 3

volumes:
  <имя_тома>:

Где ломается у новичков

  1. Сервис слушает 127.0.0.1. Внутри контейнера localhost означает сам контейнер. Приложение, которое слушает только его, снаружи недоступно даже при открытом порте. Нужен адрес 0.0.0.0.
  2. Пароли в образе. ENV DB_PASSWORD=... в Dockerfile или скопированный .env остаются в слоях, и их достанет любой, у кого есть образ.
  3. Тег latest на сервере. Сегодня и через месяц это разные образы. Откатиться «на вчерашний latest» нельзя.
  4. Данные без тома. База внутри контейнера без volume живёт до первого docker compose down.
  5. Нет .dockerignore. Сборка тянет в контекст сотни мегабайт и идёт минутами вместо секунд.

Docker Hub из России и зеркала реестров

В ночь на 30 мая 2024 года Docker Hub закрыл доступ пользователям из России, сославшись на экспортный контроль США. С тех пор доступ не раз менялся, и строить рабочую сборку на прямых скачиваниях оттуда рискованно. Компании решают это законным путём: держат собственный реестр образов (Harbor, Nexus, реестр в облаке) или ставят зеркало-кэш. Российское зеркало Docker Hub, например, запустил GitVerse.

Как подключается зеркало, описано в документации Docker: адрес прописывают в ключе registry-mirrors файла /etc/docker/daemon.json и перезапускают демон.

{
  "registry-mirrors": ["https://mirror.example.com"]
}

Вместо mirror.example.com подставьте адрес из инструкции зеркала или спросите у DevOps-инженера своей команды. Зеркало работает как кэш: первый запрос оно скачивает и сохраняет, следующие отдаёт из своей копии.

Пять вопросов про Docker с собеседований

На позиции backend и DevOps Docker спрашивают почти всегда, но редко глубоко. Чаще всего звучит вот это:

  • Чем образ отличается от контейнера и где хранятся данные контейнера после его удаления.
  • Почему порядок инструкций в Dockerfile влияет на время сборки.
  • Чем CMD отличается от ENTRYPOINT.
  • Что такое многоэтапная сборка и зачем она нужна (сборка в одном образе, запуск в другом, маленьком).
  • Как контейнеры одного compose-файла находят друг друга (по имени сервиса во внутренней сети).

Тестовое задание, которое поднимается одной командой, проверяющий замечает сразу. Как тестовое выглядит с другой стороны стола, глазами тех, кто его выдаёт и проверяет, разобрано в статье о тестовом задании для разработчика.

Вопросы и ответы

Чем Docker отличается от виртуальной машины?

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

Чем docker-compose отличается от docker compose?

Через дефис пишется старая отдельная утилита Compose V1, обновления которой прекратились в июле 2023 года. Через пробел запускается Compose V2, плагин к Docker CLI, он ставится вместе с Docker Desktop. Файлы в основном совместимы, а поле version новая версия просто игнорирует.

Можно ли работать с Docker на Windows?

Да, через Docker Desktop с бэкендом WSL 2: Linux-контейнеры работают в лёгкой виртуальной машине. Учтите лицензию: для компаний больше 250 сотрудников или с выручкой больше 10 млн долларов в год Docker Desktop платный. Для учёбы и личных проектов он бесплатен.

Нужен ли Docker фронтенд-разработчику и тестировщику?

Писать Dockerfile с нуля им приходится редко, а запускать чужие сервисы локально приходится постоянно. Тестировщику Docker помогает поднять стенд нужной версии за минуту, фронтендеру запустить бэкенд без установки базы и брокера. Базовых команд и чтения compose-файла обычно достаточно.

Сколько времени нужно, чтобы освоить Docker для работы?

Базовые команды и compose для одного-двух сервисов осваиваются за несколько вечеров практики. Сети, многоэтапные сборки, размер и безопасность образов приходят за месяцы реальных проектов. Для junior-позиции хватает первого уровня и одного своего проекта, который поднимается одной командой.

Как техническое собеседование устроено со стороны нанимающего, видно по чек-листу интервьюера.

Docker есть в вакансии. А в вашем резюме?

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

Загрузить резюме Открыть вакансии

Что почитать дальше

Источники

Проверено 20 октября 2026 года. Доступность Docker Hub из России менялась несколько раз, поэтому перед настройкой сборки проверьте её сами.

Рассылка

Подписываясь, вы соглашаетесь с политикой обработки персональных данных.