Статей про Docker много не бывает.

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

С подготовкой статьи помог:

Андрей Хомутов

Эксперт-разработчик в Ростелеком ИТ, дипломный руководитель в Нетологии (Python, JS), эксперт в ИТ–школе Ростелекома (DevOps)

Для навигации:

Что такое Docker простыми словами

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

Docker решает эту проблему — он создаёт единый и воспроизводимый способ запускать приложения.

Основная идея: Docker — это платформа контейнеризации. Она позволяет упаковать приложение вместе со всеми зависимостями в изолированную среду — контейнер

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

Чем отличается виртуализация от контейнеризации

До появления Docker основным способом изоляции приложений были виртуальные машины. 

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

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

Docker работает иначе. Он использует подход OS-level virtualization — контейнеризацию на уровне операционной системы. Вместо того чтобы поднимать отдельную ОС под каждое приложение, Docker создаёт изолированные контейнеры, которые делят ядро хостовой системы, но имеют собственное пространство процессов, файлов и сетей.

Если упрощённо:

  • виртуальная машина изолирует железо и поднимает целую операционную систему;

  • контейнер изолирует процессы в рамках одной ОС.

Как это выглядит на схеме:

Источник

5 причин, зачем нужен Docker

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

2. Лёгкость и скорость. Контейнеры используют общее ядро операционной системы и не создают отдельную копию ОС. За счёт этого о��и занимают меньше места и запускаются за секунды. Один сервер может без труда обслуживать десятки контейнеров.

3. Масштабирование. При росте нагрузки можно просто запустить дополнительные контейнеры — система быстро увеличит мощность и перераспределит ресурсы без изменения кода.

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

5. Удобная интеграция в CI/CD. Контейнеры уже стали стандартом в современных пайплайнах. Приложение можно собрать, протестировать и запустить в одинаковой среде — от локальной машины до продакшена. Благодаря этому меньше неожиданных багов, а релизы проходят быстрее и спокойнее.

Архитектура Docker: как он устроен и как работает

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

  1. CLI (Command Line Interface) — интерфейс командной строки. 

  2. Docker Daemon (dockerd) — фоновый процесс, который выполняет все реальные действия: создаёт, запускает и удаляет контейнеры.

  3. REST API — интерфейс, который предоставляет Docker Daemon для управления. В локальной установке CLI общается с демоном напрямую через сокет, но API следует REST-архитектуре.

Когда вы вводите команду вроде docker run, клиент CLI отправляет запрос в dockerd через сокет. Фоновый процесс получает эту команду, проверяет наличие нужного образа, готовит окружение, настраивает сеть, монтирует тома и запускает контейнер. Вся работа по созданию и управлению контейнерами происходит именно внутри демона (Docker Daemon) — он оркестрирует остальные части системы.

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

  • containerd отвечает за жизненный цикл контейнеров — создание, запуск, остановку и удаление

  • runc делает сам запуск контейнера: изолирует процессы и использует механизмы ядра Linux  которые отвечают за изоляцию и контроль ресурсов (namespaces, cgroups и другие). О них поговорим дальше.

Обе следуют стандартам OCI (Open Container Initiative) — это набор правил, которые определяют формат контейнеров и способы их запуска. Благодаря этому Docker совместим с другими инструментами контейнеризации и предсказу��мо работает в разных средах.

При этом работающие контейнеры не зависят от dockerd: если демон перезапустить, они продолжат работу — за них отвечает containerd.

Вся эта цепочка выглядит так:  CLI → Docker Daemon (dockerd) → containerd → runc → процессы контейнера.

Docker Desktop

Архитектура Docker отличается в зависимости от операционной системы.

На Linux Docker Engine устанавливается напрямую и работает как системная служба (systemd). Об этом писали выше.

На Windows и macOS контейнеризация не встроена в ядро, поэтому Docker запускается через Docker Desktop.

Это настольное приложение, которое устанавливает Docker Engine внутри лёгкой виртуальной машины (в Windows — через WSL 2, в macOS — через HyperKit или Apple Virtualization Framework) и предоставляет удобный интерфейс для управления контейнерами, образами и настройками.

Docker Desktop объединяет всё в единую среду:

  • внутри работает Linux с установленным Docker Engine;

  • снаружи доступны привычный CLI и графический интерфейс;

  • настройки сети, проброс портов, управление томами и обновления выполняются автоматически.

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

Как Docker работает с образами

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

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

Как устроены слои и почему это эффективно

При сборке Docker складывает слои в определённом порядке и объединяет их в единую файловую систему с помощью драйвера overlay2. Он накладывает слои друг на друга так, что контейнер видит их как один диск, хотя физически это отдельные части.

Главное преимущество такого подхода — повторное использование слоёв. Если два образа используют один и тот же базовый слой, Docker хранит его на диске только один раз. 

Например, слой ubuntu:20.04 — это минимальная версия Ubuntu, собранная специально для Docker. В нём нет лишних программ, только базовая файловая система Linux. Его используют сотни тысяч других образов, но на диске у разработчика этот слой лежит в единственном экземпляре, и Docker просто ссылается на него.

Это экономит место и ускоряет работу: если слой уже есть, Docker не скачивает его повторно.

Как работает кэширование слоёв при сборке образа

Когда Docker собирает образ из Dockerfile, он проходит инструкции сверху вниз и создаёт слой для каждой из них. 

Источник

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

Из-за этого порядок инструкций в Dockerfile критически важен:

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

  • то, что меняется часто (исходный код приложения), — ниже.

Например:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

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

Как Docker использует слои при запуске контейнера

Docker монтирует все слои образа в режиме «Только чтение» и добавляет поверх них записываемый слой. В нём появляются временные файлы, кэш, логи и любые изменения, которые происходят во время работы приложения.

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

Чтобы данные не пропадали между перезапусками, Docker хранит их вне контейнера, но с доступом к ним изнутри. Делается это двумя способами:

  • С помощью томов (volumes) — Docker создаёт отдельные области на диске и управляет ими. Контейнер может читать данные из тома и записывать их в него, а при удалении контейнера том остаётся. 

  • С помощью монтирований (bind mounts) — к контейнеру подключается папка на хостовой системе. Например, можно смонтировать локальный каталог с кодом, чтобы контейнер работал с ним напрямую.

Хранилища образов: Docker Hub и Registry

Docker хранит образы в реестрах (registries) — это серверы, которые принимают, хранят и отдают образы по запросу.

Работают они по простому принципу: при сборке образа команда docker build сохраняет его локально, а при публикации (docker push) отправляет копию в выбранный реестр (как если бы вы выкладывали проект в GitHub). Позже этот же образ можно скачать (docker pull) на другой машине и запустить как контейнер.

Самый известный и используемый реестр — Docker Hub. В нём размещаются миллионы готовых образов: как официальные (от Docker и разработчиков языков или фреймворков), так и пользовательские. Например, образы nginx, Redis, Python, PostgreSQL — все они загружаются именно оттуда.

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

Для корпоративных сценариев Docker предлагает установить собственный Docker Registry. Он разворачивается локально или в облаке, работает по тому же API, что и Hub, и поддерживает аутентификацию, разграничение прав и контроль версий образов. Это полезно, когда образы содержат закрытый код или конфигурации, которые нельзя выкладывать в публичный доступ.

Жизненный цикл Docker-образа:

Схема показывает, как образ собирается из Dockerfile, сохраняется локально, загружается в реестр (push), скачивается обратно (pull) и запускается как контейнер. Источник
Схема показывает, как образ собирается из Dockerfile, сохраняется локально, загружается в реестр (push), скачивается обратно (pull) и запускается как контейнер. Источник

Каждый образ в реестре состоит из манифеста и набора слоёв. 

Манифест — это файл-описание, в котором указано, какие слои входят в образ и как их собрать. Сами слои хранятся отдельно — как архивы, каждый со своим уникальным идентификатором (sha256).

При загрузке или скачивании Docker проверяет, не были ли повреждены файлы образа. 

Каждый слой имеет свою контрольную сумму (sha256), и система сверяет их перед установкой. Если хотя бы один байт отличается от ожидаемого, хэш не совпадает — и такой образ не принимается. Так Docker гарантирует, что загруженные данные точно совпадают с оригиналом.

У каждого образа есть тег — вроде latest, v1.0 или dev. Тег — это просто метка, которая указывает на конкретную версию образа. Но за этой меткой всегда стоит digest — уникальный хэш (sha256), по которому Docker определяет точный набор слоёв. 

Даже если тег изменится, digest остаётся прежним — и Docker понимает, какой именно образ нужно использовать.

Сеть контейнеров

При запуске Docker автоматически подключает контейнер к внутренней виртуальной сети. По умолчанию используется bridge-сеть — изолированная среда, где каждый контейнер получает свой IP-адрес и может обмениваться данными с другими участниками этой сети.

С точки зрения контейнера это выглядит как отдельная машина с полноценным сетевым интерфейсом, маршрутизацией и DNS.

Bridge-сеть создаётся на стороне хоста: Docker поднимает виртуальный интерфейс, назначает подсеть и настраивает NAT, чтобы контейнеры могли выходить в интернет. 

Как контейнеры находят друг друга внутри сети

В пользовательских bridge-сетях Docker поднимает встроенный DNS-сервер. Благодаря этому контейнеры могут обращаться друг к другу по имени, а не по IP-адресу.

Например, если в одной сети запущены два контейнера — web и db, — то приложение внутри web может подключиться к базе по адресу db:5432. Docker сам сопоставит имя контейнера с его текущим IP-адресом, так что вручную настраивать адреса не нужно.

Доступ к контейнеру снаружи

Если приложению нужно принимать запросы извне, при запуске указывают флаг -p, чтобы пробросить порт хоста внутрь контейнера (например, -p 8080:80 делает порт 80 контейнера доступным по адресу 8080 на хосте).

Кроме стандартной сети есть и другие режимы:

  • host — контейнер работает в сетевом пространстве хоста и использует его IP-адрес и интерфейсы. Режим без изоляции, зато с максимальной производительностью. Подходит, если сервису нужен прямой доступ к сети.

  • none — сеть полностью отключена. Контейнер не получает IP, не выходит в интернет и не принимает входящие запросы. Подходит для задач, которые работают изолированно и не используют сеть. 

  • overlay — объединяет контейнеры, запущенные на разных машинах, в одну виртуальную сеть. Такой тип используется, когда приложение развёрнуто на нескольких серверах и между ними нужно наладить связь.

  • macvlan — каждому контейнеру назначается собственный MAC-адрес, и в локальной сети он виден как отдельное устройство. Это удобно для тестирования сетевых сервисов или если контейнер должен работать наравне с другими машинами.

Сетью управляет Docker Daemon: он создаёт интерфейсы, подключает контейнеры и автоматически настраивает сетевые правила.Благодаря этому контейнеры остаются изолированными друг от друга, но при этом могут безопасно взаимодействовать через чётко заданные правила.

Логи и события

Все процессы внутри контейнера работают так же, как в обычной системе Linux. Всё, что они выводят в стандартные потоки (stdout и stderr), Docker перенаправляет в драйвер логирования — систему, которая отвечает за сбор и хранение логов.

По умолчанию используется драйвер json-file: логи записываются на хосте в файл в формате JSON. Это удобно для локальной отладки и просмотра через команду docker logs.

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

  • journald — чтобы писать логи в системный журнал Linux;

  • syslog — для отправки логов на внешний сервер;

  • fluentd — для сбора и анализа данных в распределённых системах.

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

Помимо логов Docker фиксирует события — всё, что происходит в системе: запуск или остановка контейнера, создание или удаление, загрузка образа, подключение тома. Эти данные доступны через API или команду docker events.

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

Благодаря этой системе Docker остаётся прозрачным: всегда можно узнать, что происходит с каждым контейнером и когда именно, не заходя внутрь вручную.

Безопасность 

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

Namespaces

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

Например, процесс с PID 1 внутри контейнера не имеет ничего общего с системным PID 1 на хосте.

То же и с сетью: у контейнера свой IP и таблицы маршрутов, и он не видит соседей, пока их явно не объединят в одну сеть.

Cgroups (control groups)

Отвечают за контроль ресурсов. Они задают, сколько CPU, оперативной памяти и дисковых операций контейнер может использовать. 

Если приложение начнёт потреблять слишком много, cgroups ограничат его, не давая перегрузить систему или повлиять на другие контейнеры.

Помимо этого, Docker использует дополнительные механизмы безопасности:

  • Capabilities — система тонкой настройки прав. Она ограничивает возможности root-пользователя внутри контейнера. Например, процесс может записывать файлы, но не может менять сетевые настройки.

  • AppArmor и SELinux — инструменты контроля доступа. Они определяют, какие файлы и операции разрешены процессам контейнера.

  • seccomp — фильтр системных вызовов, который блокирует потенциально опасные обращения к ядру, например попытки загрузить модули или изменить сетевые интерфейсы.

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

Для дополнительной защиты Docker можно запустить в rootless-режиме — тогда контейнеры работают не от имени администратора, а под обычным пользователем. Функциональности чуть меньше, зато риск для хоста минимален.

На чём написан Docker

Docker написан на Go — простом и быстром языке, удобном для создания системных инструментов. На нём легко работать с потоками и собирать программы, которые запускаются без внешних библиотек.

На Go построены все основные части Docker:

  • dockerd — демон, который принимает команды и управляет контейнерами;

  • containerd — служба, отвечающая за запуск и работу контейнеров;

  • runc — утилита, которая создаёт изолированные процессы в Linux;

  • BuildKit — инструмент для сборки образов.

Docker использует стандарты OCI (Open Container Initiative). Они описывают, из чего состоит контейнер и как его запускать. Поэтому образы, собранные в Docker, можно использовать и в других системах — например, в Podman или CRI-O.

Исходный код Docker открыт и распространяется по лицензии Apache 2.0. Она разрешает использовать код в любых целях — в личных, учебных или коммерческих проектах. Можно изменять исходники, собирать собственные версии Docker и включать его части в другие продукты. Единственное требование — сохранять уведомление об авторских правах, текст лицензии и указывать, если вносились изменения.

Имя и логотип Docker использовать без разрешения нельзя.

Основные понятия Docker: краткий справочник

Мы уже разобрались, как устроен Docker и из чего он состоит.

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

Образ (Image) 

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

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

Контейнер (Container)

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

Когда он удаляется, его данные исчезают — поэтому для постоянного хранения используется следующий механизм.

Том (Volume) 

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

Так обычно хранят базы данных, логи и другие важные файлы.

Сеть (Network) 

Отвечает за взаимодействие контейнеров между собой и с внешним миром. Docker автоматически создаёт bridge-сеть, где каждый контейнер получает свой IP-адрес.

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

Реестр (Registry) 

Реестр — это место, где хранятся Docker-образы. 

Публичный вариант — Docker Hub, в нём размещены миллионы готовых сборок, включая официальные (nginx, Redis, Python).  Если образы должны оставаться внутри компании, разворачивают собственный Docker Registry — тот же сервер хранения, но с ограниченным доступом.

Практика: установка и первые команды

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

На macOS и Windows установка выполняется через приложение Docker Desktop. Скачайте его с официального сайта

На Linux Docker устанавливается из официальных репозиториев пакетов.

Для Debian и Ubuntu команда выглядит так:

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

Подробные инструкции для других дистрибутивов есть в документации Docker.

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

После установки убедитесь, что Docker работает корректно.  Для э��ого выполните команду:

docker version

Docker выведет информацию о клиенте и сервере. Пример вывода может выглядеть так:

Client:
 Version:           28.4.0
 API version:       1.51
 Go version:        go1.25.1
 Git commit:        d8eb465f86
 OS/Arch:           linux/amd64

Server:
 Engine:
  Version:          28.4.0
  API version:      1.51 (minimum version 1.24)
  Go version:       go1.25.1
 containerd:
  Version:          v2.1.4
 runc:
  Version:          1.3.1

Главное, чтобы в выводе присутствовали обе части: Client и Server, что означает, что dockerd запущен и Docker Engine работает.

Первый тест

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

docker run hello-world

Docker загрузит с Hub тестовый образ и запустит контейнер, который выведет сообщение Hello from Docker! — это значит, что система установлена правильно и демон отвечает на запросы.

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

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

Выполните команду:

docker run -d -p 8080:80 nginx

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

  1. docker run — создаёт контейнер из указанного образа (в данном случае nginx);

  2. если образа нет локально, Docker автоматически скачает его с Docker Hub;

  3. флаг -d запускает контейнер в фоновом режиме (detached);

  4. -p 8080:80 пробрасывает порт 80 внутри контейнера на порт 8080 вашей машины.

После запуска Docker создаст контейнер и выведет его идентификатор. Теперь можно открыть http://localhost:8080 и увидеть стандартную стартовую страницу Nginx.

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

docker ps

В списке будет строка с именем nginx и статусом Up, который означает, что сервер запущен.

Интерактивный запуск контейнера

Иногда нужно не просто запустить контейнер, а зайти внутрь него ― посмотреть файлы, проверить окружение, выполнить команды. Для этого используют флаг -it.

Например:

docker run -it ubuntu bash

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

  • -i — оставляет стандартный ввод открытым,

  • -t — выделяет псевдотерминал,

  • ubuntu — образ, который нужно запустить,

  • bash — команда, которая будет выполнена внутри контейнера.

После запуска вы попадёте в оболочку контейнера и сможете выполнять обычные Linux-команды.

Чтобы выйти, используйте:

exit

Важно: если нужно зайти в уже работающий контейнер, используется другая команда:

docker exec -it <id> bash

Как следить за контейнерами

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

Для этого используем уже знакомую команду:

docker ps

Она покажет список активных контейнеров: их идентификаторы, образы, статус и открытые порты. Если добавить флаг -a, отобразятся и завершённые контейнеры — это помогает найти старые тестовые сборки или понять, почему что-то остановилось.

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

docker inspect <id>

Она выводит полное описание: от сетевых параметров до переменных окружения и точной команды запуска.

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

docker logs <id>

Но если всё-таки нужно заглянуть внутрь, например, проверить файлы или конфигурацию, — можно открыть интерактивную консоль:

docker exec -it <id> /bin/bash

Если в образе нет Bash (например, в Alpine), вместо него используют:

docker exec -it <id> /bin/sh

Так вы попадёте в среду контейнера, где доступны обычные Linux-команды.

Управление контейнерами

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

Если контейнер нужно приостановить, используйте:

docker stop <id>

Docker отправит сигнал завершения процессу внутри контейнера и аккуратно его остановит.

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

docker start <id>

Когда контейнер больше не нужен, его можно удалить:

docker rm <id>

Важно: если попытаться удалить запущенный контейнер, Docker выдаст ошибку, чтобы не потерять данные.

Для принудительного удаления (остановка и удаление одной командой) используйте флаг -f:

docker rm -f <id>

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

docker images

А удалить ненужные с помощью команды:

docker rmi <image>

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

docker system prune

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

Создание своего образа: работа с Dockerfile

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

Пример простого Dockerfile:

FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

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

  • FROM — задаёт базовый образ, от которого наследуется ваш;

  • WORKDIR — создаёт рабочую директорию внутри контейнера;

  • COPY — копирует файлы проекта с компьютера в образ;

  • RUN — выполняет команды при сборке (например, установка библиотек);

  • CMD — указывает, что делать при запуске контейнера.

Важно: когда контейнер запускается, процессы внутри него по умолчанию работают с правами root

Для обучения или экспериментов этого вполне достаточно. Но в продакшене такое поведение создаёт лишние риски: если приложение внутри контейнера уязвимо, злоумышленник получает те же привилегии, что и root-процесс.

Чтобы снизить эти риски, в Dockerfile часто добавляют собственного пользователя и запускают приложение уже от его имени. Сделать это можно буквально в пару строк:

RUN useradd -m appuser
USER appuser

ENTRYPOINT: второй способ задать команду запуска

В Dockerfile есть две инструкции, которые определяют, что выполняется при старте контейнера — CMD и ENTRYPOINT.

Чем отличаются:

  • CMD — это команда по умолчанию. Если при запуске контейнера (docker run) вы передаёте свои аргументы, Docker заменяет CMD на то, что вы указали.

  • ENTRYPOINT — это фиксированная основа команды. Она всегда выполняется, а аргументы из docker run просто добавляются к ней.

Пример:

ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]

ENTRYPOINT задаёт программу, которую контейнер запускает всегда —
в нашем случае это python app.py. CMD добавляет аргументы по умолчанию. 

То есть Docker склеивает их и при обычном запуске выполнит:

python app.py --port 8000

Если при запуске указать свои параметры:

docker run myapp --port 9000

Docker возьмёт ENTRYPOINT и добавит ваши аргументы, заменив CMD:

python app.py --port 9000

ENTRYPOINT используют, когда контейнер всегда должен запускать одно и то же приложение — веб-сервер, скрипт, обработчик. Пользователь может менять только параметры.

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

Разобрались с основами, а теперь перейдём к практике.

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

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

Сначала в папке проекта должны лежать три файла:

myapp/
├─ app.py
├─ requirements.txt
└─ Dockerfile

В app.py напишем простейшее приложение на Python. Оно будет показывать строку "Hello from Docker!", когда вы откроете страницу в браузере:

from http.server import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello from Docker!")

if __name__ == "__main__":
    HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()

Файл requirements.txt нужен, чтобы перечислить зависимости проекта. Если их нет — можно оставить его пустым.

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

Теперь откройте терминал, перейдите в папку myapp и выполните:

docker build -t myapp .

Флаг -t задаёт имя образа, а точка в конце указывает путь к контексту сборки — папке, где лежит Dockerfile и файлы проекта. После выполнения команды Docker по шагам прочитает Dockerfile и соберёт новый образ.

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

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

docker run -d -p 8000:8000 myapp

Docker создаст контейнер и запустит внутри него ваш app.py.

Откройте http://localhost:8000, и вы увидите сообщение: "Hello from Docker!".

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

Чтобы их различать, Docker использует теги — о них поговорим дальше.

Зачем нужны теги

Тег — это просто метка версии. Если его не указать, Docker присвоит latest, но лучше явно обозначать версии:

docker build -t myapp:1.0 .

Так проще поддерживать несколько вариантов образа — например, стабильный (1.0) и тестовый (dev).

Передача и хранение образов 

После сборки Docker сохраняет образ локально — он доступен только на вашем компьютере. Если нужно поделиться им с коллегами или перенести на другой компьютер, проще всего воспользоваться Docker Hub.

1. Создайте репозиторий на Docker Hub

Зайдите в свой аккаунт на https://hub.docker.com и вручную создайте новый репозиторий — так же, как создают новый проект на GitHub.

Название репозитория должно совпадать с тем, что вы будете использовать в команде docker push.

Это важный шаг: Docker не создаёт репозитории автоматически.

2. Переименуйте образ

Перед загрузкой образ нужно оформить в правильном формате:

username/repository:tag

где:

  • username — ваш логин на Docker Hub,

  • repository — имя проекта,

  • tag — версия образа (например, 1.0).

Пример:

docker tag myapp:1.0 username/myapp:1.0

3. Войдите в Docker Hub

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

docker login

Docker попросит ввести логин и пароль от вашего аккаунта. После успешного входа появится сообщение вроде Login Succeeded.

4. Отправьте образ в репозиторий

Выполните команду:

docker push username/myapp:1.0

Docker начнёт загружать образ в ваш репозиторий на Docker Hub.

Когда загрузка закончится, образ появится в вашем профиле и станет доступен другим (если репозиторий публичный).

5. Загрузите образ на другом компьютере

Чтобы использовать тот же образ на другой машине, выполните:

docker pull username/myapp:1.0

Docker скачает только нужные слои — если часть уже есть локально, она не будет загружаться заново.

После этого образ можно запустить как обычно:

docker run -d -p 8000:8000 username/myapp:1.0

Docker Compose: что это и зачем нужно

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

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

Пример простого docker-compose.yml:  

services:
  web:
    build: .
    ports:
      - "8000:8000"

  redis:
    image: redis:alpine

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

  • services — список контейнеров, которые нужно запустить;

  • web — наш основной сервис, собирается из текущей директории (build: .) и открывает порт 8000;

  • redis — дополнительный контейнер, разворачивается из готового образа redis:alpine.

Как это работает

Сохраните файл и выполните команду:

docker compose up

Docker соберёт образы, создаст контейнеры, подключит сеть и запустит всё вместе.

Чтобы остановить проект, выполните команду: 

docker compose down

Что изучать дальше

Следующие шаги зависят от того, что вы хотите делать дальше:

  • Для разработки — попробуйте написать собственный Dockerfile для своего проекта и оптимизировать его размер. Изучите многоэтапную сборку (multi-stage build) и работу с кэшем.

  • Для деплоя — посмотрите, как запускать контейнеры на сервере: настройка автозапуска через systemd, передача переменных окружения, управление логами.

  • Для продакшена — изучите оркестраторы: Docker Swarm или Kubernetes. Они позволяют управлять десятками контейнеров и масштабировать сервисы.

  • Для безопасности — разберитесь с rootless-режимом, настройкой прав доступа и сканированием образов на уязвимости (например, с помощью docker scan).

  • Для автоматизации — посмотрите, как Docker используется в CI/CD-сценариях: тестирование, сборка и публикация образов через GitHub Actions или GitLab CI.

Ну, вот и всё. Теперь у вас уже есть рабочая база Docker: вы понимаете, как устроены образы и контейнеры, умеете собирать собственные образы и запускать сервисы через Compose. Этого достаточно, чтобы уверенно продолжать и постепенно подключать более сложные вещи.


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

Или можно стать востребованным сотрудником и открыть открыть бóльшие перспективы в карьере с профессиональным обучением: