Dockerfile “failed to solve”: помилки, які можна виправити миттєво

Було корисно?

Ніщо так не символізує «спокійний, здоровий конвеєр доставки», як вибух збірки Docker зі failed to solve за п’ять хвилин до релізу. Лог CI перетворюється на детективний роман, написаний компілятором. Хтось пропонує «просто перезапустити», ніби продакшен‑системи живляться оптимізмом.

Цей посібник — практичніша версія: що насправді означає failed to solve (це BuildKit скаржиться, а не Docker), як швидко знайти реальний збій і які виправлення надійно працюють у реальному CI: нестабільні мережі, закриті ранери, надвеликі репозиторії та «безпекові» контролі, що ламають збірки.

Що насправді означає “failed to solve”

Коли ви бачите failed to solve, майже завжди це підняття помилки з боку BuildKit. BuildKit — сучасний рушій збірки Docker: він будує граф залежностей (шари, джерела, маунти), а потім «вирішує» його як план збірки. Якщо щось ламається в будь‑якому вузлі цього графа — завантаження базового образу, читання файлу в контексті збірки, виконання RUN‑команди, обчислення контрольної суми, розпакування архіву — BuildKit повідомляє про помилку як «failed to solve». Це не прагне бути загадковим. Просто воно не прагне бути корисним.

Є три ключові наслідки:

  • Реальна помилка майже завжди знаходиться кількома рядками вище. «failed to solve» — це фінал, а не сам сюжет.
  • Крок, що падає, — це вузол у графі. Номер кроку в логах може вводити в оману, якщо кроки виконуються паралельно або використовують кеш.
  • Контекст важливіший за чистоту Dockerfile. Багато збоїв — не «помилки Dockerfile», а проблеми середовища, мережі, дозволів або розміру контексту.

Якщо хочете один корисний операційний ментальний образ: Docker build — це конвеєр вхідних даних (файли контексту, базові образи, секрети, мережа) в детерміністичні перетворення (шари). «Failed to solve» означає, що один вхід відсутній, недоступний, недовірений або настільки повільний, що його вважають мертвим.

Цитата, бо це досі болісно актуально в системах збірки й у всьому іншому: «Надія — це не стратегія.» — Gene Kranz

Плейбук швидкої діагностики

Це порядок дій, що економить найбільше часу в більшості середовищ. Не імпровізуйте. Дотримуйтеся кроків, доки не знайдете вузьке місце.

1) Знайдіть першу реальну помилку, а не останню обгортку

  • Прокрутіть вгору до першого корисного рядка з помилкою. BuildKit часто друкує кілька каскадних повідомлень про помилку.
  • Визначте категорію: контекст, синтаксис, мережа, автентифікація, дозволи, платформа, кеш або час виконання.

2) Підтвердіть який саме билд‑рушій і його налаштування

  • Чи увімкнений BuildKit? Ви використовуєте buildx? Який драйвер (docker, docker-container, kubernetes)?
  • Чи помилка на віддаленому билдері, де файловий простір/мережа відрізняються від вашого ноутбука?

3) Спочатку перевірте здоров’я контексту збірки

  • Більшість «миттєвих виправлень» — це проблеми з контекстом: відсутні файли, невірний .dockerignore, гігантський контекст, дозволи.
  • Перевірте шляхи, вказані в COPY/ADD, і підтвердіть, що вони знаходяться всередині контексту збірки.

4) Якщо це мережа/автентифікація — відтворіть одним запитом

  • Спробуйте витягти базовий образ з тими самими обліковими даними й мережевими налаштуваннями.
  • Спробуйте curl до кінцевої точки пакету з раннера (не з вашої робочої станції).

5) Якщо це RUN‑крок — зменшіть його

  • Зробіть видимою проблемну RUN‑команду (без «&& … && …» довгих рядків під час налагодження).
  • Тимчасово відключіть прапори паралельності й додайте докладний вивід.

6) Якщо це пов’язано з кешем — доведіть це

  • Перезапустіть збірку з --no-cache або почистіть кеші, щоб переконатися, що ви не женетеся за застарілим станом.
  • Потім виправляйте ключі кеша, а не симптоми.

Жарт №1: Ставтеся до «failed to solve», як до істерики малюка — раніше щось реально сталося, а тепер усі просто кричать.

Категорії миттєвих виправлень (і чому вони трапляються)

Категорія A: Синтаксис Dockerfile і помилки парсера

Це приємні помилки. Вони падають швидко і послідовно. Ви побачите повідомлення на кшталт:

  • failed to parse Dockerfile
  • unknown instruction
  • invalid reference format

Виправлення майже завжди — опечатка, відсутній \, неправильні лапки або використання фічі, яку ваш билд‑рушій не підтримує. Якщо ви копіюєте приклади з блогів, ставтеся до них підозріло.

Категорія B: Проблеми з контекстом збірки (відсутні файли, неправильні шляхи, .dockerignore)

Це найпоширеніше «миттєве виправлення», і його найпростіше неправильно діагностувати, бо люди плутають структуру репозиторію з контекстом збірки. Контекст — це будь‑яка директорія, яку ви передаєте в docker build. BuildKit бачить лише файли під цією директорією (за винятком шаблонів в .dockerignore). Якщо ваш Dockerfile каже:

  • COPY ../secrets /secrets (ні)
  • COPY build/output/app /app (можливо, якщо це існує під час збірки)
  • COPY . /src (працює, але часто дурно)

То від правильності контексту залежить результат. Невелика помилка в .dockerignore може перетворитися на «checksum of ref … not found», «failed to compute cache key» або класичне «failed to walk … no such file or directory».

Категорія C: Проблеми з дозволами та власністю

BuildKit виконує кроки з певними користувачами, маунтами та режимами тільки для читання. Між rootless Docker, захищеними CI‑ранерами і корпоративними NFS ви можете зустріти:

  • permission denied під час читання файлів контексту
  • operation not permitted при chmod/chown
  • failed to create shim task коли простори імен користувачів колізуються

Виправлення варіюються від буденних (chmod файлу) до структурних (припиніть намагатися chown 200k файлів під час збірки).

Категорія D: Мережа, DNS, проксі та корпоративний MITM

Docker‑збірки завантажують базові образи й пакети. Це означає проблеми DNS, TLS, проксі та таймаути. BuildKit також робить паралельні запити, що може зробити нестабільну мережу більш помітною. Ви побачите:

  • failed to fetch oauth token
  • i/o timeout
  • x509: certificate signed by unknown authority
  • temporary failure in name resolution

Часто вирішується миттєво встановленням правильних proxy‑змінних, імпортом корпоративного CA або використанням mirror‑реєстру, доступного з раннера.

Категорія E: Автентифікація реєстру та ліміти запитів

Неавторизовані pulls потрапляють під ліміти. Неправильні облікові дані дають 401/403. Приватні реєстри з простроченими токенами б’ють у найгірший момент. BuildKit інколи обгортує це в банальний «failed to solve», але основна помилка зазвичай явна, якщо прокрутити.

Категорія F: Невідповідність платформи та архітектури

Multi‑arch збірки прекрасні, поки не ні. Якщо у вашого базового образу немає потрібної платформи, ви побачите помилки на кшталт:

  • no match for platform in manifest
  • exec format error

Виправлення: вказати --platform, обрати мульти‑арх образ або припинити будувати amd64 на arm64‑ранері без емульації.

Категорія G: Дива кешу/солвера (checksum, cache key, інвалідизація)

BuildKit агресивно кешує — це добре, поки ваша збірка не покладається на те, що не слід кешувати. Помилки можуть включати:

  • failed to compute cache key
  • failed to calculate checksum of ref
  • rpc error: code = Unknown desc = …

Це часто означає «файл, який очікує BuildKit, відсутній у контексті» або «стан кеша пошкоджено» чи «віддалений билд‑рушій втратив маунт». Це не містика — це стан.

Категорія H: Секрети, SSH‑маунти і креденшіали часу збірки

Сучасні збірки використовують RUN --mount=type=secret та RUN --mount=type=ssh. Чудово. Але якщо CI не передає секрет/ssh‑агент, BuildKit може впасти з:

  • secret not found
  • ssh agent requested but no SSH_AUTH_SOCK

Виправлення: передайте секрет або перестаньте вдавати, що збірка має доступ до приватних ресурсів без налаштування.

Практичні завдання: команди, виводи, рішення

Це реальні перевірки, які можна виконати на машині розробника або на раннері CI. Кожна містить: команду, що означає вивід і яке рішення прийняти далі. Виконуйте їх відповідно до категорії помилки.

Завдання 1: Підтвердити, чи задіяно BuildKit

cr0x@server:~$ docker build --help | head -n 5
Usage:  docker build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

Що це означає: Вихід help сам по собі не скаже про BuildKit, але підтверджує, що ви користуєтеся класичним CLI‑точкою входу.

Рішення: Далі перевірте змінні середовища й стан билдера явно.

cr0x@server:~$ echo $DOCKER_BUILDKIT

Що це означає: Порожнє зазвичай означає «за замовчуванням». У багатьох сучасних інсталяціях за замовчуванням уже ввімкнено BuildKit.

Рішення: Перевірте buildx‑билдери, щоб побачити, який бекенд ви насправді використовуєте.

Завдання 2: Переглянути buildx і активний билд

cr0x@server:~$ docker buildx ls
NAME/NODE       DRIVER/ENDPOINT             STATUS    BUILDKIT   PLATFORMS
default         docker                       running   v0.12.5    linux/amd64,linux/arm64
ci-builder *    docker-container            running   v0.12.5    linux/amd64

Що це означає: Ви використовуєте контейнеризований билд‑рушій (docker-container) з назвою ci-builder. Цей билд‑рушій має власну мережу, DNS і сховище кеша.

Рішення: Якщо помилки схожі на DNS/таймаути або відсутні файли, відтворюйте за допомогою того самого билдера, а не стандартного.

Завдання 3: Перезапустити зі звичайним прогресом, щоб знайти реальний рядок помилки

cr0x@server:~$ docker buildx build --progress=plain -t testimg:debug .
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.27kB done
#2 [internal] load metadata for docker.io/library/alpine:3.19
#2 ERROR: failed to do request: Head "https://registry-1.docker.io/v2/library/alpine/manifests/3.19": dial tcp: lookup registry-1.docker.io: temporary failure in name resolution
------
 > [internal] load metadata for docker.io/library/alpine:3.19:
------
failed to solve: failed to do request: Head "https://registry-1.docker.io/v2/library/alpine/manifests/3.19": dial tcp: lookup registry-1.docker.io: temporary failure in name resolution

Що це означає: Це не проблема Dockerfile. DNS у билдера зламано.

Рішення: Перейдіть до перевірок мережі/DNS. Не чіпайте Dockerfile поки — ви тільки додасте нові баги.

Завдання 4: Перевірити розмір контексту збірки (великі контексти спричиняють повільні «failed to solve»)

cr0x@server:~$ docker buildx build --progress=plain --no-cache -t testimg:ctx .
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.10kB done
#2 [internal] load .dockerignore
#2 transferring context: 2B done
#3 [internal] load build context
#3 transferring context: 1.48GB 34.2s done

Що це означає: Ви відправляєте 1.48GB контексту билдеру. Це не «трохи велике». Це «чому ваш git‑репозиторій одночасно файловий сервер?»

Рішення: Виправте .dockerignore і звузьте область COPY. Великі контексти спричиняють таймаути, треш кеша і повільний CI.

Завдання 5: Показати, що ігнорується .dockerignore (перевірка здорового глузду)

cr0x@server:~$ sed -n '1,120p' .dockerignore
.git
node_modules
dist
*.log

Що це означає: Виглядає нормально, але можливо ви забули build/, target/, .venv/ або великі тестові дані.

Рішення: Додайте ігнорування для згенерованих артефактів і локальних кешів; переконайтеся, що збірка все ще має необхідні файли.

Завдання 6: Піймати «COPY failed: file not found» до того, як витратите час

cr0x@server:~$ grep -nE '^(COPY|ADD) ' Dockerfile
12:COPY build/output/app /app/app
13:COPY configs/prod.yaml /app/config.yaml

Що це означає: Dockerfile очікує, що build/output/app існує у контексті збірки під час збірки.

Рішення: Якщо CI не генерує цей шлях до docker build, збірка впаде. Або створіть артефакт у попередньому кроці CI, або зробіть багатостадійну збірку, яка збирає всередині образу.

Завдання 7: Підтвердити, що згадані файли існують у контексті

cr0x@server:~$ ls -la build/output/app configs/prod.yaml
ls: cannot access 'build/output/app': No such file or directory
-rw-r--r-- 1 cr0x cr0x  842 Jan  2 09:41 configs/prod.yaml

Що це означає: Один COPY впаде одразу.

Рішення: Вирішіть, чи (a) створювати артефакт перед docker build, або (b) перейти на багатостадійну збірку, яка компілює всередині образу.

Завдання 8: Перевірити витяг базового образу та автентифікацію (окремо від збірки)

cr0x@server:~$ docker pull alpine:3.19
3.19: Pulling from library/alpine
Digest: sha256:de0eb0b3f2a47ba1b5d1c61c87a1f1d9b5a4f34b3a4a1d7f3d6c2c6a7f1d2d3c
Status: Image is up to date for alpine:3.19
docker.io/library/alpine:3.19

Що це означає: Доступ до реєстру з Docker‑хоста в порядку. Якщо buildx використовує віддалений билд‑рушій, той може бути все ще зламаний — не святкуйте перемогу зарано.

Рішення: Якщо збірка все ще падає при витягу того самого образу, перегляньте мережу билдера або налаштування проксі.

Завдання 9: Перевірити DNS та вихідне з’єднання з контейнера билдера

cr0x@server:~$ docker ps --filter name=buildx_buildkit --format '{{.ID}} {{.Names}}'
a1b2c3d4e5f6 buildx_buildkit_ci-builder0
cr0x@server:~$ docker exec -it buildx_buildkit_ci-builder0 sh -lc 'cat /etc/resolv.conf && nslookup registry-1.docker.io 2>/dev/null | head -n 5'
nameserver 127.0.0.11
options ndots:0

Server:    127.0.0.11
Address:   127.0.0.11:53

Що це означає: Билдер використовує вбудований DNS Docker (127.0.0.11). Якщо nslookup зависає або падає, ймовірно, проблема в конфігурації демонa Docker або заблокований вихідний UDP/TCP на 53 порті.

Рішення: Виправте налаштування DNS демона Docker або вкажіть явні DNS‑сервери для середовища билдера.

Завдання 10: Виявити невідповідність налаштувань проксі (класичний корпоративний режим відмови)

cr0x@server:~$ env | grep -iE 'http_proxy|https_proxy|no_proxy'
HTTP_PROXY=http://proxy.corp:8080
HTTPS_PROXY=http://proxy.corp:8080
NO_PROXY=localhost,127.0.0.1,.corp

Що це означає: Проксі встановлено у вашому шеллі. Контейнер билдера може не наслідувати його автоматично залежно від способу створення.

Рішення: Передайте proxy як build‑арги або налаштуйте билдера/демона на використання проксі послідовно. Якщо TLS перехоплюється, доведеться також додати корпоративний CA всередину образу збірки.

Завдання 11: Швидко виявити невідповідність платформи

cr0x@server:~$ docker buildx imagetools inspect alpine:3.19 | sed -n '1,40p'
Name:      docker.io/library/alpine:3.19
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Manifests:
  Name:      docker.io/library/alpine:3.19@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
  Platform:  linux/amd64
  Name:      docker.io/library/alpine:3.19@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
  Platform:  linux/arm64

Що це означає: Базовий образ мульти‑архітектурний. Добре. Якщо ваш базовий образ має тільки одну платформу, а ви будуєте для іншої — ось корінь проблеми.

Рішення: Оберіть базовий образ з потрібною платформою або вкажіть --platform явно і налаштуйте емульацію за потреби.

Завдання 12: Примусова чиста збірка, щоб виключити корупцію кеша

cr0x@server:~$ docker buildx build --no-cache --progress=plain -t testimg:nocache .
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.27kB done
#2 [internal] load metadata for docker.io/library/alpine:3.19
#2 DONE 0.9s
#3 [internal] load build context
#3 transferring context: 52.4MB 1.1s done
#4 [1/6] FROM docker.io/library/alpine:3.19@sha256:bbbb...
#4 DONE 0.0s
#5 [2/6] RUN apk add --no-cache ca-certificates
#5 DONE 2.8s

Що це означає: Якщо чиста збірка проходить, а кешовані збірки падають періодично — у вас проблеми зі станом кеша (часто диск билдера під тиском або пошкоджені записи кеша).

Рішення: Очистіть сховище билдера та впорядкуйте експорт/імпорт кеша замість того, щоб «вимкнути кеш назавжди».

Завдання 13: Перевірити тиск на диск билдера (мовчазний вбивця)

cr0x@server:~$ docker exec -it buildx_buildkit_ci-builder0 sh -lc 'df -h /var/lib/buildkit | tail -n 1'
overlay          20G   19G  1.0G  95% /

Що це означає: 95% заповнено на файловій системі билдера. Очікуйте дивні помилки: збій розпакування, «no space left», випадкові проблеми кеша й суттєве уповільнення.

Рішення: Очищуйте кеш збірки, розширте диск або припиніть експортувати величезні кеші, які ви ніколи не використовуєте.

Завдання 14: Безпечно очистити кеш BuildKit (і як інтерпретувати вивід)

cr0x@server:~$ docker buildx prune -f --verbose
ID                                              RECLAIMABLE     SIZE        LAST ACCESSED
v1:9n8m7l6k5j4h3g2f1d0s                          true            1.2GB       2 days ago
v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa          true            650MB       7 days ago
Total: 1.85GB reclaimed

Що це означає: Ви звільнили 1.85GB. Якщо билдер був на 95%, це може відразу перетворити збій на успішну збірку.

Рішення: Якщо прибирання допомогло, запровадьте політики утримання кеша й моніторинг диска билдера. Якщо ні — продовжуйте шукати причину.

Завдання 15: Діагностувати «secret not found» і виправити підключення

cr0x@server:~$ docker buildx build --progress=plain --secret id=npmrc,src=$HOME/.npmrc -t testimg:secrets .
#7 [4/6] RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
#7 DONE 18.9s

Що це означає: Ваш секретний маунт присутній і крок завершився успішно. Якщо ви опустите --secret, BuildKit впаде на кроці виконання.

Рішення: У CI переконайтеся, що секрет існує, має правильну область дії і передається командою збірки. Ніколи не запікайте секрет у образ. Ніколи.

Завдання 16: Зробити проблемний RUN‑крок налагоджуваним

cr0x@server:~$ docker buildx build --progress=plain --build-arg DEBUG=1 -t testimg:debugrun .
#9 [5/6] RUN set -eux; apk add --no-cache git; git --version
+ apk add --no-cache git
(1/4) Installing ca-certificates (20241121-r0)
(2/4) Installing libcurl (8.6.0-r0)
(3/4) Installing pcre2 (10.42-r2)
(4/4) Installing git (2.45.2-r0)
OK: 26 MiB in 24 packages
+ git --version
git version 2.45.2

Що це означає: set -eux показує точну команду, яка впала, і зупиняється на першій помилці. Це зупиняє здогадки.

Рішення: Тримайте шаблон «debug mode» у Dockerfile через build‑arg, щоб включати його коли CI ламається.

Типові помилки: симптом → корінь проблеми → виправлення

1) «failed to compute cache key» біля COPY/ADD

Симптоми: Збірка падає на рядку COPY з повідомленнями про cache‑key/checksum; інколи згадується «not found».

Корінь проблеми: Джерельний шлях не існує в контексті збірки (або ігнорується .dockerignore), тому BuildKit не може його захешувати для обчислення ключа кешу.

Виправлення: Переконайтеся, що файл існує до запуску build, відкоригуйте директорію контексту або оновіть .dockerignore. Краще копіювати тільки потрібні файли, а не COPY . ..

2) «COPY failed: stat … no such file or directory»

Симптоми: Пряма помилка про відсутній файл.

Корінь проблеми: Невірний відносний шлях, неправильний контекст або CI запускає з іншої робочої директорії, ніж ви припускали.

Виправлення: Використовуйте однозначні команди: docker build -f path/to/Dockerfile path/to/context. У CI друкуйте pwd і ls перед збіркою. Зробіть шляхи нудно очевидними.

3) «failed to do request: Head … temporary failure in name resolution»

Симптоми: Не вдається отримати метадані базового образу.

Корінь проблеми: DNS всередині билдера зламаний або вихід заблоковано.

Виправлення: Налаштуйте DNS демона, виправте політику мережі раннера або запускайте билдера з відомими DNS‑серверами. Перевіряйте зсередини контейнера билдера.

4) «x509: certificate signed by unknown authority» під час установки пакета або витягу образу

Симптоми: TLS‑помилки при зверненні до реєстрів або mirror‑сервісів, особливо у корпоративному середовищі.

Корінь проблеми: Перехоплення TLS / кастомний корпоративний CA не довірений у базовому образі або середовищі билдера.

Виправлення: Встановіть корпоративний CA в стадії збірки (і краще — в спільній базі). Не відключайте перевірку TLS як «виправлення», якщо вам не до вподоби інцидент‑реакція.

5) «failed to fetch oauth token» або 401/403 при витягу базових образів

Симптоми: Помилки автентифікації реєстру, часто періодичні при прострочених токенах.

Корінь проблеми: Відсутній docker login в CI, неправильний credential helper, прострочені токени або витяг з приватного реєстру без передачі облікових даних до віддаленого билдера.

Виправлення: Залогіньтесь перед збіркою; для віддалених buildx‑билдерів переконайтеся, що облікові дані доступні в контексті билдера. Підтвердіть через прямий docker pull в тому самому середовищі.

6) «no match for platform in manifest»

Симптоми: Збірка падає рано при резолюції базового образу.

Корінь проблеми: Базовий образ не опублікований для потрібної архітектури.

Виправлення: Використовуйте мульти‑арх образ, зафіксуйте правильну платформу або змініть ранери. Якщо ви будуєте на arm64, але потрібен amd64 — вкажіть це явно.

7) «exec format error» під час RUN

Симптоми: Базовий образ витягнуто, але виконання бінарів падає.

Корінь проблеми: Невідповідність архітектури в часі виконання (наприклад, amd64‑бінар на arm64) або відсутня налаштувана емульація QEMU.

Виправлення: Узгодьте базовий образ, бінарі і платформу. Якщо використовуєте емульацію — налаштуйте binfmt/qemu на хості, що запускає билдера.

8) «no space left on device» (інколи замасковано як помилки розпакування)

Симптоми: Випадкові збої при розпакуванні шарів, записі кешу або експорті образів.

Корінь проблеми: Сховище билдера повне (поширено при драйвері docker-container buildx), закінчення inode або обмеження overlayfs.

Виправлення: Очищуйте кеші, збільшіть диск та припиніть копіювати гігантські директорії на ранніх стадіях Dockerfile (це множить збережені шари).

9) «failed to create LLB definition» або дивні RPC‑помилки

Симптоми: BuildKit повідомляє gRPC/rpc помилки з «unknown desc».

Корінь проблеми: Нестабільність билдера, невідповідність версій, пошкоджений стан або ресурсний тиск (диск, пам’ять).

Виправлення: Перезапустіть билдера, оновіть BuildKit/buildx, перевірте ресурси і відтворіть з --progress=plain. Якщо після очищення кеша проблема зникла — це був тиск стану.

10) «secret not found» / помилки маунту SSH

Симптоми: RUN‑крок, що очікує секрет/ssh‑агент, падає одразу.

Корінь проблеми: Команда збірки не передала --secret або --ssh, або CI не експонує секрет.

Виправлення: Правильно підключіть секрет. Якщо не можете — змінить дизайн: отримаєте залежності поза образом або вендоруйте їх. Не комітьте секрети від розпачу.

Три корпоративні міні‑історії з полів

Міні‑історія 1: Інцидент через хибне припущення

Команда перенесла збірки зі self‑hosted GitLab‑раннера на керований флот раннерів. Той самий репозиторій, той самий Dockerfile, ті ж команди. Перша збірка впала з failed to solve на кроці COPY. Розробники знизали плечима: «Файл у репозиторії, не може бути відсутнім.»

Файл був згенерований. Локально в усіх він був тому, що їхній робочий процес запускав інструмент, який виробляв build/output/app. Старий CI‑раннер також мав попередній крок, що запускав той самий інструмент — але він жив у загальному шаблоні, який ніхто вже не пам’ятав. Новий CI‑пайплайн «очистили», щоб він був «простіший», що по‑корпоративному означало «ми видалили нудні частини, що робили все працюючим».

Хибне припущення було тонким: вони вважали, що Docker build бачить увесь стан репозиторію, включно з тим, що «з’явиться до моменту запуску збірки». Але контекст Docker build — це знімок файлової системи в момент виклику build. Якщо артефакт не там — неважливо, наскільки він «духовно присутній».

Виправлення — формалізувати створення артефакту: або окремий CI‑крок, що його виробляє перед зборкою образу, або багатостадійна Docker‑збірка, яка компілює всередині стадії билдера. Вони обрали багатостадійну. Спочатку повільніше, потім швидше після налаштування кеша, і найголовніше — детерміністично.

Постмортем‑урок: коли збірки ламаються після зміни раннерів, вважайте, що ваша залежність від середовища була недокументована. Зазвичай так і є.

Міні‑історія 2: «Оптимізація», що обернулася проти

Інша організація мала мету: скоротити час CI. Хтось помітив, що Docker‑збірки «повільні, бо надсилають занадто багато контексту», і вирішив стати хитрим. Додали агресивні правила в .dockerignore, виключивши фактично все, крім Dockerfile і кількох директорій. CI став швидшим. Аплодисменти.

Потім кандидат у реліз впав із failed to compute cache key: failed to calculate checksum на файлі в COPY — файлі, що існував, але тепер ігнорувався. Команда виправляла, розігнуздувала інші файли, нові збої з’являлися. Це перетворилося на whack‑a‑mole, бо правила ігнорування писали без картографії залежностей Dockerfile.

Наслідки були не лише у збоях. Це був час, витрачений на відновлення списку того, що образ насправді потребує, і тонкий ризик безпеки: розробники почали тимчасово «фіксити», копіюючи ., щоб збірка пройшла, випадково вкидаючи credentials і dev‑сміття в образи. Ось так у продакшні з’являється .env і відділ комплаєнсу у календарі.

Остаточне рішення було нудним і правильним: вони перебудували .dockerignore з першопринципів. Перелік файлів, що копіюються в образ, додали тільки ті директорії, які потрібні, і зробили COPY конкретним і стабільним. Контекст зменшився без порушення залежностей.

Урок: оптимізувати контекст без розуміння залежностей COPY — це наче знімати болти, щоб зробити міст легшим.

Міні‑історія 3: Нудна практика, що врятувала день

Платформна команда запустила кластер buildx билдера. Нічого екстраординарного: драйвер container, персистентне сховище і чітка політика. Кожен нода билдера експортував метрики дискового простору, inode і розмір кеша. Також регулярно чистили кеші за політикою з вікном утримання, налаштованим під навантаження.

В одну п’ятницю збірка продуктової команди почала випадково падати з «failed to solve»: інколи при розпакуванні шару, інколи при експорті кеша. Розробники думали «Docker глючить» і почали перезапускати джоби, поки не потрапляли на зелений.

Платформна команда подивилася на дашборди. Диски билдера повільно наповнювалися. Не через один проєкт, а через те, що кілька команд увімкнули експорт кеша й ніхто не поставив ліміти. Коли тиск на диск зріс, BuildKit почав падати по‑непривітному — бо фейли сховища рідко чемні.

Вони збільшили розмір тома билдера і затягнули політику prune. Збірки стабілізувалися одразу. Без драматичних героїв — просто хтось, хто відслідковує «нудні» сигнали дискового використання. Команда продукту зашила реліз. Реліз пройшов тихо. Кращого результату й бути не може.

Урок: диск билдера — це інфраструктура продакшена. Ставтеся до нього як до продакшен‑інфраструктури, інакше він так ставиться до вас.

Чеклісти / покроковий план

Покроково: від червоного CI до підтвердженого кореня проблеми

  1. Перезапустіть раз зі --progress=plain. Мета — ясність, а не надія.
  2. Ідентифікуйте фазу збою: internal/context, metadata fetch, COPY/ADD, RUN, export.
  3. Перевірте контекст збірки: підтвердіть наявність файлів; перевірте .dockerignore; виміряйте розмір контексту.
  4. Перевірте резолюцію базового образу: витягніть вручну; інспектуйте платформи; перевірте автентифікацію.
  5. Перевірте середовище билдера: який buildx builder, тип драйвера, DNS/proxy, тиск на диск.
  6. Зменшіть проблемний RUN‑команду: додайте set -eux, видаліть зчеплені команди, перезапустіть.
  7. Виключіть корупцію кеша: запустіть з --no-cache; якщо це вирішує — виправте стратегію кешування.
  8. Застосуйте найменше виправлення, що робить збірку детерміністичною. Детермінізм кращий за хитрість.

Чекліст: щоб помилки Dockerfile були менш ймовірні наступного тижня

  • Тримайте .dockerignore коротким, явним і перевіряйте разом із змінами Dockerfile.
  • Надавайте перевагу багатостадійним збіркам, щоб створення артефактів було частиною графа збірки, а не зовнішньою залежністю.
  • Фіксуйте базові образи на тегах, якими ви керуєте (і розгляньте digests для критичних пайплайнів).
  • Припиніть жити стилем «COPY . .». Копіюйте конкретні директорії.
  • Розділяйте ризикові RUN‑кроки. Один крок для завантаження пакетів, один для збірки, один для очищення.
  • Моніторте диск билдера. Встановіть політики prune. Документуйте конфігурацію билдера, як базу даних (бо він так себе поводить).
  • Обробляйте проксі та корпоративні CA явно. Якщо вони потрібні — зафіксуйте це кодом.
  • Використовуйте mounts для секретів; перевірте, що CI їх передає; ніколи не запікайте секрети в шарах.

Жарт №2: «Ми просто вимкнемо кеш, щоб виправити збірку» — CI‑еквівалент відключення пожежної сигналізації, щоб поспати.

Цікаві факти та історичний контекст

  • BuildKit починався як окремий проєкт і згодом став стандартним рушієм збірки для багатьох Docker‑інсталяцій, бо класичний рушій не масштабував кешування й паралелізм так добре.
  • Концепція “LLB” (Low‑Level Build) — це внутрішній формат графа збірки BuildKit; «solve» означає обчислення і виконання цього графа.
  • Оригінальний билд Docker виконував кроки послідовно; BuildKit ввів більше паралелізму і розумніший кеш, що також змінює прояв помилок.
  • .dockerignore існує, бо ранні Docker‑збірки були надзвичайно повільні через невмотивоване відправлення цілих репозиторіїв (включно з .git) як контексту.
  • Ліміти реєстрів стали реальною операційною проблемою зі зростанням використання CI; багато організацій дізналися про це лише коли пайплайни почали падати в години пік.
  • Мульти‑архітектурні образи стали масовими з появою ARM‑серверів і Apple Silicon; відповідно зросла кількість помилок невідповідності платформи.
  • Rootless Docker і захищені ранери підвищили безпеку, але зробили припущення про дозволи в старих Dockerfile частіше приводити до помилок.
  • Підтримка секретів у BuildKit значно покращилась через маунти, що зменшило потребу в ARG‑хаку, який витікав у шари.
  • Репродуковані збірки стали очікуванням у зв’язку з питаннями безпеки ланцюга постачання; «вона працює, якщо її перезапустити» вже не влаштовує.

Часті запитання

Чому Docker каже «failed to solve» замість реальної помилки?

Тому що BuildKit повідомляє загальне падіння для графа збірки («solve failed») і включає основну помилку вище. Використовуйте --progress=plain, щоб зробити основну помилку явно видимою.

Чи завжди «failed to solve» — це проблема Dockerfile?

Ні. Часто це мережа/DNS, автентифікація реєстру, контекст збірки або тиск на диск билдера. Вважайте Dockerfile винним тільки після того, як доведете, що середовище здорове.

Чому на моєму ноутбуці працює, а в CI — ні?

CI‑ранери мають інші мережеві політики, проксі, облікові дані, файлові дозволи і часто інший шлях контексту. Також CI може використовувати віддалений buildx‑билдер з власним середовищем.

Як швидко побачити, який крок фактично впав?

Запустіть збірку з docker buildx build --progress=plain і прокрутіть до першого ERROR‑блоку. Ігноруйте фінальний обгортковий рядок, поки не прочитаєте реальну помилку.

Який найшвидший спосіб виявити проблеми з контекстом збірки?

Перевірте .dockerignore, прогляньте рядки COPY/ADD і підтвердіть, що джерельні шляхи існують під директорією контексту. Якщо передача контексту сотні мегабайт чи більше — виправте це також.

Чи варто фіксувати базові образи по digest?

Якщо вам важлива відтворюваність і контроль ланцюга постачання — так, особливо для production‑пайплайнів. Якщо потрібні регулярні оновлення безпеки, керуйте оновленнями digests централізовано замість плаваючих тегів.

Як вирішити корпоративні проксі та CA під час збірки?

Передавайте проксі‑налаштування послідовно до билдера й кроків збірки, а також встановіть корпоративний CA в образі збірки (а іноді й у середовищі билдера). Не вимикайте перевірку TLS як тимчасове рішення.

Який правильний спосіб використовувати секрети під час docker build?

Використовуйте BuildKit‑маунти секретів (--secret та RUN --mount=type=secret). Переконайтеся, що CI передає секрет. Уникайте ARG/ENV для секретів, бо вони витікають у історію образу та шари.

Чому помилки, пов’язані з кешем, виглядають так дивно?

Тому що кеші BuildKit адресуються за вмістом і прив’язані до графа збірки. Коли посилане вмісту немає (ігнорований файл, відсутній маунт, пошкоджений кеш), з’являються помилки контрольних сум і ключів кешу.

Коли варто використовувати --no-cache?

Як діагностичний інструмент. Якщо це вирішує збірку — ви довели проблему зі станом кеша. Тоді виправте стратегію кешування або здоров’я билдера — не відмовляйтеся від кеша назавжди, якщо не любите повільний CI.

Висновок: кроки, що справді зменшують біль

«Failed to solve» — це не повідомлення, це категорія. Ваше завдання — зняти обгортку до першої конкретної помилки, тоді вирішити, чи маєте справу з контекстом, мережею, автентифікацією, платформою, дозволами, кешем чи часом виконання.

Зробіть ці три речі, і ваш майбутній ви надішле вам подяку (мовчки — не будити о 2:00):

  1. Стандартизуйте використання --progress=plain в логах CI (хоча б при помилці), щоб реальна помилка була видима.
  2. Зробіть контекст детермінованим: явні шляхи COPY, дисциплінований .dockerignore і багатостадійні збірки для згенерованих артефактів.
  3. Експлуатуйте свої билдери: моніторте диск, розумно очищайте кеші і ставтеся до інфраструктури збірки як до продакшенної — бо вона upstream для продакшену.

Більшість помилок Dockerfile «failed to solve» виправляються миттєво, коли ви перестаєте дивитися на останній рядок і починаєте трактувати збірки як системи: вхідні дані, стан і домени збоїв.

← Попередня
Стилізація форм для виробництва: інпути, select, чекбокси, радіо, перемикачі
Наступна →
WireGuard проти IPsec для офісів: що простіше в обслуговуванні та типові пастки

Залишити коментар