«Build, ship, run — собери, доставь, запусти» — слоган Docker
Главная боль разработки: «у меня на машине работает, а на сервере — нет». Причина — разное окружение: версии языка, библиотек, системных пакетов. Docker решает это, упаковывая приложение вместе со всем его окружением в изолированную «коробку» — контейнер, который одинаково запускается где угодно.
Docker — открытая платформа для сборки, доставки и запуска приложений в контейнерах: изолированных процессах, которые несут с собой все свои зависимости. Это виртуализация на уровне операционной системы — без отдельной гостевой ОС под каждое приложение.
- Одинаковое окружение везде — конец «у меня работает». Dev, stage, prod собираются из одного образа.
- Лёгкий старт — не нужен README на полстраницы «как поднять проект»:
docker runи готово. - Изоляция — зависимости, версии Python/Node, ключи и
env-переменные не текут в систему и не конфликтуют между проектами. - Воспроизводимость и стандартизация — окружение описано кодом (Dockerfile), а не «настроено руками».
И то, и другое изолирует приложение, но по-разному. Виртуальная машина тащит целую гостевую ОС поверх гипервизора; контейнер делит ядро хоста и несёт только само приложение и его зависимости.
| Виртуальная машина | Контейнер (Docker) | |
|---|---|---|
| Изолирует через | гипервизор + гостевая ОС | движок Docker, общее ядро хоста |
| Вес | гигабайты | мегабайты |
| Старт | минуты | секунды |
| Накладные расходы | высокие | низкие |
| Изоляция | полная (своё ядро) | на уровне процессов и ФС |
Контейнеры легче и быстрее ВМ, поэтому на одной машине их можно держать десятки — отсюда удобство для микросервисов и CI/CD.
Docker устроен как клиент — демон — реестр:
┌──────────┐ команды ┌──────────────┐ pull/push ┌─────────────┐
│ docker │ ───────────▶ │ dockerd │ ◀───────────▶ │ Registry │
│ (клиент) │ REST API │ (демон) │ │ (Docker Hub)│
└──────────┘ └──────┬───────┘ └─────────────┘
│ управляет
образы · контейнеры · тома · сети
- Демон
dockerd— фоновый процесс, который слушает Docker API и управляет объектами: образами, контейнерами, сетями, томами. - Клиент
docker— командная строка, через которую ты отдаёшь команды демону. - Реестр (registry) — хранилище образов. Публичный по умолчанию — Docker Hub.
Образ — read-only шаблон для создания контейнера: файловая система + метаданные (что запускать). Собирается из Dockerfile и состоит из слоёв — как коммиты в GIT, каждый слой наслаивается на предыдущий и кешируется.
Контейнер — запущенный экземпляр образа; процесс на хосте со своей файловой системой, сетью и деревом процессов, изолированный от хоста. Из одного образа можно поднять сколько угодно контейнеров.
Реестр — где образы хранятся и откуда раздаются:
docker push— отправить образ в реестр;docker pull— забрать образ из реестра.
Dockerfile ──build──▶ Образ ──run──▶ Контейнер
▲
pull │ push
▼
Registry (Docker Hub)
Dockerfile — текстовый файл с инструкциями сборки образа. Выполняется сверху вниз, каждая инструкция — отдельный слой.
# базовый образ-основа
FROM python:3.12-slim
# рабочая директория внутри образа
WORKDIR /app
# сначала только зависимости — чтобы слой кешировался, пока они не меняются
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# затем сам код
COPY . .
# объявить порт (документирующая метка)
EXPOSE 8000
# команда запуска контейнера
CMD ["python", "main.py"]| Инструкция | Что делает |
|---|---|
FROM |
базовый образ-основа |
WORKDIR |
рабочая директория для последующих команд |
COPY / ADD |
копирование файлов с хоста в образ (ADD умеет ещё распаковку и URL) |
RUN |
выполнить команду при сборке (установка пакетов) |
CMD |
команда по умолчанию при запуске (легко переопределить) |
ENTRYPOINT |
основная команда запуска (не переопределяется аргументами run) |
ENV |
переменные окружения внутри контейнера |
ARG |
переменные только на время сборки (--build-arg) |
EXPOSE |
пометить порт для прослушивания |
VOLUME |
точка монтирования тома |
USER |
от чьего имени выполняются следующие инструкции и сам процесс |
HEALTHCHECK |
чем проверять, что приложение внутри живо |
Порядок имеет значение для кеша. Копируй
requirements.txtи ставь зависимости до копирования кода — тогда при правке кода слой с зависимостями берётся из кеша, и пересборка идёт за секунды. Лишнее в образ не тащи — исключай через.dockerignore.
Исходным образом служит ядро ОС: Ubuntu, Debian, Alpine и др. Alpine Linux — популярная основа за лёгкость и безопасность: базовый размер всего ~5 МБ (без ядра). Поверх собирают свой образ с нужными зависимостями — например, backend на Python или готовый веб-сервер (Nginx / Apache).
Проблема: чтобы собрать зависимости, нужны компилятор и заголовочные файлы, а чтобы запустить приложение — нет. В однослойной сборке весь этот инструментарий остаётся в финальном образе и раздувает его в разы.
Решение — несколько FROM в одном Dockerfile. Каждый начинает новую стадию, а в итоговый образ попадает только последняя; из предыдущих забирают готовые артефакты через COPY --from:
# ── стадия 1: собираем зависимости, тут можно быть грязным ──
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# ── стадия 2: то, что реально поедет в прод ────────────────
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /wheels /wheels # берём из стадии builder только колёса
RUN pip install --no-cache-dir --no-index --find-links=/wheels /wheels/* \
&& rm -rf /wheels
COPY . .
CMD ["python", "main.py"]Компилятор, кеш pip и заголовки остаются в builder и до прода не доезжают. Для Go и C выигрыш ещё нагляднее: финальный образ может быть вообще scratch — один бинарник без операционной системы.
Побочная выгода, о которой спрашивают отдельно: в прод-образ не попадают секреты сборки — токены приватного репозитория, ключи. Если они использовались в
builder, в финальном образе их слоёв нет, а значит, их не вытащить черезdocker history.
По умолчанию процесс в контейнере идёт от root. Изоляция контейнера не абсолютна: пробой в приложении плюс неудачно смонтированный том или неверная настройка — и права хоста оказываются шире, чем предполагалось.
RUN useradd --create-home --shell /bin/bash app
COPY --chown=app:app . . # файлы сразу с нужным владельцем
USER app # всё, что ниже, и сам процесс — от app
CMD ["python", "main.py"]USER действует на всё, что идёт после него, поэтому apt-get install ставят до него. И порты ниже 1024 непривилегированному пользователю недоступны — приложение слушает 8000, а наружу отдаётся -p 80:8000.
Запущенный контейнер и работающее приложение — разное: процесс может висеть, потеряв соединение с базой, а Docker будет считать его исправным, потому что он не завершился. HEALTHCHECK даёт базе критерий:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1Состояние видно в docker ps как starting → healthy / unhealthy. --start-period — фора на запуск: неудачи в это окно не считаются.
⚠️ Docker сам по себе не перезапускаетunhealthy-контейнер — он только меняет статус.restart: alwaysреагирует на завершение процесса, а не на здоровье. Перезапуск по здоровью делают оркестраторы (Kubernetes с его liveness probe, Swarm). Главная практическая польза — в Compose:depends_onсcondition: service_healthyзаставляет приложение ждать, пока база действительно начнёт принимать запросы, а не просто пока её контейнер стартует.
Контейнер эфемерен: удалил — данные внутри исчезли. Чтобы данные жили дольше контейнера (базы, загрузки, логи), их выносят в том.
docker volume create mydata
docker run -d --name app --mount source=mydata,target=/app/data nginx:latestКоманды для томов:
docker volume create— создать том;docker volume ls— список томов;docker volume inspect— подробности тома;docker volume rm— удалить том;docker volume prune— удалить все неиспользуемые тома.
Тома можно шарить между контейнерами, бэкапить и подключать внешние хранилища.
Контейнеры объединяются в сети; поведение задаёт драйвер:
| Драйвер | Когда |
|---|---|
| bridge | по умолчанию: изолированная сеть на одном хосте |
| host | убрать изоляцию, общая сеть с хостом |
| overlay | связать несколько демонов Docker (Swarm), общение между узлами |
| macvlan | контейнеру свой MAC — выглядит как физическое устройство в сети |
| none | сеть отключена |
docker network create --driver bridge test-net
docker run -dit --name a1 --network test-net alpine
docker run -it --name a2 --network test-net alpine
# внутри a2: ping a1 → контейнеры в одной сети видят друг друга по имениКоманды для сетей:
docker network create— создать сеть;docker network ls— список сетей;docker network inspect— подробности;docker network connect / disconnect— подключить / отключить контейнер;docker network rm— удалить;docker network prune— удалить неиспользуемые.
docker build -t my-app:1.0 . # собрать образ из Dockerfile в текущей папке
docker images # список образов
docker pull nginx # скачать образ из реестра
docker push my-app:1.0 # отправить образ в реестр (после docker login)
docker tag my-app:1.0 user/my-app:1.0 # дать образу новое имя/тег
docker inspect my-app:1.0 # подробные метаданные (JSON)
docker history my-app:1.0 # из каких слоёв собран
docker save -o app.tar my-app:1.0 # выгрузить образ в архив
docker load -i app.tar # загрузить образ из архива
docker rmi my-app:1.0 # удалить образdocker run -d -p 8000:8000 --name app my-app:1.0 # создать и запустить (фон, проброс порта)
docker ps # запущенные контейнеры (-a — все, включая остановленные)
docker start / stop / restart app # запустить / остановить / перезапустить
docker kill app # принудительно убить
docker pause / unpause app # заморозить / разморозить процессы
docker exec -it app bash # войти внутрь работающего контейнера
docker logs -f app # смотреть логи (поток)
docker cp app:/path ./local # копировать файлы контейнер ↔ хост
docker inspect app # метаданные контейнера (JSON)
docker stats # потребление ресурсов в реальном времени
docker top app # процессы внутри контейнера
docker rename app web # переименовать
docker rm app # удалить контейнер (-f — остановить и удалить)Ключевые флаги docker run:
-d— запуск в фоне (detached);-p хост:контейнер— проброс порта;--name имя— имя контейнера;-v том:путь— подключить том или папку хоста;-e VAR=val— переменная окружения (--env-file .env— из файла);--network имя— подключить к сети;--restart unless-stopped— политика перезапуска;--rm— удалить контейнер после остановки.
docker login / docker logout # вход / выход в реестр (Docker Hub или свой)
docker search nginx # искать образы в Docker Hub
docker info # информация о системе Docker
docker version # версии клиента и демона
docker system df # сколько занято места
docker system prune # убрать мусор (-a — агрессивно, --volumes — и тома)Один контейнер запускают через docker run. Но реальное приложение — это несколько сервисов: веб-сервер + база + кеш. Поднимать и связывать их руками неудобно. Docker Compose — инструмент для описания и запуска многоконтейнерных приложений: вся конфигурация в одном YAML-файле (compose.yaml / docker-compose.yml), запуск — одной командой.
Оркестрация — автоматизация жизненного цикла контейнеров: развёртывание, масштабирование, балансировка нагрузки, доступность и сети между контейнерами. Compose покрывает её для одного хоста (для кластера — Kubernetes или Docker Swarm).
Файл состоит из трёх секций верхнего уровня:
services— сами контейнеры: какой образ или сборка, порты, перезапуск, сети, тома, зависимости;volumes— тома; Compose создаёт их сам по описанию;networks— сети между сервисами.
services:
web:
build: . # собрать из локального Dockerfile
ports: ["8000:8000"]
env_file: .env
depends_on: [db, redis] # стартовать после базы и кеша
db:
image: postgres:16
volumes: ["pgdata:/var/lib/postgresql/data"]
restart: unless-stopped # перезапускать, если упал
redis:
image: redis:7
volumes:
pgdata:Ключи сервиса: image или build, ports, volumes, depends_on, env_file / environment, restart, networks.
Ловушка
depends_on. По умолчанию он ждёт только запуска контейнера, а не готовности сервиса принимать запросы. База может быть ещё в процессе инициализации, а приложение уже стартовало и упало с ошибкой подключения. Чтобы дождаться именно готовности, нуженhealthcheckу зависимости иcondition: service_healthy:depends_on: db: condition: service_healthy
Команды Compose:
docker compose up -d # собрать (если надо) и поднять всё в фоне
docker compose up --build # пересобрать образы и поднять
docker compose down # остановить и удалить контейнеры и сети (-v — и тома)
docker compose start / stop / restart # управлять уже созданными
docker compose build # собрать / пересобрать образы
docker compose ps # статус сервисов
docker compose logs -f # логи всех сервисов (поток)
docker compose exec web bash # войти в сервис
docker compose run web pytest # разовый запуск команды в новом контейнере сервиса
docker compose pull # скачать образы сервисов
docker compose config # проверить и показать итоговый конфиг
docker compose top # процессы в сервисахКогда одних и тех же сервисов нужно несколько вариантов — прод без открытых портов, разработка с портами и веб-интерфейсами — файлы не копируют, а переиспользуют через include и extends. Как именно: Docker Compose — несколько окружений.
Docker (docker run) |
Docker Compose | |
|---|---|---|
| Масштаб | один контейнер | много связанных сервисов |
| Конфигурация | флаги в командной строке | один YAML-файл |
| Запуск | команда на каждый контейнер | docker compose up для всех |
| Когда | разовый запуск, простой образ | приложение из веб + БД + кеш и т.п. |
Соберём типичный backend на Python (FastAPI) с базой PostgreSQL и кешем Программирование/BackEnd/Python/Библиотеки/Сторонние/Redis — от пустой папки до рабочего http://localhost:8000. Делаем по порядку.
Шаг 1. Структура проекта.
myapp/
├── app/
│ └── main.py # код приложения
├── requirements.txt # зависимости Python
├── Dockerfile # как собрать образ приложения
├── .dockerignore # что НЕ копировать в образ
├── docker-compose.yml # как поднять все сервисы вместе
└── .env # секреты и настройки (НЕ в git!)
Шаг 2. requirements.txt — что ставим внутрь образа:
fastapi
uvicorn[standard]
psycopg2-binary
redis
Шаг 3. app/main.py — минимальное приложение:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"status": "ok"}Шаг 4. Dockerfile — рецепт образа (читается сверху вниз; цифры = порядок выполнения):
FROM python:3.12-slim # 1. базовый образ с Python
WORKDIR /app # 2. рабочая папка внутри контейнера
COPY requirements.txt . # 3. сначала зависимости — ради кеша
RUN pip install --no-cache-dir -r requirements.txt # 4. установка
COPY . . # 5. затем код приложения
EXPOSE 8000 # 6. порт, который слушаем
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] # 7. запускПочему
--host 0.0.0.0, а не127.0.0.1. У контейнера свой сетевой стек, и-pпробрасывает порт на его интерфейс. Приложение, слушающее внутри контейнера петлю127.0.0.1, останется недоступным даже с правильным-p— подробно про интерфейсы и привязку.
Шаг 5. .dockerignore — чтобы не тащить лишнее в образ (легче и безопаснее):
.git
__pycache__/
*.pyc
.env
venv/
Шаг 6. docker-compose.yml — поднимаем приложение + базу + кеш одной командой:
services:
web:
build: . # собрать из Dockerfile рядом
ports: ["8000:8000"] # наружу на localhost:8000
env_file: .env # переменные из .env
depends_on: [db, redis] # стартовать после базы и кеша
restart: unless-stopped
db:
image: postgres:16 # готовый образ Postgres
environment:
POSTGRES_PASSWORD: secret
volumes: ["pgdata:/var/lib/postgresql/data"] # данные переживут пересоздание
restart: unless-stopped
redis:
image: redis:7
volumes:
pgdata: # именованный том для базыШаг 7. .env — настройки и секреты отдельно от кода:
DATABASE_URL=postgresql://postgres:secret@db:5432/postgres
REDIS_URL=redis://redis:6379/0
Внутри Compose сервисы видят друг друга по имени —
db,redis, а неlocalhost. Это и есть встроенная сеть Compose.
Шаг 8. Запуск по порядку:
docker compose up -d --build # 1. собрать образ web и поднять все три сервиса
docker compose ps # 2. проверить, что web, db, redis подняты
docker compose logs -f web # 3. смотреть логи приложения
# открыть http://localhost:8000 → {"status": "ok"}
docker compose down # 4. остановить и удалить (том pgdata с данными останется)Как это читать. Docker собирает образ web по Dockerfile, скачивает готовые postgres и redis, создаёт между ними сеть и том pgdata под базу. depends_on задаёт порядок старта, web ходит в базу и кеш по их именам. Изменил код — docker compose up -d --build пересоберёт только web.
- Docker Compose — несколько окружений — прод и разработка из одной базы сервисов:
profiles,include,extendsи правила слияния; - CI CD — образы собираются и публикуются в пайплайне, оттуда выкатываются на сервер;
- Django, Программирование/BackEnd/Python/Библиотеки/Сторонние/Redis, Программирование/BackEnd/Python/Библиотеки/Сторонние/Celery — типичные сервисы в
docker-compose.yml; - GIT —
Dockerfileиdocker-compose.ymlхранятся в репозитории рядом с кодом; - Python — образ
pythonкак база для backend-контейнера.