Docker — это платформа контейнеризации: она упаковывает приложение вместе с зависимостями в образ и запускает его в изолированном процессе-контейнере, который одинаково ведет себя на ноутбуке разработчика, в CI и на сервере. Ниже — словарь терминов, первые команды, свой образ через Dockerfile, данные в volume, несколько сервисов через docker compose и минимум безопасности.
Содержание
- Пять терминов, которые путают
- Как это работает и чем контейнер отличается от виртуальной машины
- Первые команды
- Свой образ: минимальный полный пример
- Данные: volume и bind mount
- Несколько сервисов: docker compose
- Безопасность: минимум, который нужен сразу
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Пять терминов, которые путают
| Термин | Что это | Аналогия |
|---|---|---|
| Образ (image) | Неизменяемый шаблон: файловая система из слоев + метаданные (команда запуска, порты) | Чертеж |
| Контейнер (container) | Запущенный экземпляр образа со своим тонким записываемым слоем | Дом по чертежу |
| Dockerfile | Текстовый рецепт сборки образа | Техзадание на чертеж |
| Volume | Хранилище данных вне контейнера, переживает его удаление | Внешний диск |
| Registry | Хранилище образов (Docker Hub, приватный реестр) | Библиотека чертежей |
Цепочка на одном примере: Dockerfile -> docker build -> образ hello-app:1.0 -> docker run -> контейнер. Из одного образа можно запустить сколько угодно контейнеров. Удаление контейнера стирает его записываемый слой, но не образ и не volume.
Как это работает и чем контейнер отличается от виртуальной машины
Контейнер — это обычный процесс хоста, изолированный механизмами ядра Linux: namespaces ограничивают, что процесс видит (процессы, сеть, файловую систему), cgroups — сколько ресурсов он может взять. Команду docker обрабатывает демон Docker Engine, который запускает контейнеры через containerd и runc.
| Контейнер | Виртуальная машина | |
|---|---|---|
| Ядро ОС | Общее с хостом | Свое у каждой VM |
| Старт | Обычно секунды и быстрее | Загрузка гостевой ОС |
| Размер | Десятки-сотни МБ | Обычно гигабайты |
| Изоляция | Слабее: общее ядро | Сильнее: граница гипервизора |
Упрощение и его граница: «контейнер легче VM» верно для типичных случаев, но из-за общего ядра Linux-контейнер требует ядра Linux. На macOS и Windows Docker Desktop поднимает для этого легкую Linux-VM, поэтому контейнеры там все равно работают внутри виртуальной машины.
Первые команды
Установка: на Linux — Docker Engine из официального репозитория Docker для своего дистрибутива, на macOS и Windows — Docker Desktop. Проверка:
docker version
docker run --rm hello-world
Если все в порядке, вторая команда скачает маленький образ и напечатает Hello from Docker!. Флаг --rm удалит контейнер после выхода.
Запустим веб-сервер в фоне с пробросом порта:
docker run -d --name web -p 8080:80 nginx:1.30-alpine
docker ps
docker logs web
docker stop web && docker rm web
-d — фоновый режим, -p 8080:80 — порт 8080 хоста ведет на порт 80 контейнера. Пока контейнер работает, http://localhost:8080 в браузере покажет приветственную страницу nginx. Тег 1.30-alpine (текущая стабильная ветка nginx) зафиксирован сознательно: latest через месяц может означать другую версию. Ветку при этом нужно время от времени обновлять: старые ветки, например 1.28, перестают получать исправления.
Свой образ: минимальный полный пример
Три файла в пустой папке. Приложение на стандартной библиотеке Python, app.py:
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = f"Hello from container, uid={os.getuid()}\n".encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
Dockerfile:
FROM python:3.14-slim
RUN useradd --create-home --uid 10001 appuser
WORKDIR /app
COPY app.py .
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]
.dockerignore — чтобы в контекст сборки не попали лишние и секретные файлы:
.git
.env
db_password.txt
__pycache__/
Сборка и запуск:
docker build -t hello-app:1.0 .
docker run -d --name hello -p 8080:8000 hello-app:1.0
curl http://localhost:8080
Ожидаемый ответ:
Hello from container, uid=10001
Разбор. FROM задает базовый образ с конкретной версией Python. useradd и USER appuser запускают процесс не от root — uid в ответе это подтверждает. CMD в exec-форме (JSON-массив) делает Python главным процессом контейнера, и docker stop корректно доставляет ему сигнал. EXPOSE только документирует порт, публикует его флаг -p.
Слои и кеш: каждая инструкция дает слой, и при пересборке Docker переиспользует слои до первой изменившейся инструкции. Поэтому в реальном проекте сначала копируют requirements.txt и ставят зависимости, а код копируют последним — правка кода не заставит заново качать пакеты.
Данные: volume и bind mount
Все, что контейнер записал в свой слой, исчезнет вместе с docker rm. Для данных есть два основных способа:
| Named volume | Bind mount | |
|---|---|---|
| Где лежит | В области Docker, управляется им | Любая папка хоста |
| Синтаксис | -v pgdata:/var/lib/postgresql |
-v "$(pwd)/src:/app" |
| Когда брать | Данные БД, состояние сервисов | Код при разработке, конфиги |
Несколько сервисов: docker compose
Когда контейнеров больше одного, их описывают в compose.yaml. Актуальная команда — docker compose (плагин Compose, линейка v2 и новее; docker compose version сейчас показывает 5.x). Старый отдельный docker-compose (v1) больше не поддерживается, а поле version: в начале файла устарело: Compose v2 его игнорирует и выводит предупреждение.
services:
web:
build: .
ports:
- "8080:8000"
depends_on:
- db
db:
image: postgres:18-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- pgdata:/var/lib/postgresql
secrets:
db_password:
file: ./db_password.txt
volumes:
pgdata:
Перед запуском создайте файл с паролем рядом с compose.yaml: без него up остановится с ошибкой bind source path does not exist. Файл не должен попасть в git — добавьте его в .gitignore.
printf '%s' 'замените-на-длинный-пароль' > db_password.txt
chmod 600 db_password.txt
docker compose up -d --build
docker compose ps
docker compose logs db
docker compose down
up -d --build соберет web, скачает postgres:18-alpine и запустит оба сервиса в общей сети, где web видит базу по имени db. Пароль лежит в файле, который монтируется в /run/secrets/, а не в тексте compose.yaml. down удалит контейнеры и сеть, но volume pgdata сохранится; удалить его вместе с данными можно только явно, через down -v.
Граница: depends_on в такой форме ждет запуска контейнера db, а не готовности PostgreSQL принимать соединения. Для этого добавляют healthcheck и condition: service_healthy.
Безопасность: минимум, который нужен сразу
Типичная ошибка — секрет в образе:
ENV DB_PASSWORD=supersecret
Результат: пароль виден любому, у кого есть образ, через docker inspect hello-app:1.0 и docker history. С файлами то же самое: если секрет скопирован через COPY, а следующей инструкцией удален RUN rm, он остается в предыдущем слое образа.
Исправление: секрет не попадает в сборку, а передается при запуске — файлом-секретом, как в compose-примере выше, или через секрет-хранилище платформы. Если секрет нужен именно при сборке, используют BuildKit: RUN --mount=type=secret,id=token ... с docker build --secret id=token,src=token.txt — так он не сохраняется в слоях.
Остальные базовые правила:
- Не root в контейнере. Инструкция
USERс непривилегированным uid, как в примере. - Pin версий. Конкретный тег (
python:3.14-slim), а для воспроизводимости в production — digest@sha256:.... Обновление версии — осознанный коммит, а не сюрприз. - Минимальная база.
slim/alpine/distroless-образы — меньше пакетов, меньше уязвимостей. Образы стоит проверять сканером уязвимостей в CI. - Сокет Docker — это root. Членство в группе
dockerи проброс/var/run/docker.sockв контейнер дают фактически полный доступ к хосту.
Это базовый уровень, а не «полностью защищенная» конфигурация: в production еще нужны ограничение ресурсов, read-only файловая система где возможно, сетевые политики и регулярное обновление базовых образов.
Если не получилось
| Симптом | Вероятная причина | Что делать |
|---|---|---|
permission denied ... docker.sock |
Пользователь без прав на демон | Linux: sudo или группа docker (помня, что это root-доступ) |
Cannot connect to the Docker daemon |
Демон не запущен | Linux: sudo systemctl start docker; Desktop: запустить приложение |
port is already allocated |
Порт хоста занят | Сменить левую часть: -p 8081:8000 |
Контейнер сразу в статусе Exited |
Главный процесс завершился или упал | docker logs <имя> покажет ошибку |
curl не отвечает, контейнер жив |
Приложение слушает 127.0.0.1 внутри контейнера |
Слушать 0.0.0.0, как в app.py |
Выводы
- Образ — неизменяемый шаблон, контейнер — запущенный процесс из него; данные, которые должны пережить
docker rm, хранят в volume. - Контейнер делит ядро с хостом, поэтому он легче VM, но изолирован слабее; на macOS и Windows работает внутри Linux-VM.
- Свой образ описывают в Dockerfile, порядок инструкций влияет на кеш сборки.
- Несколько сервисов запускают через
docker compose(v2 и новее), полеversionвcompose.yamlне нужно. - Базовая безопасность: не root, секреты не в образе, зафиксированные версии образов.
Где применяется / связь с практикой
Освойте тему на практике
Docker — основа современной поставки приложений: из того же образа, что собран в CI, запускают тесты, стенды и production, а оркестраторы вроде Kubernetes управляют уже контейнерами. Сборку образов, пайплайны CI/CD, инфраструктуру как код и мониторинг системно разбирают на курсе «DevOps практики и инструменты». Попробовать формат и задать вопросы практикующим инженерам можно на бесплатных открытых уроках.
FAQ
Docker бесплатный?
Docker Engine и CLI — открытое ПО. Docker Desktop бесплатен для личного использования, обучения и небольших компаний, а для крупных организаций и госструктур требует платной подписки; актуальные условия — в лицензии на сайте Docker.
Можно ли запустить Windows-приложение в Docker на Linux?
Нет: Windows-контейнеры требуют ядра Windows и запускаются на Windows-хосте в соответствующем режиме.
Чем Docker отличается от Podman?
Podman — совместимый по командам инструмент без постоянного демона, изначально ориентированный на запуск без root. Образы формата OCI работают в обоих.



