Docker для начинающих: образ, контейнер, volume и docker compose

Docker для начинающих: образ, контейнер, volume и docker compose

Многие слышали про Docker, но, запустив его в первый раз, не поняли, что происходит. Помню, как впервые прочитал слово «контейнер» и представил коробку 📦. На самом деле идея проще: Docker — это способ упаковать программу со всеми зависимостями так, чтобы она везде работала одинаково.

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

Образ (image) и контейнер (container)

Представьте, что образ — это формочка для пирогов, а контейнер — готовый пирог.

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

Контейнер — работающая программа, запущенная из образа. Из одного образа можно запустить много контейнеров, как из одной формочки испечь много пирогов.

Пример Dockerfile для простого Python-приложения:

FROM python:3.12-slim

WORKDIR /app

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

COPY app.py .

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

Что здесь:
— FROM python:3.12-slim — берём готовый образ, в котором уже установлен Python 3.12
— WORKDIR /app — рабочая папка внутри контейнера
— COPY — копируем файлы с компьютера в образ
— RUN — выполняем команду при сборке (здесь — установка зависимостей)
— CMD — команда, которая запускается при старте контейнера

Зависимости копируются раньше кода не случайно: если вы поменяли только app.py, Docker возьмёт установленные пакеты из кэша, и пересборка займёт секунды.

Собираем образ:

docker build -t my-app:1.0 .

Здесь -t my-app:1.0 — имя образа и версия. Точка . означает, что Dockerfile лежит в текущей папке.

Запускаем контейнер из образа:

docker run my-app:1.0

Если приложение слушает порт (например, веб-сервер на порту 5000), запускаем так:

docker run -p 8080:5000 my-app:1.0

-p 8080:5000 значит: «порт 8080 на моём компьютере перенаправляй на порт 5000 в контейнере». Теперь приложение открывается по адресу http://localhost:8080.

Volume: хранение данных

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

Volume — это хранилище, которое живёт отдельно от контейнера. Контейнер можно удалить и создать заново, а данные останутся.

Пример: запускаем PostgreSQL с volume для данных:

docker run -d --name db \
  -e POSTGRES_PASSWORD=сложный_пароль \
  -v my_data:/var/lib/postgresql/data \
  postgres:16

-v my_data:/var/lib/postgresql/data означает: «создай volume my_data и подключи его к папке /var/lib/postgresql/data в контейнере». Без POSTGRES_PASSWORD официальный образ PostgreSQL не запустится — это частая ошибка новичков.

Теперь, даже если контейнер удалить, данные в my_data сохранятся.

Посмотреть список volume:

docker volume ls

Удалить volume:

docker volume rm my_data

⚠️ Внимание: удаление volume стирает все данные в нём. Проверяйте дважды.

Docker Compose: несколько сервисов вместе

Обычно приложение состоит из нескольких частей: веб-сервер, база данных, кэш. Запускать каждый контейнер отдельной командой неудобно.

Docker Compose — это файл, в котором описаны все сервисы и связи между ними.

Файл compose.yaml (старое имя docker-compose.yml тоже работает):

services:
  web:
    image: my-app:1.0
    ports:
      - "8080:5000"
    environment:
      DATABASE_URL: postgresql://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

Что здесь:
— services — список сервисов (web и db)
— image — какой образ использовать
— ports — какие порты открыть наружу (у базы их нет, и это правильно: к ней ходит только web)
— environment — переменные окружения (логин, пароль, адреса). В реальном проекте пароли выносят в файл .env, который не попадает в Git
— depends_on — compose сначала запустит db, потом web. Но он не ждёт, пока база будет готова принимать подключения, поэтому приложение должно уметь переподключаться
— volumes — какие volume использовать

Сеть отдельно описывать не нужно: сервисы одного compose-файла сами видят друг друга по имени. Поэтому web обращается к базе по адресу db:5432.

Запускаем всё:

docker compose up -d

-d — запуск в фоне.

Посмотреть логи:

docker compose logs -f web

-f — показывать новые строки по мере появления.

Остановить всё:

docker compose down

Если нужно удалить ещё и volume (с данными!):

docker compose down -v

В старых инструкциях встречается команда docker-compose через дефис — это прежняя версия. Сейчас compose встроен в Docker и пишется через пробел.

Зачем это нужно

Docker пригодится в трёх случаях:

  1. На своём сервере: упаковали приложение в образ — и оно работает одинаково на любой машине. Обновление простое: собрали новый образ, выполнили docker compose up -d, и compose пересоздаст изменившиеся контейнеры.

  2. В облаке и Kubernetes: почти все современные платформы запускают приложения из Docker-образов. Без образа пришлось бы вручную устанавливать всё на сервер.

  3. В разработке: у всей команды одинаковое окружение. Не будет ситуации «у меня работает, а у тебя нет» из-за разных версий Python или PostgreSQL.

Типичные ошибки

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

Решение: берите облегчённые базовые образы (python:3.12-slim, node:22-alpine), а для компилируемых языков используйте многоэтапную сборку: собираете в одном образе, в итоговый кладёте только готовую программу.

Ошибка 2: забыть про volume — и данные потеряются при пересоздании контейнера.

Решение: для баз данных и всего, что пишет файлы, всегда указывайте volume.

Ошибка 3: открывать в интернет порты, которым там не место (база данных, Redis, панели управления).

Решение: публикуйте через ports только то, что должно быть доступно снаружи (веб, API). Если сервис нужен только на этом же сервере, привяжите его к локальному адресу: 127.0.0.1:5432:5432.

Ошибка 4: забыть, что контейнер изолирован. Если приложение ищет файл /etc/config.txt, а вы положили его в /home/user/config.txt на сервере, внутри контейнера он не найдётся.

Решение: копируйте файл в образ через COPY или подключайте его через volume, а при ошибках смотрите логи: docker compose logs.

Дальше

Docker — это начало. Когда сервис растёт, приходится:

  • Масштабировать: запускать несколько копий приложения и распределять между ними нагрузку (балансировщик).
  • Обновлять без простоя: rolling update — новые копии запускаются и начинают принимать запросы, а старые дорабатывают текущие и только потом останавливаются.

Для этого используют Kubernetes или облачные сервисы (AWS ECS, Google Cloud Run). Но это уже следующий шаг после Docker.

На чём бы ни был написан ваш сервис — Python, Node.js или что-то ещё, — Docker упакует его целиком. Начните с него, разберитесь, а потом усложняйте.

Есть задача — развернуть бота, мини-приложение или сервис? Напишите, помогу понять, нужен ли Docker в вашем случае и как всё настроить.

Нужен Telegram-бот для вашего дела?

Расскажите задачу — отвечу в течение дня, предложу решение и точную цену.

Написать в Telegram