Docker с нуля: контейнеризация и практика

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

1. Введение в контейнеризацию и установка Docker

Введение в контейнеризацию и установка Docker

Зачем нужна контейнеризация

Когда вы разрабатываете приложение, оно почти всегда зависит от окружения:

  • версии языка и рантайма
  • системных библиотек
  • настроек сервиса (порты, переменные окружения)
  • сторонних утилит
  • Проблема в том, что на разных компьютерах и серверах окружение отличается. Отсюда появляются ситуации вроде «у меня работает, а на сервере нет».

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

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

    Виртуальная машина (VM) запускает полноценную гостевую ОС поверх гипервизора. Контейнеры же используют ядро хостовой ОС и изолируют процессы на уровне операционной системы.

    !Сравнение архитектуры VM и контейнеров

    Практические последствия:

  • Контейнеры обычно стартуют быстрее, чем VM
  • Контейнеры легче по размеру и проще масштабируются
  • Контейнеры удобны для доставки приложения вместе с зависимостями
  • Важно: контейнеры не являются «полной виртуализацией» железа, и их изоляция устроена иначе, чем у VM.

    Базовые понятия Docker

    Docker

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

    Официальная документация: Docker Docs

    Образ

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

    Пример: образ с Nginx или образ с Python.

    Контейнер

    Контейнер (container) — это запущенный экземпляр образа. Если образ — это рецепт, то контейнер — приготовленное блюдо.

    Реестр

    Реестр (registry) — место, где хранятся образы. Самый популярный публичный реестр — Docker Hub.

    Dockerfile

    Dockerfile — файл с инструкциями, как собрать образ. В этом курсе вы к нему перейдёте позже, после освоения базовых команд.

    Что именно устанавливается

    Docker Engine

    Docker Engine — это «движок», который запускает контейнеры. Внутри него есть демон (фоновая служба) и API.

    Docker CLI

    Docker CLI — командная строка docker, через которую вы управляете движком.

    Docker Desktop

    Docker Desktop — удобный пакет для Windows и macOS (и также доступен для Linux), который включает Docker Engine, CLI и графический интерфейс.

    Установка Docker

    Надёжнее всего ставить Docker по официальным инструкциям под вашу ОС.

    Главная страница установки: Get Docker

    Windows

    Рекомендуемый вариант — Docker Desktop.

    Ключевые моменты:

  • современные версии Docker Desktop используют WSL 2 (подсистема Linux в Windows)
  • после установки Docker Desktop обычно предлагает включить WSL 2 и поставить нужные компоненты
  • Порядок действий:

  • Скачайте Docker Desktop со страницы Get Docker
  • Установите приложение
  • Запустите Docker Desktop и дождитесь статуса running
  • Откройте PowerShell или Windows Terminal и выполните проверку из раздела Проверка установки
  • macOS

    Также рекомендуется Docker Desktop.

    Порядок действий:

  • Скачайте Docker Desktop со страницы Get Docker
  • Установите приложение и запустите
  • Дайте нужные разрешения, если система их запросит
  • Выполните проверку из раздела Проверка установки
  • Linux

    На Linux чаще всего ставят Docker Engine из репозиториев, рекомендованных Docker.

    Важные моменты:

  • команды установки отличаются для разных дистрибутивов
  • после установки может понадобиться настроить запуск без sudo
  • Выберите вашу систему в официальной инструкции: Install Docker Engine

    #### Запуск Docker без sudo (опционально)

    По умолчанию Docker может требовать sudo, потому что доступ к демону — привилегированная операция.

    Если вы хотите запускать docker от обычного пользователя, официальный подход — добавить пользователя в группу docker:

  • Создайте группу docker (если её нет):
  • Добавьте текущего пользователя в группу:
  • Перезайдите в систему (или перезапустите сессию), затем проверьте:
  • Подробности: Manage Docker as a non-root user

    Проверка установки

    После установки выполните две базовые проверки.

    Проверка версий

    Что важно увидеть:

  • информация о клиенте (Client)
  • информация о сервере (Server)
  • Если есть только Client, а Server не доступен, обычно это означает, что Docker Engine не запущен.

    Запуск тестового контейнера

    Что делает эта команда:

  • Docker ищет образ hello-world локально
  • если не находит, скачивает его из реестра
  • запускает контейнер, который печатает сообщение и завершает работу
  • Если вы увидели приветственный текст, значит базовый цикл скачать образ → запустить контейнер работает.

    Как читать простую команду docker run

    В этом курсе вы будете часто видеть команды вида:

    На старте достаточно понимать:

  • docker run — создать и запустить контейнер
  • <образ> — из какого образа запускать
  • <команда> — что выполнить внутри контейнера (часто уже задано в образе и не требуется)
  • Частые проблемы и быстрые ориентиры

  • Docker Desktop установлен, но docker не работает
  • - проверьте, что Docker Desktop запущен и движок в статусе running
  • На Linux ошибка вида permission denied при обращении к Docker
  • - временно используйте sudo docker ... или настройте группу docker по официальной инструкции
  • На Windows контейнеры не стартуют
  • - часто причина в WSL 2: проверьте, что WSL включён и установлен, и что Docker Desktop использует WSL 2 backend

    Что дальше по курсу

    Дальше вы будете:

  • изучать основные команды для управления образами и контейнерами
  • разбираться с портами, томами и переменными окружения
  • собирать собственные образы через Dockerfile
  • собирать мини-проекты и практиковать типовые сценарии разработки
  • 2. Образы и контейнеры: запуск, управление, логи

    Образы и контейнеры: запуск, управление, логи

    Связь с предыдущей темой

    В прошлой статье вы установили Docker и проверили, что базовый цикл скачать образ → запустить контейнер работает на примере hello-world. Теперь закрепим это на практике: научимся управлять образами и контейнерами, разберём жизненный цикл контейнера и научимся смотреть логи.

    Образ и контейнер: простая модель

    Образ — это неизменяемый шаблон приложения (файлы + зависимости + метаданные), из которого можно запускать контейнеры.

    Контейнер — это запущенный (или остановленный) экземпляр образа, у которого есть:

  • свой файловый слой поверх образа
  • свой идентификатор и имя
  • своё состояние (создан, запущен, остановлен)
  • !Схема связи образа и нескольких контейнеров

    Работа с образами

    Где берутся образы

    Образы чаще всего скачиваются из реестра (например, Docker Hub), либо собираются вами через Dockerfile (к этому вы перейдёте позже).

    Docker Hub: Docker Hub

    Скачивание образа

  • nginx — имя репозитория
  • latest — тег (версия). Если тег не указан, Docker обычно подразумевает latest, но в реальной работе лучше явно указывать версию
  • Просмотр локальных образов

    Вы увидите список репозиториев, тегов, идентификаторов и размеров.

    Удаление образа

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

    Первый полезный запуск контейнера

    Запустим Nginx и откроем его в браузере.

    Разберём опции:

  • -d — запустить в фоне (detached)
  • --name web — присвоить контейнеру понятное имя вместо случайного
  • -p 8080:80 — проброс порта хоста 8080 на порт контейнера 80
  • После запуска откройте:

  • http://localhost:8080
  • Чтобы остановить контейнер:

    Чтобы снова запустить уже созданный контейнер:

    Важно: docker run создает новый контейнер, а docker start запускает существующий.

    Жизненный цикл контейнера и основные команды

    Список контейнеров

    Показать запущенные контейнеры:

    Показать все, включая остановленные:

    Остановка, запуск, перезапуск

  • Остановить:
  • Запустить:
  • Перезапустить:
  • Удаление контейнера

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

    Удалить контейнер автоматически после завершения (удобно для одноразовых команд):

    Здесь alpine — маленький Linux-образ, часто используемый для утилитарных задач.

    Логи контейнера

    Логи — это один из первых инструментов диагностики. В контейнерах принято писать логи в стандартные потоки вывода (stdout/stderr), и Docker умеет их показывать.

    Показать логи контейнера:

    Следить за логами в реальном времени:

    Показать последние 50 строк:

    Полезно помнить:

  • если контейнер “молчит”, возможно приложение внутри не пишет в stdout/stderr
  • если контейнер быстро завершается, docker logs часто подсказывает причину
  • Выполнение команд внутри контейнера

    Иногда нужно “зайти” в контейнер и выполнить команду (например, посмотреть файлы или проверить конфиг).

    Запустить команду внутри запущенного контейнера:

    Открыть интерактивную оболочку (если она есть в образе):

  • -i — интерактивный режим
  • -t — выделить псевдотерминал
  • Замечание: в разных образах доступна разная оболочка. Часто это sh. bash может отсутствовать.

    Переменные окружения при запуске

    Переменные окружения — частый способ конфигурации контейнеров.

    Пример (на базе образа, который печатает переменные):

  • -e MY_VAR=123 задаёт переменную окружения внутри контейнера
  • В реальных сервисах (например, базы данных) переменными окружения часто задают пароль, имя базы, режим работы и т.д.

    Быстрая диагностика: inspect и “почему не работает”

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

    Если Nginx недоступен по localhost:8080, проверьте по порядку:

  • Контейнер запущен ли вообще:
  • Проброс портов:
  • Логи:
  • Не занят ли порт 8080 на хосте другим приложением (в этом случае запустите с другим портом, например -p 8081:80)
  • Краткая шпаргалка команд

    | Задача | Команда | Что важно помнить | |---|---|---| | Скачать образ | docker pull nginx:latest | Лучше указывать тег явно | | Список образов | docker images | Показывает только локальные | | Запустить новый контейнер | docker run ... | Создает новый контейнер | | Список запущенных | docker ps | По умолчанию только running | | Список всех | docker ps -a | Видно остановленные | | Остановить | docker stop <name> | Корректная остановка процесса | | Запустить существующий | docker start <name> | Не создает новый | | Логи | docker logs <name> | -f для просмотра “вживую” | | Команда внутри контейнера | docker exec <name> ... | Работает для запущенных | | Удалить контейнер | docker rm <name> | Контейнер должен быть остановлен | | Удалить образ | docker rmi <image> | Мешают связанные контейнеры |

    Что дальше по курсу

    Дальше вы будете углубляться в практику запуска сервисов: разберёте порты и сетевое взаимодействие, тома для хранения данных, а затем перейдёте к сборке собственных образов через Dockerfile и к более реалистичным сценариям разработки.

    3. Dockerfile: сборка собственных образов

    Dockerfile: сборка собственных образов

    Связь с предыдущими темами

    В прошлых статьях вы научились:

  • устанавливать Docker и проверять, что он работает
  • скачивать готовые образы
  • запускать контейнеры, смотреть логи и выполнять команды внутри контейнера
  • Следующий шаг в практике Docker — научиться собирать свои образы. Для этого нужен Dockerfile: текстовый файл с инструкциями, как получить образ, который затем можно запускать как контейнер.

    Что такое Dockerfile и зачем он нужен

    Dockerfile — это рецепт сборки образа. Он позволяет:

  • зафиксировать окружение приложения (версии рантайма, пакеты, файлы)
  • делать сборку повторяемой на любом компьютере и сервере
  • отправлять образ в реестр (например, Docker Hub) и запускать его где угодно
  • Главная идея: вы описываете шаги сборки, а Docker превращает их в образ, из которого можно запускать контейнеры.

    Как Docker собирает образ: слои и кэш

    Docker собирает образ слоями: каждая инструкция Dockerfile обычно добавляет новый слой (или метаданные), а Docker пытается переиспользовать уже собранные слои через кэш.

    Практический смысл кэша:

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

    Контекст сборки: откуда Docker берёт файлы

    Когда вы выполняете сборку, вы передаёте Docker контекст сборки — папку, содержимое которой доступно инструкциям COPY и ADD.

    Типичная команда:

    Здесь точка . означает: контекст сборки — текущая папка.

    Важно помнить:

  • Docker не может сделать COPY файлов, которых нет в контексте
  • слишком большой контекст замедляет сборку, потому что Docker должен отправить его сборщику
  • Минимальный Dockerfile: свой «hello-world»

    Сделаем образ, который при запуске печатает текст.

    Создайте папку проекта и файл Dockerfile внутри:

    Разберём инструкции:

  • FROM задаёт базовый образ (откуда начинаем)
  • CMD задаёт команду по умолчанию при запуске контейнера
  • Сборка:

    Запуск:

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

    Полезный пример: статический сайт на встроенном HTTP-сервере Python

    Соберём образ, который запускает простой веб-сервер и раздаёт файл index.html.

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

  • Dockerfile
  • index.html
  • Пример index.html:

    Dockerfile

    Что здесь происходит:

  • FROM python:3.12-slim берёт компактный образ с Python
  • WORKDIR /app задаёт рабочую директорию внутри образа
  • COPY копирует файл из контекста сборки внутрь образа
  • EXPOSE 8000 документирует, что контейнер слушает порт 8000
  • CMD запускает сервер
  • Сборка:

    Запуск с публикацией порта:

    Проверьте в браузере:

  • http://localhost:8080
  • Важно: EXPOSE сам по себе не делает порт доступным с хоста. Чтобы открыть доступ с хоста, нужен -p host_port:container_port.

    Основные инструкции Dockerfile

    Ниже — набор инструкций, которые чаще всего встречаются в реальных проектах.

    FROM

    FROM выбирает базовый образ. Почти любой Dockerfile начинается с него.

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

  • по возможности фиксируйте версию, а не полагайтесь на latest
  • выбирайте минимальный подходящий образ (например, *-slim)
  • Документация: Dockerfile reference

    WORKDIR

    WORKDIR /path задаёт рабочую папку для следующих инструкций (RUN, COPY, CMD).

    Плюсы:

  • не нужно постоянно писать длинные пути
  • проще читать Dockerfile
  • COPY

    COPY <src> <dest> копирует файлы из контекста сборки внутрь образа.

    Важно:

  • источники берутся только из контекста сборки
  • COPY . . копирует всё из контекста в текущую WORKDIR
  • RUN

    RUN ... выполняет команду на этапе сборки и сохраняет результат в слое образа.

    Примеры:

  • установка пакетов
  • сборка приложения
  • генерация файлов
  • CMD

    CMD — команда по умолчанию, которая запускается при docker run.

    Особенности:

  • CMD можно переопределить, дописав команду в конце docker run
  • Пример переопределения:

    ENTRYPOINT

    ENTRYPOINT задаёт «основную» команду контейнера. Часто используется, когда контейнер должен вести себя как утилита.

    Простая модель:

  • ENTRYPOINT — что именно запускать
  • CMD — аргументы по умолчанию для запуска
  • ENV и ARG

  • ENV KEY=value задаёт переменные окружения внутри образа и контейнера
  • ARG KEY=value задаёт переменную только на время сборки (она доступна в Dockerfile)
  • Важное правило безопасности:

  • не кладите секреты в ENV и не хардкодьте их в Dockerfile
  • Документация по сборке: docker build

    .dockerignore: ускоряем сборку и не тащим лишнее

    Файл .dockerignore позволяет исключить файлы из контекста сборки.

    Это полезно, чтобы:

  • ускорить сборку
  • не копировать мусор (кэш, логи, сборочные артефакты)
  • случайно не отправить в контекст секреты
  • Пример .dockerignore:

    Как ускорить сборку: правильный порядок инструкций

    Одна из самых практичных техник — отделять зависимости от часто меняющегося кода.

    Пример для Python-приложения с requirements.txt:

    Почему это ускоряет сборку:

  • зависимости ставятся на шаге, который меняется редко
  • если вы изменили только код, Docker часто переиспользует кэш слоя с установленными зависимостями
  • Мультистейдж-сборка: идея без углубления

    Иногда нужно собрать приложение «тяжёлым» окружением (компиляторы, build-инструменты), но запускать в «лёгком». Для этого используют мультистейдж (несколько FROM в одном Dockerfile): один этап собирает, другой — запускает.

    Официальная практика: Use multi-stage builds

    На этом этапе курса достаточно понимать цель:

  • уменьшить размер итогового образа
  • убрать из runtime-образа лишние инструменты
  • Типичные ошибки новичков

  • Путаница между docker run и docker start
  • - docker run создаёт новый контейнер, docker start запускает уже созданный
  • Ожидание, что EXPOSE «открывает порт наружу»
  • - наружу порт публикует только -p или -P
  • Копирование всего подряд через COPY . . без .dockerignore
  • - сборка становится медленной и непредсказуемой
  • Использование latest в учебных и рабочих проектах
  • - сборка может внезапно начать работать иначе из-за обновления базового образа

    Что дальше по курсу

    Теперь у вас есть базовый навык: превращать исходники в образ через Dockerfile и запускать его как контейнер. Дальше логичное продолжение практики — научиться подключать хранилище (тома), настраивать сеть между контейнерами и запускать несколько сервисов вместе.

    4. Хранение данных: volumes и bind mounts

    Хранение данных: volumes и bind mounts

    Связь с предыдущими темами

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

    Docker предлагает два основных механизма “вынести” данные за пределы контейнера:

  • volumes (тома, управляемые Docker)
  • bind mounts (примонтированные папки и файлы с вашего компьютера)
  • Почему данные могут “пропасть”

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

  • Образ (image) неизменяемый
  • Контейнер добавляет поверх образа свой записываемый слой
  • Если контейнер удалить и создать новый, изменения в файловой системе контейнера не будут перенесены в новый контейнер
  • Это нормально для одноразовых задач, но плохо для:

  • баз данных
  • загрузок пользователей
  • кэшей, которые нужно сохранять
  • логов, которые вы хотите хранить вне контейнера
  • !Схема показывает, почему данные контейнера не стоит хранить только в writable слое и как volumes/bind mounts подключаются к контейнеру

    Два подхода к хранению данных

    Volume

    Volume — это хранилище, которое создаёт и управляет Docker. Вы подключаете volume в контейнер по пути, и данные сохраняются независимо от жизненного цикла контейнера.

  • Docker сам выбирает, где физически хранить данные
  • volume удобно переносить между контейнерами
  • volume хорошо подходит для production-данных
  • Документация: Docker Volumes

    Bind mount

    Bind mount — это примонтирование конкретного файла или папки с вашего компьютера внутрь контейнера.

  • вы полностью контролируете путь на хосте
  • удобно для разработки (код на хосте, запуск в контейнере)
  • важно следить за правами доступа и путями
  • Документация: Bind mounts

    Volumes на практике

    Создание и просмотр volumes

    Создать именованный volume:

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

    Посмотреть подробности (включая точку хранения на хосте):

    Удалить volume:

    Удалить неиспользуемые volumes:

    Важно: volume можно удалить, только если он не используется контейнерами.

    Подключение volume к контейнеру

    Есть два популярных синтаксиса: короткий -v и более явный --mount.

    Короткий синтаксис:

    Явный синтаксис:

    Проверим, что данные действительно записываются в volume:

    Теперь удалим контейнер и создадим новый, подключив тот же volume:

    Если вы увидели hello, значит данные пережили удаление контейнера.

    Реалистичный пример: PostgreSQL с volume

    Базы данных почти всегда требуют устойчивого хранения.

  • pg-data хранит данные БД
  • /var/lib/postgresql/data — стандартная директория данных PostgreSQL внутри контейнера
  • Документация по образу: Postgres image on Docker Hub

    Bind mounts на практике

    Когда bind mounts удобнее volumes

    Bind mounts особенно полезны, когда:

  • вы разрабатываете приложение и хотите, чтобы изменения в коде сразу отражались в контейнере
  • вы хотите подложить контейнеру конфиг с хоста
  • вам нужно работать с конкретными файлами в понятном месте файловой системы
  • Пример: примонтировать папку проекта в контейнер

    Предположим, у вас есть локальная папка проекта ./app.

    Linux и macOS:

    bash docker run --rm -it -v "(pwd)/app":/app:ro alpine:3.20 ls -la /app bash docker run --rm \ --mount type=bind,source="$(pwd)/app",target=/app,readonly \ alpine:3.20 ls -la /app ``

    Что выбрать: volume или bind mount

    | Критерий | Volume | Bind mount | |---|---|---| | Где физически хранятся данные | В месте, управляемом Docker | В конкретном пути на вашем компьютере | | Подходит для production данных | Да | Обычно нет, зависит от окружения | | Подходит для разработки (live-код) | Реже | Да | | Переносимость между хостами | Выше | Ниже (пути завязаны на хост) | | Типовые риски | “Забыли удалить volume и копится мусор” | Права доступа, неверные пути, случайная перезапись файлов |

    Частые ошибки и практические советы

  • Путаница жизненного цикла
  • Контейнер можно удалить, а volume оставить, данные сохранятся
  • Если удалить volume, данные исчезнут
  • Неправильный путь в bind mount
  • Если указать несуществующий путь, Docker может создать папку автоматически (зависит от ОС и сценария), и вы будете смотреть “не туда”
  • Права доступа
  • Процесс внутри контейнера может работать не под вашим пользователем
  • Если контейнер не может писать в примонтированную папку, проблема часто в правах на хосте
  • Выбор синтаксиса
  • --mount` обычно проще читать и меньше ошибаться в сложных сценариях
  • Документация по синтаксису: docker run reference

    Что дальше по курсу

    Теперь вы умеете сохранять данные вне контейнера и подключать файлы с хоста. Это основа для практики с базами данных, конфигами и разработкой “код на хосте, запуск в контейнере”. Следующий логичный шаг — научиться связывать несколько контейнеров в одну систему через сеть и запускать их вместе.

    5. Сети в Docker: порты, bridge, взаимодействие сервисов

    Сети в Docker: порты, bridge, взаимодействие сервисов

    Связь с предыдущими темами

    Ранее вы научились:

  • запускать контейнеры и публиковать порты через -p
  • собирать свои образы с помощью Dockerfile
  • сохранять данные через volumes и подключать файлы через bind mounts
  • Теперь соберём это в более реалистичную картину: как контейнеры общаются друг с другом по сети, что такое bridge-сеть, и почему один сервис “не видит” другой, пока вы не настроите сеть правильно.

    Базовая модель сетей в Docker

    У Docker есть встроенный сетевой драйвер bridge, который чаще всего используется на одном хосте.

    Идея:

  • контейнеры получают собственные IP-адреса в виртуальной сети
  • контейнеры могут общаться между собой по этой сети
  • доступ извне (с вашего компьютера) обычно делается через публикацию портов
  • !Как хост попадает в контейнер через опубликованный порт, а контейнеры общаются по имени внутри bridge-сети

    Порты: что значит “открыть” контейнер наружу

    Порт внутри контейнера и порт на хосте

    Важно различать:

  • порт контейнера — где приложение слушает внутри контейнера
  • порт хоста — куда вы обращаетесь снаружи (например, из браузера)
  • Команда:

    Означает:

  • Nginx слушает 80 внутри контейнера
  • Docker публикует его как 8080 на хосте
  • вы открываете http://localhost:8080, а запрос попадает в контейнер на порт 80
  • -p и -P

  • -p host_port:container_port — явная публикация порта
  • -P — публикация всех портов, указанных как EXPOSE в образе, на случайные порты хоста
  • Пример:

    Команда docker port покажет, на какой порт хоста Docker “повесил” контейнерный порт.

    EXPOSE в Dockerfile — это не публикация

    EXPOSE — это документация в образе о том, какой порт обычно использует приложение.

    Чтобы порт стал доступен с хоста, нужен -p или -P.

    Почему localhost внутри контейнера — это не ваш компьютер

    Типичная ошибка: приложение в контейнере пытается обратиться к localhost:5432 и не находит базу данных.

    Правило:

  • localhost внутри контейнера — это сам контейнер
  • другой контейнер — это другой сетевой “узел”, даже если они на одном компьютере
  • Поэтому для связи сервисов почти всегда используют:

  • имя контейнера или сетевой алиас (например, db)
  • порт сервиса внутри сети (например, 5432)
  • Bridge-сети: стандартная и пользовательская

    Стандартная сеть bridge

    По умолчанию контейнеры подключаются к стандартной сети bridge.

    Минусы для практики:

  • DNS по имени контейнера в стандартной bridge ведёт себя ограниченно и зависит от сценария
  • лучше сразу привыкать к пользовательским сетям, где всё предсказуемо
  • Пользовательская bridge-сеть

    Пользовательская сеть решает задачу “сервисы должны находить друг друга по имени”.

    Создать сеть:

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

    Посмотреть детали (включая подключённые контейнеры):

    Взаимодействие сервисов по имени (DNS внутри сети)

    В пользовательской bridge-сети Docker даёт встроенный DNS:

  • контейнер db доступен по имени db
  • контейнер web доступен по имени web
  • То есть приложение в контейнере web может подключаться к базе по адресу db:5432.

    Практика: два сервиса в одной сети (web + PostgreSQL)

    Ниже пример без Docker Compose, чтобы закрепить именно сети.

    Шаг 1. Создаём сеть

    Шаг 2. Запускаем PostgreSQL в этой сети

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

    Пояснения:

  • --network app-net подключает контейнер к сети
  • -v pg-data:... сохраняет данные базы между пересозданиями контейнера
  • отсутствие -p означает, что база не торчит наружу на ваш компьютер
  • Шаг 3. Проверяем, что web видит db по имени

    Запустим временный контейнер в той же сети и проверим DNS:

    Если имя резолвится, значит сеть настроена правильно.

    Шаг 4. Публикуем наружу только web

    В качестве простого “web” возьмём Nginx (тут важно именно то, как он публикуется наружу, а не логика приложения):

    Итог:

  • с хоста доступен только web по localhost:8080
  • внутри сети контейнеры могут общаться друг с другом
  • база остаётся внутренней частью системы
  • Подключение контейнера к сети и сетевые алиасы

    Подключить уже созданный контейнер к сети

    Если контейнер уже существует, его можно подключить:

    И отключить:

    Алиас для сервиса

    Иногда удобно дать сервису “стабильное” имя (например, postgres вместо db):

    После этого внутри сети можно обращаться и как db, и как postgres.

    Как контейнеру обратиться к сервису на хосте

    Иногда контейнеру нужен доступ к вашему компьютеру (например, к локальному API, не в Docker).

  • На Docker Desktop (Windows/macOS) часто работает имя host.docker.internal
  • На Linux это зависит от версии Docker и настроек; иногда используют --add-host или специальные сетевые режимы
  • Документация:

  • Docker container networking
  • Use host.docker.internal
  • Быстрая диагностика сетевых проблем

    Проверить, опубликован ли порт

    Посмотреть сетевые настройки контейнера

    Что полезно найти в выводе:

  • какие сети подключены
  • какие IP-адреса выданы
  • какие порты опубликованы
  • Проверить, что сервис жив

  • логи:
  • выполнить команду внутри контейнера:
  • Практические правила, которые экономят время

  • Создавайте отдельную пользовательскую сеть для набора связанных сервисов (docker network create ...).
  • Публикуйте наружу только то, что реально должно быть доступно с хоста.
  • Для связи контейнеров используйте имена контейнеров или алиасы, а не IP.
  • Не используйте localhost для обращения к другому контейнеру: это почти всегда ошибка.
  • Что дальше

    Вы уже умеете запускать контейнеры, собирать образы, подключать данные и настраивать сеть. Следующий логичный шаг практики — запуск нескольких сервисов как единого приложения одной командой и управление ими как набором. Обычно для этого используют Docker Compose.

    6. Docker Compose: многоконтейнерные приложения

    Docker Compose: многоконтейнерные приложения

    Связь с предыдущими темами

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

    Проблема в том, что как только сервисов становится больше одного (например, web + db), ручные команды docker run ... становятся неудобными:

  • трудно помнить параметры (порты, сети, переменные окружения, volumes)
  • сложно повторять запуск на другом компьютере
  • неудобно поднимать и останавливать всё приложение целиком
  • Docker Compose решает эту задачу: вы описываете набор сервисов в одном файле, а затем запускаете всё одной командой.

    Что такое Docker Compose

    Docker Compose — инструмент для описания и запуска многоконтейнерных приложений.

    Главная идея:

  • вы храните конфигурацию в файле compose.yaml (или docker-compose.yml)
  • вы запускаете приложение командами docker compose up, docker compose down
  • Официальные материалы:

  • Docker Compose overview
  • Compose file reference
  • docker compose CLI reference
  • Как Compose связывает сервисы: сеть, DNS и имена

    По умолчанию Compose создаёт:

  • отдельную пользовательскую bridge-сеть для проекта
  • DNS-имена сервисов внутри этой сети
  • Практический смысл:

  • сервис db доступен другим сервисам по имени db (например, db:5432)
  • вам обычно не нужно вручную создавать сеть через docker network create ...
  • !Схема показывает: наружу опубликованы только нужные порты, а сервисы общаются внутри сети по именам

    Структура compose.yaml: ключевые понятия

    compose.yaml описывает набор сущностей. На базовом уровне вам достаточно трёх:

  • services: контейнеры вашего приложения (например, web, db)
  • volumes: именованные тома для хранения данных (например, данные PostgreSQL)
  • networks: сети (часто можно не описывать явно, Compose создаст сам)
  • Файл — это YAML. Важно:

  • отступы имеют значение
  • обычно используют 2 пробела
  • Минимальный пример: один сервис

    Пример, который удобно сравнить с docker run.

    compose.yaml:

    Запуск:

    Проверка:

  • откройте localhost:8080
  • Остановка и удаление контейнеров проекта:

    Важно: down останавливает и удаляет контейнеры, но не удаляет именованные volumes, если вы не попросите об этом отдельно.

    Практический пример: web + db + хранение данных

    Соберём типичную учебную связку:

  • db: PostgreSQL с volume для данных
  • adminer: простой веб-интерфейс для просмотра БД (чтобы быстро убедиться, что база доступна)
  • compose.yaml:

    Что здесь важно:

  • db не публикует порт наружу, он доступен только внутри сети Compose
  • adminer публикует порт 8081 на хост, чтобы вы открыли UI в браузере
  • pg-data — именованный volume, данные БД переживут пересоздание контейнера
  • depends_on задаёт порядок старта контейнеров, но не гарантирует, что база уже готова принимать подключения
  • Запуск:

    Откройте Adminer:

  • localhost:8081
  • Данные для входа (если Adminer попросит):

  • System: PostgreSQL
  • Server: db
  • Username: postgres
  • Password: pass
  • Database: app
  • Завершение работы:

    Если вы хотите удалить ещё и volume (а значит, потерять данные БД), используйте:

    Основные команды docker compose, которые используются каждый день

    Запуск

  • запуск в фоне:
  • пересобрать образы (если есть build) и запустить:
  • Просмотр состояния

  • список контейнеров проекта:
  • логи всех сервисов:
  • логи конкретного сервиса:
  • Выполнить команду внутри сервиса

    Аналогично docker exec, но через Compose:

    Замечание: exec работает для запущенных контейнеров.

    Остановка и удаление

  • остановить сервисы (контейнеры остаются созданными):
  • снова запустить:
  • остановить и удалить контейнеры, сеть и служебные ресурсы проекта:
  • image или build: запуск готового образа и сборка своего

    У сервиса может быть:

  • image: запуск готового образа из реестра
  • build: сборка образа из вашего Dockerfile
  • Пример с build:

    Это означает:

  • Compose выполнит docker build из текущей папки
  • затем запустит контейнер
  • Полезная практика:

  • во время разработки используйте build и bind mount для кода
  • в деплое часто используют уже собранный image с конкретным тегом
  • Volumes и bind mounts в Compose

    Именованный volume

    В примере с PostgreSQL было:

  • pg-data:/var/lib/postgresql/data
  • Это почти то же самое, что в docker run -v pg-data:/var/lib/postgresql/data ....

    Bind mount (удобно для разработки)

    Пример: примонтировать текущую папку в контейнер.

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

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

    Часто конфигурацию сервиса задают переменными окружения.

    Способы:

  • через environment в compose.yaml
  • через файл .env рядом с compose.yaml (Compose автоматически читает его для подстановки переменных)
  • Пример подстановки из .env:

    compose.yaml:

    .env:

    Важно:

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

  • Путаница docker compose down -v
  • down по умолчанию не удаляет именованные volumes, а -v удаляет
  • Ожидание, что depends_on гарантирует готовность базы
  • depends_on управляет порядком старта, но проверку готовности обычно делают отдельными механизмами приложения
  • Публикация лишних портов
  • наружу публикуйте только то, что реально нужно с хоста
  • Использование latest без необходимости
  • лучше фиксировать версии (например, postgres:16 вместо postgres:latest)
  • Что дальше по курсу

    Теперь вы умеете описывать многоконтейнерное приложение в compose.yaml, управлять им как единым целым и понимать, как Compose автоматически настраивает сеть и удобные имена сервисов.

    Следующий практический шаг обычно такой:

  • взять своё приложение из Dockerfile
  • добавить рядом сервис базы данных
  • подключить volume для данных и bind mount для кода
  • добиться повторяемого запуска проекта одной командой docker compose up
  • 7. Практика и деплой: registry, теги, best practices

    Практика и деплой: registry, теги, best practices

    Связь с предыдущими темами

    Ранее вы научились запускать контейнеры, собирать образы через Dockerfile, подключать данные (volumes и bind mounts), настраивать сеть и запускать несколько сервисов через Docker Compose.

    Теперь логичный шаг к практике и деплою: научиться

  • правильно именовать и версировать образы (теги)
  • хранить и распространять образы через registry
  • безопасно и повторяемо обновлять приложение на сервере
  • следовать базовым best practices, чтобы не усложнять себе жизнь
  • Что такое registry и зачем он нужен

    Registry — это хранилище Docker-образов. Оно решает задачу доставки:

  • вы собираете образ один раз
  • загружаете его в registry
  • на другом компьютере или сервере скачиваете (pull) и запускаете
  • Самый известный публичный registry — Docker Hub: Docker Hub

    Также существуют другие варианты:

  • GitHub Container Registry: GitHub Container Registry
  • GitLab Container Registry: GitLab Container Registry
  • Как устроено имя образа

    Имя образа обычно выглядит так:

  • nginx:1.27
  • username/myapp:1.0.0
  • Более общий вид:

  • registry-host/namespace/name:tag
  • Пояснение частей:

  • registry-host — адрес registry (часто опускается, тогда подразумевается Docker Hub)
  • namespace — пользователь или организация
  • name — имя репозитория образа
  • tag — версия (или иной ярлык)
  • Примеры:

  • postgres:16 означает Docker Hub + официальный образ postgres с тегом 16
  • ghcr.io/acme/api:2.3.1 означает registry ghcr.io, namespace acme, имя api, тег 2.3.1
  • Теги: что это и почему latest опасен

    Тег — это метка версии образа. Новички часто воспринимают теги как “магическое обновление”, но важно понимать:

  • тег может указывать на разное содержимое в разные моменты времени
  • если вы не фиксируете тег, вы не фиксируете окружение
  • Почему не стоит полагаться на latest

    Тег latest не означает “самое новое” по строгим правилам. Это просто обычный тег, который кто-то должен поддерживать.

    Практические проблемы:

  • сегодня latest работает, завтра — ломается из-за обновления
  • при расследовании инцидента трудно понять, какой именно образ был запущен
  • Рекомендация для обучения и работы:

  • для базовых сервисов используйте фиксированные версии: postgres:16, python:3.12-slim
  • для своих образов используйте явные версии: myapp:1.0.0, myapp:1.1.0
  • Мини-стратегия версионирования для учебных и небольших проектов

    Удобный минимум:

  • 1.0.0 как первая стабильная версия
  • 1.0.1 для багфиксов
  • 1.1.0 для новых фич без ломающих изменений
  • 2.0.0 если вы сломали обратную совместимость
  • Вам не обязательно глубоко изучать семантическое версионирование прямо сейчас, но привычка фиксировать версии резко снижает количество сюрпризов.

    Практика: собрать, затегировать и загрузить образ в Docker Hub

    Ниже — типовой сценарий доставки своего приложения.

    !Общая картинка жизненного цикла образа от сборки до запуска на сервере

    Предусловия

  • у вас есть аккаунт в Docker Hub: Docker Hub
  • вы создали репозиторий, например username/myapp (через интерфейс Docker Hub)
  • Шаг 1. Логин в registry

    Команда:

    Документация: docker login

    Шаг 2. Сборка образа

    Допустим, у вас есть Dockerfile в текущей папке.

    Шаг 3. Тегирование под имя Docker Hub

    Локальный тег myapp:1.0.0 удобен, но для загрузки в Docker Hub нужно имя вида username/myapp:1.0.0.

    Документация: docker image tag

    Шаг 4. Загрузка (push) в Docker Hub

    Документация: docker image push

    Шаг 5. Проверка: скачать образ на другом компьютере

    На другой машине (или на сервере):

    Документация: docker image pull

    Шаг 6. Запуск

    Если это веб-приложение и оно слушает порт 8000 внутри контейнера:

    Практика деплоя через Docker Compose

    В реальности удобно деплоить не одиночный контейнер, а набор сервисов.

    Пример compose.yaml для сервера

    Идея:

  • app запускается из готового образа (не build на сервере)
  • db хранит данные в volume
  • наружу публикуется только то, что нужно
  • Запуск и обновление обычно выглядят так:

    Если вы выпустили новую версию, например username/myapp:1.0.1, вы меняете тег в compose.yaml и повторяете команды.

    Best practices, которые реально помогают

    Ниже набор практик, которые чаще всего отличают “работает сейчас” от “нормально поддерживается”.

    Официальная подборка: Dockerfile best practices

    Фиксируйте версии базовых образов

    Плохо:

  • FROM python:latest
  • Лучше:

  • FROM python:3.12-slim
  • Почему:

  • повторяемость сборок
  • меньше внезапных поломок
  • Делайте образы меньше

    Практические методы:

  • используйте *-slim, если подходит
  • не ставьте лишние пакеты
  • чистите временные файлы после установки пакетов (особенно в Debian-based образах)
  • используйте мультистейдж-сборку, если есть компиляция
  • Меньший образ обычно означает:

  • быстрее загрузка на сервер
  • меньше поверхность атаки
  • быстрее старт в CI
  • Не запускайте приложение от root без необходимости

    По умолчанию многие образы работают от root. В production это риск.

    Типовой подход в Dockerfile:

    Почему это важно:

  • если приложение уязвимо, атакующий получает меньше прав внутри контейнера
  • Секреты не должны попадать в образ

    Плохие варианты:

  • пароли в Dockerfile через ENV
  • .env с секретами внутри Git-репозитория
  • копирование секретов внутрь образа через COPY
  • Рабочий минимум для старта:

  • передавайте секреты как переменные окружения на этапе запуска (Compose environment, .env вне репозитория)
  • для серьёзных окружений используйте специализированные хранилища секретов (это отдельная тема)
  • Разделяйте конфигурацию и образ

    Образ должен быть максимально “универсальным”, а окружение задаётся при запуске:

  • порты
  • адреса баз данных
  • режим работы
  • Так вы сможете запускать один и тот же образ:

  • локально
  • на тестовом сервере
  • в production
  • меняя только переменные окружения.

    Логи пишите в stdout и stderr

    Хорошая практика для контейнеров:

  • приложение пишет логи в stdout и stderr
  • Docker собирает их, а вы смотрите через docker logs или docker compose logs
  • Так проще:

  • мониторинг
  • диагностика
  • централизованный сбор логов
  • Не публикуйте лишние порты

    Правило:

  • наружу публикуйте только то, к чему вы реально подключаетесь с хоста или извне
  • Например, базу данных часто не нужно публиковать на хост, если к ней ходит только app внутри сети Compose.

    Используйте healthcheck для зависимых сервисов, когда это важно

    depends_on в Compose задаёт порядок старта, но не гарантирует “готовность” сервиса.

    Если ваша система чувствительна к старту (например, app должен ждать db), рассмотрите healthcheck.

    Пример идеи (упрощённо):

    Это не единственный способ, но сам принцип важен: порядок старта и готовность сервиса — разные вещи.

    Проверяйте уязвимости образов

    Минимум, который стоит знать:

  • образы могут содержать уязвимые пакеты
  • полезно регулярно сканировать образы
  • В Docker-экосистеме для этого есть Docker Scout: Docker Scout

    Мини-шпаргалка команд для доставки

    | Задача | Команда | |---|---| | Собрать образ | docker build -t myapp:1.0.0 . | | Присвоить тег под registry | docker tag myapp:1.0.0 username/myapp:1.0.0 | | Загрузить в registry | docker push username/myapp:1.0.0 | | Скачать на сервер | docker pull username/myapp:1.0.0 | | Запустить | docker run -d ... username/myapp:1.0.0 | | Обновить Compose-проект | docker compose pull && docker compose up -d |

    Итог

    На этом этапе вы умеете выстраивать полный путь:

  • собрать образ из Dockerfile
  • корректно проставить тег версии
  • отправить образ в registry
  • скачать и запустить на сервере
  • использовать Compose для повторяемого деплоя
  • Это база, на которую дальше легко накладываются CI, автоматические сборки, тестирование, безопасное хранение секретов и более продвинутые практики эксплуатации.