У попередніх статтях ми навчилися створювати функціональні та оптимізовані за розміром Docker-образи. Але є ще один критично важливий аспект — швидкість збірки. У реальних проєктах ви будете збирати образи десятки разів на день: при кожному коміті, при кожному pull request, при кожному деплої. Якщо збірка займає 10 хвилин, це 100 хвилин на день, 500 хвилин на тиждень — величезна втрата продуктивності.
Docker має потужний механізм кешування, який може зменшити час збірки з 10 хвилин до 10 секунд. Але щоб ефективно використовувати цей механізм, потрібно розуміти, як він працює, що таке build context, як правильно структурувати Dockerfile та використовувати .dockerignore.
У цій статті ми детально розглянемо механізм кешування шарів, дізнаємося, що таке build context і чому він критично важливий для швидкості збірки, навчимося оптимізувати порядок інструкцій у Dockerfile, та познайомимося з BuildKit — сучасним builder'ом Docker з просунутими можливостями кешування.
Build context — це набір файлів та директорій, які Docker надсилає демону для побудови образу. Зазвичай це директорія, яку ви вказуєте в команді docker build:
docker build -t myapp .
# ^ build context (поточна директорія)
Коли ви виконуєте цю команду, Docker:
COPY та ADDРозглянемо типову структуру .NET проєкту:
MyProject/
├── src/
│ ├── MyApp/
│ │ ├── bin/ # 50 МБ (скомпільовані файли)
│ │ ├── obj/ # 100 МБ (проміжні файли збірки)
│ │ └── *.cs
│ └── MyApp.Tests/
│ ├── bin/ # 30 МБ
│ ├── obj/ # 80 МБ
│ └── *.cs
├── .git/ # 200 МБ (історія Git)
├── .vs/ # 50 МБ (Visual Studio кеш)
├── node_modules/ # 300 МБ (якщо є frontend)
├── Dockerfile
└── .dockerignore
Без .dockerignore Docker надішле 810 МБ демону, навіть якщо для збірки потрібні лише .cs та .csproj файли (~5 МБ).
Наслідки:
node_modules інвалідує кеш COPY . .# Побудова з виводом розміру context
docker build --progress=plain -t myapp . 2>&1 | grep "transferring context"
Вивід:
#1 transferring context: 810.5MB
.dockerignore для виключення непотрібних файлів..dockerignore працює аналогічно .gitignore — він визначає, які файли та директорії виключити з build context.
Створіть файл .dockerignore в кореневій директорії проєкту (поруч з Dockerfile):
# Коментар
# Виключити конкретний файл
README.md
# Виключити директорію
bin/
obj/
# Виключити всі файли з розширенням
*.log
*.tmp
# Виключити всі файли в усіх піддиректоріях
**/*.log
# Виключити, але зробити виняток
*.md
!IMPORTANT.md
Типовий .dockerignore для .NET:
# Виключити результати збірки
**/bin/
**/obj/
# Виключити Git
.git/
.gitignore
.gitattributes
# Виключити IDE
.vs/
.vscode/
*.user
*.suo
# Виключити документацію
*.md
docs/
# Виключити тести (якщо не потрібні в образі)
**/*Tests/
**/*.Tests/
# Виключити CI/CD конфігурації
.github/
.gitlab-ci.yml
azure-pipelines.yml
# Виключити Docker файли
Dockerfile*
.dockerignore
docker-compose*.yml
# Виключити тимчасові файли
*.tmp
*.log
*.cache
# Виключити Node.js (якщо є frontend)
node_modules/
npm-debug.log
# Виключити OS файли
.DS_Store
Thumbs.db
# Без .dockerignore
docker build --progress=plain -t myapp . 2>&1 | grep "transferring context"
#1 transferring context: 810.5MB
# З .dockerignore
docker build --progress=plain -t myapp . 2>&1 | grep "transferring context"
#1 transferring context: 5.2MB
Економія: 805 МБ (99.4%)!
# * — будь-які символи (крім /)
*.log # file.log, app.log
test* # test.txt, testing.log
# ** — будь-які символи (включно з /)
**/*.log # src/app.log, tests/unit/test.log
# ? — один будь-який символ
file?.txt # file1.txt, fileA.txt
# ! — виняток (не виключати)
*.md # Виключити всі .md
!README.md # Але залишити README.md
Щоб побачити, які файли потрапляють у build context:
# Створити tar-архів build context (як це робить Docker)
tar -czf context.tar.gz -C . $(docker build --no-cache -q . 2>&1 | grep -oP '(?<=context: ).*')
# Або використати BuildKit
DOCKER_BUILDKIT=1 docker build --progress=plain . 2>&1 | grep "transferring"
.dockerignore одразу при створенні Dockerfile. Виключайте bin/, obj/, .git/, .vs/, node_modules/ — це стандарт для .NET проєктів. Це прискорить збірку в 10-100 разів.Docker кешує кожен шар образу. Якщо інструкція не змінилася, Docker використовує кешований шар замість повторного виконання.
Для кожної інструкції Docker обчислює хеш на основі:
COPY та ADD)Якщо хеш співпадає з кешованим шаром, Docker використовує кеш. Якщо ні — виконує інструкцію та інвалідує кеш для всіх наступних інструкцій.
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /src
# Копіювання всього коду
COPY . .
# Restore та build
RUN dotnet restore
RUN dotnet build -c Release
Проблема: Будь-яка зміна в коді (навіть коментар у Program.cs) інвалідує COPY . ., що призводить до повторного виконання dotnet restore (завантаження всіх NuGet пакетів — 1-2 хвилини).
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /src
# Спочатку копіюємо лише .csproj
COPY *.csproj .
# Restore (кешується, якщо .csproj не змінився)
RUN dotnet restore
# Потім копіюємо решту коду
COPY . .
# Build (виконується лише якщо код змінився)
RUN dotnet build -c Release
Переваги: Зміна коду не інвалідує dotnet restore. Restore виконується лише якщо змінилися залежності (.csproj).
Перша збірка (без кешу):
time docker build -t myapp .
# real 2m 15s (restore: 1m 30s, build: 45s)
Друга збірка (зміна коду, погана структура):
# Змінили Program.cs
time docker build -t myapp .
# real 2m 15s (restore знову: 1m 30s, build: 45s)
Друга збірка (зміна коду, оптимізована структура):
# Змінили Program.cs
time docker build -t myapp .
# real 0m 50s (restore з кешу: 0s, build: 50s)
Економія: 1 хвилина 25 секунд (63%)!
.csproj) змінюються рідше, ніж код (.cs), тому COPY *.csproj має бути перед COPY . ..Розглянемо детально, як правильно структурувати Dockerfile для .NET проєкту, щоб максимально використовувати кешування.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ❌ Копіюємо все одразу
COPY . .
# Restore, build, publish
RUN dotnet restore "MyApp/MyApp.csproj"
RUN dotnet build "MyApp/MyApp.csproj" -c Release -o /app/build
RUN dotnet publish "MyApp/MyApp.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Проблеми:
COPY . . копіює весь код, включно з .cs файламиdotnet restoreFROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Крок 1: Копіюємо ЛИШЕ .csproj файли
COPY ["MyApp/MyApp.csproj", "MyApp/"]
# ✅ Крок 2: Restore (кешується, якщо .csproj не змінився)
RUN dotnet restore "MyApp/MyApp.csproj"
# ✅ Крок 3: Копіюємо решту коду
COPY . .
# ✅ Крок 4: Build та Publish
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Переваги:
.cs файлів не інвалідує dotnet restoreРезультати:
| Сценарій | Погана структура | Оптимізована | Економія |
|---|---|---|---|
| Перша збірка | 160 с | 162 с | -2 с (незначно) |
| Зміна коду | 160 с | 70 с | 90 с (56%) |
| Зміна залежностей | 160 с | 162 с | -2 с |
Для solution з кількома проєктами:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Копіюємо всі .csproj та .sln
COPY ["MyApp.sln", "."]
COPY ["MyApp.Api/MyApp.Api.csproj", "MyApp.Api/"]
COPY ["MyApp.Core/MyApp.Core.csproj", "MyApp.Core/"]
COPY ["MyApp.Infrastructure/MyApp.Infrastructure.csproj", "MyApp.Infrastructure/"]
# ✅ Restore для всього solution
RUN dotnet restore "MyApp.sln"
# ✅ Копіюємо весь код
COPY . .
# ✅ Build та Publish
WORKDIR "/src/MyApp.Api"
RUN dotnet build "MyApp.Api.csproj" -c Release -o /app/build
RUN dotnet publish "MyApp.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.Api.dll"]
.dockerignore, щоб виключити bin/ та obj/ з усіх проєктів: **/bin/, **/obj/.Docker обчислює хеш для COPY на основі:
# Приклад: зміна Program.cs
# Хеш для "COPY *.csproj ." — НЕ ЗМІНИВСЯ (файл не торкнутий)
# Хеш для "COPY . ." — ЗМІНИВСЯ (Program.cs змінився)
За замовчуванням dotnet restore завантажує пакети у глобальний кеш NuGet всередині контейнера. При кожній новій збірці цей кеш втрачається. BuildKit дозволяє зберігати кеш між збірками.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
# ❌ Пакети завантажуються у /root/.nuget/packages
# При кожній збірці цей шар створюється заново
RUN dotnet restore "MyApp/MyApp.csproj"
Проблема: Якщо .csproj не змінився, Docker використовує кеш. Але якщо змінився — всі пакети завантажуються знову (навіть ті, що вже були).
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
# ✅ Монтуємо persistent кеш NuGet
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore "MyApp/MyApp.csproj"
COPY . .
WORKDIR "/src/MyApp"
# ✅ Монтуємо кеш для build artifacts
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet build "MyApp.csproj" -c Release -o /app/build
Переваги:
--mount=type=cache потрібен BuildKit. Увімкніть його через export DOCKER_BUILDKIT=1 або додайте # syntax=docker/dockerfile:1 на початку Dockerfile.Сценарій: Додали новий NuGet пакет Newtonsoft.Json до .csproj.
| Підхід | Час restore | Пояснення |
|---|---|---|
| Без кешу | 90 с | Завантажує всі 50 пакетів знову |
| З Docker layer cache | 90 с | Шар інвалідовано, завантажує всі 50 пакетів |
| З BuildKit cache mount | 12 с | Завантажує лише 1 новий пакет |
Якщо у вас solution з 10+ проєктами, копіювання кожного .csproj окремо стає громіздким. Використовуйте скрипт або wildcards.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Копіюємо .sln та всі .csproj
COPY ["MyApp.sln", "."]
COPY ["src/MyApp.Api/MyApp.Api.csproj", "src/MyApp.Api/"]
COPY ["src/MyApp.Core/MyApp.Core.csproj", "src/MyApp.Core/"]
COPY ["src/MyApp.Infrastructure/MyApp.Infrastructure.csproj", "src/MyApp.Infrastructure/"]
COPY ["tests/MyApp.Tests/MyApp.Tests.csproj", "tests/MyApp.Tests/"]
RUN dotnet restore "MyApp.sln"
COPY . .
RUN dotnet build "src/MyApp.Api/MyApp.Api.csproj" -c Release -o /app/build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Копіюємо .sln
COPY ["*.sln", "."]
# ✅ Копіюємо всі .csproj, зберігаючи структуру директорій
COPY ["**/*.csproj", "./"]
# Restore
RUN dotnet restore "MyApp.sln"
COPY . .
RUN dotnet build "src/MyApp.Api/MyApp.Api.csproj" -c Release -o /app/build
**/*.csproj працюють у BuildKit. Переконайтеся, що у вас увімкнено BuildKit (DOCKER_BUILDKIT=1) або додайте # syntax=docker/dockerfile:1 на початку Dockerfile.Для production-образів можна зменшити розмір та прискорити запуск через trimming або AOT compilation.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore "MyApp/MyApp.csproj"
COPY . .
WORKDIR "/src/MyApp"
# ✅ Publish з trimming (видаляє невикористаний код)
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish "MyApp.csproj" \
-c Release \
-o /app/publish \
/p:PublishTrimmed=true \
/p:TrimMode=link
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Результати trimming:
| Метрика | Без trimming | З trimming | Економія |
|---|---|---|---|
| Розмір образу | 220 МБ | 95 МБ | 125 МБ (57%) |
| Час запуску | 1.2 с | 0.8 с | 0.4 с (33%) |
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Копіюємо .csproj для restore
COPY ["MyApp/MyApp.csproj", "MyApp/"]
# Restore з persistent NuGet cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore "MyApp/MyApp.csproj"
# Копіюємо весь код
COPY . .
WORKDIR "/src/MyApp"
# Build з cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet build "MyApp.csproj" -c Release -o /app/build
# Publish з trimming
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish "MyApp.csproj" \
-c Release \
-o /app/publish \
/p:UseAppHost=false \
/p:PublishTrimmed=true
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Non-root user для безпеки
RUN addgroup -g 1000 appuser && adduser -u 1000 -G appuser -s /bin/sh -D appuser
USER appuser
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
Характеристики:
Час збірки:
| Сценарій | Час |
|---|---|
| Перша збірка (без кешу) | 180 с |
| Зміна коду (з кешем) | 55 с |
| Зміна залежностей (з BuildKit cache) | 70 с |
Docker інвалідує кеш шару, якщо:
COPY та ADD)--no-cache (примусова збірка без кешу)FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /src
# Шар 1: ✓ Кеш (файл не змінився)
COPY ["MyApp.csproj", "."]
# Шар 2: ✓ Кеш (попередній шар з кешу + інструкція не змінилася)
RUN dotnet restore
# Шар 3: ✗ Інвалідовано (Program.cs змінився)
COPY . .
# Шар 4: ✗ Інвалідовано (попередній шар інвалідовано)
RUN dotnet build -c Release
# Шар 5: ✗ Інвалідовано (попередній шар інвалідовано)
RUN dotnet publish -c Release -o /app
Іноді потрібно повністю перебудувати образ, ігноруючи кеш:
# Збірка без використання кешу
docker build --no-cache -t myapp .
Коли використовувати:
FROM)apt-get update)BuildKit дозволяє інвалідувати кеш для конкретного stage:
# Інвалідувати кеш лише для stage "build"
docker build --no-cache-filter build -t myapp .
Приклад Dockerfile з іменованими stages:
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS restore
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore
FROM restore AS build
COPY . .
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
# Інвалідувати кеш лише для stage "build" та всіх наступних
docker build --no-cache-filter build -t myapp .
# restore — використає кеш
# build, publish — перебудуються
За замовчуванням Docker ігнорує timestamps файлів при обчисленні хешу. Враховується лише вміст файлу.
# Зміна timestamp не інвалідує кеш
touch Program.cs
docker build -t myapp .
# Кеш використовується, бо вміст файлу не змінився
Але якщо ви використовуєте ADD замість COPY, timestamps можуть впливати на кеш (ще одна причина уникати ADD).
COPY замість ADD для локальних файлів. ADD має додаткову логіку (розпакування архівів, завантаження URL), яка може призвести до непередбачуваної інвалідації кешу.Змінні оточення (ENV, ARG) впливають на кеш:
FROM mcr.microsoft.com/dotnet/sdk:8.0
# ❌ Погано: версія хардкодиться в інструкції
RUN apt-get update && apt-get install -y curl=7.81.0-1ubuntu1.15
# ✅ Краще: версія через ARG
ARG CURL_VERSION=7.81.0-1ubuntu1.15
RUN apt-get update && apt-get install -y curl=${CURL_VERSION}
Зміна ARG при збірці:
# Перша збірка
docker build --build-arg CURL_VERSION=7.81.0-1ubuntu1.15 -t myapp .
# Друга збірка з іншою версією — кеш інвалідується
docker build --build-arg CURL_VERSION=7.81.0-1ubuntu1.16 -t myapp .
Інструкції, що завантажують дані з інтернету, можуть використовувати застарілий кеш:
FROM ubuntu:22.04
# ❌ Проблема: apt-get update кешується
RUN apt-get update && apt-get install -y curl
# Через місяць цей шар все ще використовує кеш,
# навіть якщо в репозиторіях з'явилися оновлення безпеки
Рішення 1: Об'єднати update та install
# ✅ Завжди виконувати update разом з install
RUN apt-get update && apt-get install -y \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*
Рішення 2: Використовувати --no-cache для production збірок
# У CI/CD для production завжди збирати без кешу
docker build --no-cache -t myapp:production .
Під час збірки Docker показує, чи використовується кеш:
docker build -t myapp .
Вивід:
[+] Building 45.2s (12/12) FINISHED
=> [internal] load build definition from Dockerfile
=> [internal] load .dockerignore
=> [internal] load metadata for mcr.microsoft.com/dotnet/sdk:8.0
=> [1/6] FROM mcr.microsoft.com/dotnet/sdk:8.0
=> [internal] load build context
=> CACHED [2/6] WORKDIR /src # ← Кеш використано
=> CACHED [3/6] COPY MyApp.csproj . # ← Кеш використано
=> CACHED [4/6] RUN dotnet restore # ← Кеш використано
=> [5/6] COPY . . # ← Виконано (кеш інвалідовано)
=> [6/6] RUN dotnet build -c Release # ← Виконано
=> exporting to image
Ключові слова:
CACHED — шар взято з кешуCACHED — шар виконано зановоdocker build, щоб зрозуміти, які шари інвалідуються. Якщо dotnet restore виконується при кожній збірці, перевірте порядок інструкцій COPY.BuildKit — це новий backend для побудови Docker-образів, який замінює класичний builder. Він увімкнений за замовчуванням у Docker 23.0+ (2023 рік), але для старіших версій потрібно увімкнути вручну.
Переваги BuildKit:
Для Docker 23.0+ (за замовчуванням увімкнено):
# Перевірити версію
docker version
# Якщо версія >= 23.0, BuildKit вже активний
Для старіших версій Docker:
# Тимчасово для однієї команди
DOCKER_BUILDKIT=1 docker build -t myapp .
# Постійно (додати у ~/.bashrc або ~/.zshrc)
export DOCKER_BUILDKIT=1
# Або через daemon.json (глобально)
# /etc/docker/daemon.json
{
"features": {
"buildkit": true
}
}
Через Dockerfile (рекомендовано):
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0
# Решта інструкцій...
Перший рядок # syntax=docker/dockerfile:1 автоматично активує BuildKit для цього Dockerfile.
# syntax=docker/dockerfile:1 — це не коментар, а директива для Docker. Вона вказує використовувати останню версію BuildKit frontend.Найпотужніша функція BuildKit — --mount=type=cache. Вона дозволяє зберігати директорії між збірками.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
# ✅ Монтуємо /root/.nuget/packages як persistent cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
COPY . .
# ✅ Використовуємо той самий кеш для build
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet build -c Release -o /app/build
Як це працює:
/root/.nuget/packagesБез cache mount:
# Перша збірка
docker build -t myapp .
# dotnet restore: 90 секунд (завантажує 50 пакетів)
# Змінили .csproj (додали новий пакет)
docker build -t myapp .
# dotnet restore: 90 секунд (завантажує всі 51 пакет знову)
З cache mount:
# Перша збірка
DOCKER_BUILDKIT=1 docker build -t myapp .
# dotnet restore: 90 секунд (завантажує 50 пакетів)
# Змінили .csproj (додали новий пакет)
DOCKER_BUILDKIT=1 docker build -t myapp .
# dotnet restore: 8 секунд (завантажує лише 1 новий пакет)
Економія: 82 секунди (91%)!
Можна монтувати кілька директорій одночасно:
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet \
dotnet restore
COPY . .
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/tmp/.buildcache \
dotnet build -c Release -o /app/build
Bind mounts дозволяють монтувати файли з build context без копіювання їх у шар образу.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Монтуємо .csproj без копіювання у шар
RUN --mount=type=bind,source=MyApp.csproj,target=MyApp.csproj \
dotnet restore MyApp.csproj
# ✅ Монтуємо весь код для build
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.nuget/packages \
dotnet build MyApp.csproj -c Release -o /app/build
Переваги:
COPY для великих файлівCOPY.Secret mounts дозволяють передавати секрети (токени, паролі) у build без збереження їх у шарах образу.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Монтуємо секрет для доступу до приватного NuGet feed
RUN --mount=type=secret,id=nuget_token \
dotnet nuget add source https://private.nuget.org/v3/index.json \
--name PrivateFeed \
--username user \
--password $(cat /run/secrets/nuget_token) \
--store-password-in-clear-text
COPY ["MyApp.csproj", "."]
RUN dotnet restore
Передача секрету при збірці:
# Секрет з файлу
docker build --secret id=nuget_token,src=./nuget-token.txt -t myapp .
# Секрет зі змінної оточення
docker build --secret id=nuget_token,env=NUGET_TOKEN -t myapp .
Безпека:
RUN інструкціїdocker historySSH mounts дозволяють клонувати приватні репозиторії під час збірки.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# ✅ Монтуємо SSH ключ для git clone
RUN --mount=type=ssh \
git clone git@github.com:mycompany/private-lib.git
COPY ["MyApp.csproj", "."]
RUN dotnet restore
Передача SSH ключа при збірці:
# Використовуємо SSH agent
eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa
# Збірка з SSH mount
docker build --ssh default -t myapp .
BuildKit автоматично виявляє незалежні шари та будує їх паралельно.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS base
WORKDIR /src
# Stage 1: Restore API
FROM base AS restore-api
COPY ["MyApp.Api/MyApp.Api.csproj", "MyApp.Api/"]
RUN dotnet restore "MyApp.Api/MyApp.Api.csproj"
# Stage 2: Restore Worker (паралельно з API)
FROM base AS restore-worker
COPY ["MyApp.Worker/MyApp.Worker.csproj", "MyApp.Worker/"]
RUN dotnet restore "MyApp.Worker/MyApp.Worker.csproj"
# Stage 3: Build API
FROM restore-api AS build-api
COPY MyApp.Api/ MyApp.Api/
RUN dotnet build "MyApp.Api/MyApp.Api.csproj" -c Release
# Stage 4: Build Worker (паралельно з API)
FROM restore-worker AS build-worker
COPY MyApp.Worker/ MyApp.Worker/
RUN dotnet build "MyApp.Worker/MyApp.Worker.csproj" -c Release
BuildKit автоматично:
restore-api та restore-worker паралельноbuild-api та build-worker паралельноBuildKit показує інтерактивний прогрес:
docker build -t myapp .
Класичний builder:
Step 1/8 : FROM mcr.microsoft.com/dotnet/sdk:8.0
---> a1b2c3d4e5f6
Step 2/8 : WORKDIR /src
---> Running in 1234567890ab
---> a1b2c3d4e5f6
...
BuildKit:
[+] Building 45.2s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> [internal] load .dockerignore 0.0s
=> [internal] load metadata for mcr.microsoft.com/dotnet/sdk:8.0 1.2s
=> [1/6] FROM mcr.microsoft.com/dotnet/sdk:8.0 0.0s
=> [internal] load build context 0.3s
=> CACHED [2/6] WORKDIR /src 0.0s
=> CACHED [3/6] COPY MyApp.csproj . 0.0s
=> CACHED [4/6] RUN dotnet restore 0.0s
=> [5/6] COPY . . 0.5s
=> [6/6] RUN dotnet build -c Release 42.1s
=> exporting to image 1.0s
Переваги:
CACHED)BuildKit дозволяє зберігати кеш у registry (Docker Hub, GitHub Container Registry) та використовувати його в CI/CD.
# Збірка з експортом кешу у registry
docker buildx build \
--cache-to type=registry,ref=myuser/myapp:buildcache,mode=max \
--cache-from type=registry,ref=myuser/myapp:buildcache \
-t myuser/myapp:latest \
--push \
.
Параметри:
--cache-to — куди експортувати кеш--cache-from — звідки імпортувати кешmode=max — зберігати всі шари (не лише фінальні)Використання в CI/CD (GitHub Actions):
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: myuser/myapp:latest
cache-from: type=registry,ref=myuser/myapp:buildcache
cache-to: type=registry,ref=myuser/myapp:buildcache,mode=max
Переваги:
Дано неоптимізований Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet build -c Release
RUN dotnet publish -c Release -o /out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY --from=0 /out .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Завдання:
.dockerignore для виключення непотрібних файлівОчікуваний результат: Час повторної збірки (при зміні коду) зменшиться з 120 с до 40-50 с.
Завдання:
bin/, obj/, .git/).dockerignore.dockerignoreКоманди:
# Перевірка розміру context
docker build --progress=plain -t myapp . 2>&1 | grep "transferring context"
Очікуваний результат: Зменшення розміру context з 500+ МБ до 5-10 МБ.
Завдання:
Питання для аналізу:
Завдання:
dotnet restore без cache mount.csproj (додайте новий пакет)--mount=type=cache для NuGetОчікуваний результат: Час restore зменшиться з 60-90 с до 5-10 с.
У цій статті ми детально розглянули механізм кешування Docker та оптимізацію швидкості збірки:
Build Context:
.dockerignore виключає непотрібні файли (економія 90-99% розміру)Механізм кешування:
Оптимізація для .NET:
.csproj окремо від коду для кешування dotnet restoreCache Busting:
--no-cache — повна перебудова без кешу--no-cache-filter — вибіркова інвалідація для конкретного stageBuildKit:
COPY *.csproj → RUN dotnet restore → COPY . . → RUN dotnet build. Це одна зміна, яка прискорить ваші збірки у 5-10 разів.