Learning Platform
Глоссарий Troubleshooting
Урок 08.05 · 22 мин
Начальный
dockerbuild-contextdockerignorebuild-performance

Build context и .dockerignore: что попадает в образ

Build context это часто упускаемая, но влиятельная концепция Docker. Когда ты пишешь docker build ., эта точка означает «всё содержимое директории отправь docker daemon’у». На локальной разработке это редко заметно — секундная задержка. Но как только проект растёт (.git/ 200 MB, data/ 1 GB, node_modules/), build context раздувается, build context transfer тормозит, и cache invalidation начинает срабатывать на каждое изменение в любом мусорном файле.

В этом уроке: что физически содержит build context, синтаксис .dockerignore, удалённые builds.


Что такое build context

docker build . запускает следующую последовательность:

  1. Docker CLI смотрит на аргумент . — это путь к директории, который и есть build context.
  2. CLI отправляет всё содержимое этой директории Docker daemon’у (через UNIX-сокет или TCP, если daemon удалённый).
  3. Daemon принимает context во временную директорию.
  4. Daemon выполняет инструкции Dockerfile, имея доступ к этому контексту через COPY/ADD.
  5. После build временная директория удаляется.

Ключевая мысль: COPY/ADD работают ТОЛЬКО с файлами внутри build context. Это не filesystem текущей машины, это та папка, которую ты передал в docker build.

# Build context = текущая директория (./)
docker build -t myapp:v1 .

# Build context = /home/user/project (можно указать любую директорию)
docker build -t myapp:v1 /home/user/project

# Dockerfile из другого места, но context = ./
docker build -t myapp:v1 -f docker/Dockerfile.prod .

# Context из git
docker build https://github.com/user/repo.git

# Context из URL с tarball
docker build https://example.com/context.tar.gz

В Dockerfile можно делать только COPY ./src /app, никак не COPY /etc/hosts /app/etc/hosts это путь на хосте, а не в context.

WARNING

Build context это копия! Если ты в Dockerfile делаешь COPY large-file.bin /app, и large-file.bin лежит в context’е, он будет передан daemon’у (network трафик, если daemon удалённый), временно сохранён, копирован в layer. На больших файлах это заметно. Решение — использовать .dockerignore или вынести большие файлы за пределы context’а.


Что показывается при build

В выводе docker build есть строка:

 => => transferring context: 1.45kB

Это размер передаваемого контекста. На чистом мини-проекте — килобайты. На раздутом проекте без .dockerignore может быть сотни мегабайт:

 => => transferring context: 234.5MB

Если ты видишь >10MB на маленьком проекте — это сигнал, что .dockerignore не настроен или некорректен.


.dockerignore: синтаксис

.dockerignore это файл в корне build context’а (рядом с Dockerfile или указанный явно), который говорит Docker какие пути не отправлять daemon’у.

Синтаксис похож на .gitignore:

# Comments start with #

# Конкретный файл
secrets.env

# Wildcard для одного компонента имени
*.log
*.tmp

# Wildcard для нескольких компонентов пути (рекурсивно)
**/__pycache__
**/*.pyc

# Директория (с trailing slash или без -- работает одинаково)
.git
.venv/

# Negation -- включить обратно
# Например: игнорировать все .pem, кроме public.pem
*.pem
!public.pem

# Pattern с подкаталогами
docs/build/

# Якорь к корню build context
/data
# (без leading / pattern применяется в любой глубине: "data" игнорирует /data, /sub/data, /sub/sub/data)

Особые case’ы:

# Игнорировать всё
*

# Кроме конкретного
!Dockerfile
!src/
!requirements.txt

Это паттерн «whitelist» — игнорируется всё, кроме явно перечисленного. Полезно для минимизации context’а в монорепо.


Типичный .dockerignore для Python-DE

# .dockerignore

# Version control
.git
.gitignore
.gitattributes
.gitmodules

# Python build artifacts
__pycache__
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/

# Virtual environments
.venv
venv/
ENV/
env/

# Testing
.pytest_cache
.mypy_cache
.ruff_cache
.tox
.coverage
htmlcov/
.hypothesis

# IDE / OS
.vscode
.idea
*.swp
*.swo
.DS_Store
Thumbs.db

# Documentation
docs/_build
*.md

# Local data
data/
output/
logs/
*.parquet
*.csv
*.json.gz
*.db
*.sqlite

# Secrets / config
.env
.env.*
*.pem
*.key
*.crt
secrets/

# Docker-specific
Dockerfile.*
docker-compose*.yml
.dockerignore

# CI / scripts
.github/
.gitlab-ci.yml
ci/
scripts/

# Misc
README.md
LICENSE
CHANGELOG.md
TODO.md
.editorconfig

После добавления этого файла размер контекста уменьшается с сотен MB до десятков KB.


Сравнение: с .dockerignore и без

Возьмём средний DE-проект с такой структурой:

project/
├── .git/                 (180 MB)
├── .venv/                (380 MB)
├── data/                 (2.5 GB)
├── tests/fixtures/       (450 MB)
├── src/                  (8 MB)
├── requirements.txt      (1 KB)
├── Dockerfile
└── .dockerignore

Без .dockerignore:

$ docker build -t app:v1 .
 => => transferring context: 3.4GB    # !
[+] Building 124.5s ...

3.4 GB трафика на daemon. Если daemon локальный — это I/O. Если удалённый (например, в CI с remote builder) — это network. В обоих случаях это секунды-минуты на каждый build.

С .dockerignore выше:

$ docker build -t app:v1 .
 => => transferring context: 1.2MB    # только нужное
[+] Building 18.5s ...

В 2800 раз меньше context’а. Build быстрее, нагрузка на daemon мизерная, cache invalidation чище (никакие изменения в data/ не инвалидируют слои).


.dockerignore влияет на cache invalidation

Это самый важный эффект .dockerignore, который часто упускают. Когда ты делаешь COPY . /app, Docker вычисляет хэш всего, что попадает в context (минус .dockerignore). Если хоть один байт изменился — cache инвалидируется, RUN’ы после COPY пересобираются.

Без .dockerignore:

# Я отредактировал README.md
$ docker build -t app:v1 .
[+] Building 95s
 => [3/5] COPY . /app    # cache MISS (потому что README.md тоже в context)
 => [4/5] RUN pip install -r requirements.txt    # cache MISS (зависит от COPY)

С .dockerignore (где README.md игнорируется):

# Я отредактировал README.md
$ docker build -t app:v1 .
[+] Building 1.2s
 => CACHED [3/5] COPY . /app    # README.md проигнорирован, остальное не изменилось
 => CACHED [4/5] RUN pip install

При работе с DE-проектами, где есть большие data/ или tests/fixtures/, корректный .dockerignore означает разницу между «build 30 секунд» и «build 5 минут на каждом коммите».

Влияние .dockerignore на сборку
3.4 GB contextБез .dockerignore: весь проект (включая .git, data/, .venv) идёт в context.
transfer 30s+
Cache miss oftenЛюбое изменение в .git/, data/, README.md инвалидирует cache COPY.
1.2 MB contextС .dockerignore: только src/ + requirements.txt + Dockerfile.
transfer 0.1s
Cache reliableCache валиден до изменений в src/. README.md, тесты, данные не влияют.

Безопасность: secrets не должны попадать в context

Права доступа к файлам и почему секреты опасны в образах

.env, *.pem, *.key файлы — если они в репо (даже если в .gitignore), они могут попасть в context при build, и затем в образ через COPY . /app. Это утечка secrets — любой, у кого есть pull-доступ к образу, увидит их через docker history или docker run image cat /app/.env.

.dockerignore это критическая линия защиты:

# Secrets -- НИКОГДА не должны попадать в context
.env
.env.*
*.pem
*.key
*.crt
*.p12
secrets/

В CI это надо завайтлистить через линтер (Hadolint правила, custom CI-check). Если кто-то случайно сделает COPY . /app без .dockerignore, проверки выловят.


Remote builds: builder в другом месте

В современных CI builders могут быть удалёнными (build-as-a-service: Depot, Buildkite Cloud, GitHub Actions buildx remote). Тогда build context передаётся по сети.

# Создаём remote builder (через buildx)
docker buildx create --name remote --driver docker-container --use

# Build -- context отправляется builder'у по сети
docker buildx build -t myapp:v1 .

В этом сценарии большой context = долгий network transfer. .dockerignore критичен.

Также есть remote contexts — построение прямо из git:

# Context из git, daemon склонирует репо сам
docker build https://github.com/user/repo.git#main:src
# main = branch, src = subdirectory

# Через URL с tar
docker build https://example.com/context.tar.gz

При remote git context .dockerignore применяется так же, как локально (но .git/ не клонируется в context).


Попробуй сам

Сравни context size с и без .dockerignore:

mkdir context-test && cd context-test

# Создадим "проект" с мусором
mkdir -p .git src tests/fixtures data __pycache__
dd if=/dev/zero of=data/big.csv bs=1M count=50 2>/dev/null
dd if=/dev/zero of=.git/big.pack bs=1M count=100 2>/dev/null
dd if=/dev/zero of=tests/fixtures/sample.parquet bs=1M count=30 2>/dev/null
echo "print('hi')" > src/main.py
echo "pandas==2.2.3" > requirements.txt

cat > Dockerfile <<'EOF'
FROM python:3.13-slim
WORKDIR /app
COPY . .
CMD ["python", "src/main.py"]
EOF

# Build без .dockerignore -- посмотри размер context'а
docker build -t test:no-ignore . 2>&1 | grep "transferring context"
# transferring context: 180.5MB

# Добавим .dockerignore
cat > .dockerignore <<'EOF'
.git
data
tests
__pycache__
EOF

# Build с .dockerignore
docker build -t test:with-ignore . 2>&1 | grep "transferring context"
# transferring context: 156B

# Сравни время сборки
time docker build --no-cache -t test:no-ignore .
time docker build --no-cache -t test:with-ignore .

cd .. && rm -rf context-test
docker rmi test:no-ignore test:with-ignore

Разница на больших проектах драматичная.


Проверка знанийKnowledge check
Команда жалуется, что docker build часто пересобирает слой RUN pip install -r requirements.txt при незначительных изменениях, например в README.md или tests/. Dockerfile корректный: COPY requirements.txt . идёт перед COPY . .. В чём проблема и как исправить?
ОтветAnswer
Проблема не в порядке инструкций, а в отсутствии или некорректном .dockerignore. COPY requirements.txt . действительно изолирован, но при отсутствии .dockerignore некоторые dev-инструменты могут менять timestamp или содержимое .git/ при коммитах, что не влияет напрямую на cache. Скорее всего здесь другая причина: они меняют requirements.txt косвенно (например, pre-commit hook добавляет comment) или используют COPY . . не строго после requirements. Проверка: docker build с --progress=plain покажет какой именно слой потерял cache. Если действительно из-за README/tests -- значит RUN pip install идёт ПОСЛЕ COPY . . (надо обратный порядок). Также добавить агрессивный .dockerignore: .git, README*, tests/, docs/, *.md.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что отправляется docker daemon'у при выполнении docker build .?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5