Docker «manifest unknown»: теги проти дайджестів — пояснення й виправлення

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

Ви робите деплой. Вчора все працювало. Сьогодні ваш pipeline падає з помилкою manifest unknown, і єдина «зміна»
в тому, що хтось десь оновив «latest», бо «та це ж просто тег». Тепер прод застряг у
ImagePullBackOff, канал інцидентів кипить, і настав час нарешті зрозуміти різницю між
зручним для людини ярликом і криптографічним вказівником.

Хороша новина: manifest unknown зазвичай не містить містики. Це реєстр, який каже: «Я не можу знайти
маніфест, про який ви питаєте». Погана новина: ви запитали його способом, який легко зробити неправильно. Зробимо це знову нудним.

Що насправді означає «manifest unknown»

У світі Docker/OCI «образ» — це не один бінарний блок. Це маніфест, який посилається на шари й конфігурацію, що зберігаються
в реєстрі. Коли ви виконуєте docker pull repo:tag, ваш клієнт запитує в реєстру маніфест,
що відповідає цьому посиланню.

«manifest unknown» — це спосіб реєстру сказати: «У мене немає маніфесту за цим ім’ям+посиланням.»
Це посилання може бути:

  • Тегом (tag) (repo:1.2.3)
  • Дайджестом (digest) (repo@sha256:…)

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

Типові реальні причини:

  • Того тегу ніколи не було в цьому реєстрі (описка, неправильний проект, неправильний регіон).
  • Тег був, але його видалили або перемістили (політика збереження, збірка сміття, ручне чищення).
  • Ви неправильно запушили мультиарх образ, тож тег вказує на інше, ніж очікує клієнт.
  • Ви тягнете з неправильного endpoint реєстру (proxy/cache/mirror не збігаються).
  • Авторизація дивна, і реєстр навмисно повертає «unknown», щоб не розкривати наявність артефактів.

Одна операційна істина, яку варто витатуювати собі в повіки:
теги — це вказівники, а не ідентичності. Якщо ви ставитеся до них як до ідентичностей, ваш пейджер вважатиме вас хобі-оператором.

Теги vs дайджести: правда й оманливість

Теги: зручні, змінні, легко порушуються

Docker-тег — це рядковий ярлик, асоційований із маніфестом у репозиторії. Реєстр зберігає відображення:
tag → manifest. І все. Немає гарантій, що відображення стабільне. Немає гарантій, що тег не буде перезаписано.
Немає гарантій, що ваш CI/CD не перетвориться на гонку записів.

Теги чудові для людей. Вони зручні для процесів типу «промотнути 1.2.3 у прод, перемістивши тег на prod».
Але вони жахливі як єдиний засіб контролю ланцюга поставок у проді.

Дайджести: нудні, незмінні, єдине, що можна довести

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

У практичних термінах:

  • Pull за тегом: «дай мені те, що наразі реєстр вважає за 1.2.3
  • Pull за дайджестом: «дай мені цей конкретний маніфест, без замін.»

Коли інцидент — «в стагу працює, а в проді — ні», перше, що я перевіряю — дрейф тегів. Друге — дрейф мультиарх. Третє —
чи хтось «оптимізував» джоб очищення реєстру.

Жарт №1: Теги — як етикетки в офісному холодильнику: корисні, поки хтось не вирішить «схоже, застаріло» і не викине ваш обід.

Що саме хешує дайджест?

Це важливо, бо люди плутаються між «дайджест шару» і «дайджесту маніфесту»:

  • Дайджест маніфесту: хеш JSON-документа маніфесту (посилання на шари/конфіг). Pull за дайджестом адресує саме його.
  • Дайджести шарів: хеші кожного стиснутого blob-шару. Ці шари діляться між образами.
  • Дайджест конфігу: хеш JSON-конфігурації образу (env, entrypoint, history, rootfs diff IDs тощо).

«manifest unknown» стосується того, що не знайдено маніфест (або manifest list) для вашого посилання. Це не скарга на відсутній шар — у випадку браку шарів ви побачите інші помилки (часто 404 на blob або помилки завантаження).

Маніфести, списки маніфестів і чому мультиарх ускладнює ситуацію

Одноплатформенні образи прості: тег вказує на маніфест для однієї платформи (наприклад linux/amd64). Мультиарх
образи додають ще один рівень: тег вказує на manifest list (OCI index / Docker manifest list),
який потім посилається на маніфести для кожної платформи.

Ваш клієнт робить content negotiation через заголовки і вибирає запис, що відповідає його платформі (архітектура, OS,
іноді варіант). Якщо в manifest list відсутній запис для платформи клієнта, ви можете бачити помилки на кшталт
«manifest unknown» або «no matching manifest», залежно від клієнта й реєстру.

Звична картина у 2026 році: команди будують на Arm-ноутбуках, пушать «latest», а прод-ноди (amd64) тягнуть і падають.
Ніхто не міняв код. Змінилася архітектура.

Чому proxy і mirror погіршують ситуацію

Кеші реєстру (pull-through caches, корпоративні mirror, artifact manager-и) можуть кешувати теги, маніфести або
blob-и з різними TTL. Якщо тег оновили зверху, але кеш застарів — або, ще гірше, частково оновився — ви можете отримати
«manifest unknown» від кешу, хоча зверху все гаразд. Або ви можете тягнути не звідти, звідки думали, і отримати помилку.

Цікаві факти та коротка історія (бо минуле все ще на виклику)

  • Факт 1: Docker Registry HTTP API v2 (той, яким користуються всі зараз) був запроваджений, щоб виправити проблеми масштабування й коректності v1, зокрема кращу адресацію вмісту.
  • Факт 2: OCI (Open Container Initiative) стандартизувала формат образів і специфікації дистрибуції після раннього вибуху екосистеми Docker, тож ви часто маєте справу з «OCI образами», навіть коли говорите «Docker image».
  • Факт 3: «Manifest list» у термінах Docker фактично — OCI «image index». Два імені, ті самі операційні головні болі.
  • Факт 4: Реєстри часто реалізують видалення і garbage collection як окремі кроки; ви можете видалити посилання тегу, але бінарні blob-и залишаться, або пізніше видалити blob-и й зламати старі посилання.
  • Факт 5: Деякі реєстри навмисно повертають 404/«unknown» для неавторизованих клієнтів, щоб не розкривати, які репозиторії/теги існують.
  • Факт 6: Мультиарх образи стали масовими, коли сервери й розробницькі машини на Arm стали поширеними; раніше багато команд ніколи не бачили manifest list у повсякденній роботі.
  • Факт 7: Kubernetes не «розуміє теги» більше, ніж просто передає їх рантайму; коли теги дрейфують, K8s граційно розгортає ваш хаос у масштабі.
  • Факт 8: Дайджест, який ви бачите як RepoDigests локально, прив’язаний до референту реєстру; той самий локальний образ може мати кілька repo digests, якщо його тягнули з кількох реєстрів/тегів згодом.
  • Факт 9: BuildKit і buildx зробили мультиплатформені збірки доступними, але також полегшили пуш часткових результатів, якщо ви не використовуєте правильні прапори.

Швидкий план діагностики

Мета — швидкість: підтвердити, чи маєте ви справу з (a) неправильним ім’ям/тегом, (b) відсутньою платформою в мультиархі, (c) проблемами з авторизацією/міррором,
або (d) політикою збереження/видалення в реєстрі.

Перше: підтвердьте точне посилання, яке ваша система намагається тягнути

  • Скопіюйте повний референс образу з логів: хост реєстру, шлях репозиторію, тег або дайджест.
  • Перевірте на «невидимі» помилки: неправильний проект, відсутній namespace, описка в тегі, випадковий верхній регістр.

Друге: спробуйте pull з чистого середовища (без кешованих облікових даних і образів)

  • Спробуйте docker pull з тимчасового раннера або робочої станції з відомою робочою мережею.
  • Якщо там працює, а на нодах — ні, підозрюйте proxy/mirror, різницю в авторизації або застарілий кеш.

Третє: вирішіть тег у дайджест і потім тягніть за дайджестом

  • Якщо pull за дайджестом працює, проблема в дрейфі або видаленні тега.
  • Якщо pull за дайджестом також не працює з «unknown», маніфест зник (або ви в неправильному реєстрі).

Четверте: перевірте відповідність платформи / мультиарх

  • Інспектуйте за допомогою docker buildx imagetools inspect.
  • Якщо потрібної платформи немає в списку, перебудуйте/запуште коректно.

П’яте: підтвердіть стан на боці реєстру й політики збереження

  • Перелічіть теги, якщо це підтримується; перегляньте аудиторські логи, якщо вони є.
  • Шукайте автоматичні джоби очищення, правила видалення «без тегів» або політики за часом, які могли з’їсти ваш тег.

Парафраз ідеї Вернера Фогельса (CTO Amazon): Якщо ви це збудували, ви й запускаєте. Якщо ви доставляєте образи, ви відповідаєте за те, як вони посилаються й зберігаються.

Практичні завдання: команди, виводи й рішення (12+)

Це реальні кроки оператора: виконуйте команду, інтерпретуйте вивід, приймайте рішення. Тримайте їх у своєму runbook.

Завдання 1: Відтворіть помилку в точності (і не «спрощуйте» ім’я образу)

cr0x@server:~$ docker pull registry.example.com/payments/api:1.8.4
Error response from daemon: manifest for registry.example.com/payments/api:1.8.4 not found: manifest unknown: manifest unknown

Що це означає: Реєстр відповів, і в нього немає маніфесту для цього repo+тега.
Рішення: Перевірте коректність референсу, потім перевірте, чи існує тег і на що він вказує.

Завдання 2: Перевірте, чи це описка в тегі, інспектуючи сусідні теги (де підтримується)

cr0x@server:~$ curl -s -u "$REG_USER:$REG_PASS" https://registry.example.com/v2/payments/api/tags/list | jq .
{
  "name": "payments/api",
  "tags": [
    "1.8.3",
    "1.8.4-hotfix1",
    "1.9.0",
    "latest"
  ]
}

Що це означає: 1.8.4 не існує, але 1.8.4-hotfix1 є.
Рішення: Виправте посилання в деплойменті. Якщо ви очікували 1.8.4, з’ясуйте, чому його ніколи не запушили.

Завдання 3: Вирішіть тег у дайджест клієнтськи за допомогою buildx imagetools

cr0x@server:~$ docker buildx imagetools inspect registry.example.com/payments/api:1.9.0
Name:      registry.example.com/payments/api:1.9.0
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
Manifests:
  Name: registry.example.com/payments/api:1.9.0@sha256:aa1c... (linux/amd64)
  Name: registry.example.com/payments/api:1.9.0@sha256:bb2d... (linux/arm64)

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

Завдання 4: Тягніть за дайджестом, щоб виключити дрейф тегу

cr0x@server:~$ docker pull registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
4c3b7d6b2a6d: Pulling from payments/api
Digest: sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
Status: Downloaded newer image for registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7

Що це означає: Вміст існує; проблема в тега відокремлена.
Рішення: Зафіксуйте маніфести/деплойменти через дайджест у проді або виправте процес промоції.

Завдання 5: Підтвердіть, що у вас локально є (RepoTags vs RepoDigests)

cr0x@server:~$ docker image inspect registry.example.com/payments/api:1.9.0 --format '{{json .RepoTags}} {{json .RepoDigests}}'
["registry.example.com/payments/api:1.9.0"] ["registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7"]

Що це означає: У вас є і змінний тег, і незмінне посилання на дайджест.
Рішення: Використовуйте дайджест у lockfile-ах, значеннях Helm і Kubernetes-манифестах для проду.

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

cr0x@server:~$ uname -m
aarch64

Що це означає: Ви на Arm64.
Рішення: Якщо образ має лише amd64 маніфести, ви отримаєте «no matching manifest» або «unknown». Інспектуйте індекс і перебудуйте як мультиарх.

Завдання 7: Інспектуйте manifest list і перевірте, чи є потрібна платформа

cr0x@server:~$ docker buildx imagetools inspect registry.example.com/analytics/worker:2.1.0
Name:      registry.example.com/analytics/worker:2.1.0
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:9b7f...
Manifests:
  Name: registry.example.com/analytics/worker:2.1.0@sha256:1c2a... (linux/amd64)

Що це означає: Опубліковано лише amd64.
Рішення: Або перебудуйте/запуште arm64, або примусьте планування на amd64 ноди, або припиніть вважати «працює на моєму ноуті» платформенною стратегією.

Завдання 8: Доведіть, що ви спілкуєтеся з тим реєстром, який думаєте

cr0x@server:~$ docker info | sed -n '1,25p'
Client:
 Version:    26.1.0
 Context:    default
 Debug Mode: false

Server:
 Containers: 12
  Running: 3
  Paused: 0
  Stopped: 9
 Registry Mirrors:
  https://mirror.corp.local
 Live Restore Enabled: false

Що це означає: Налаштовано mirror реєстру.
Рішення: Якщо pulls падають лише на певних хостах, порівняйте налаштування mirror; спробуйте обійти mirror для діагностики.

Завдання 9: Тимчасово обійдіть registry mirror, щоб підтвердити його застарілість

cr0x@server:~$ sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
cr0x@server:~$ sudo jq 'del(.["registry-mirrors"])' /etc/docker/daemon.json.bak | sudo tee /etc/docker/daemon.json >/dev/null
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ docker pull registry.example.com/payments/api:1.9.0
1.9.0: Pulling from payments/api
Digest: sha256:4c3b...
Status: Downloaded newer image for registry.example.com/payments/api:1.9.0

Що це означає: Mirror був проблемою (застарілий кеш, заблокований шлях, невідповідність авторизації).
Рішення: Виправте або очистіть кеш mirror; не залишайте ноди «тимчасово» обхідними, якщо вам подобаються несподівані рахунки за вихідний трафік.

Завдання 10: Перевірте події Kubernetes, коли помилка з’являється в кластерах

cr0x@server:~$ kubectl -n payments describe pod api-7f95b6d8dd-4n2qg | sed -n '/Events:/,$p'
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Pulling    2m50s                kubelet            Pulling image "registry.example.com/payments/api:1.8.4"
  Warning  Failed     2m49s                kubelet            Failed to pull image "registry.example.com/payments/api:1.8.4": rpc error: code = Unknown desc = failed to resolve reference "registry.example.com/payments/api:1.8.4": not found
  Warning  Failed     2m49s                kubelet            Error: ErrImagePull
  Warning  BackOff    2m35s                kubelet            Back-off pulling image "registry.example.com/payments/api:1.8.4"
  Warning  Failed     2m35s                kubelet            Error: ImagePullBackOff

Що це означає: Kubelet/рантайм не зміг вирішити референс. Це узгоджується з відсутнім тегом, проблемами auth/mirror або невідповідністю платформи.
Рішення: Перевірте референс образу та облікові дані, які використовує нода, а не ваш ноутбук.

Завдання 11: Перевірте рантайм ноди та його вид на референс образу

cr0x@server:~$ kubectl get node ip-10-1-2-3 -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}{"\n"}'
containerd://1.7.18

Що це означає: Ви використовуєте containerd, а не Docker Engine.
Рішення: Використовуйте інструменти crictl або інструменти containerd на ноді для глибшої діагностики pull; Docker CLI сам по собі може вводити в оману.

Завдання 12: Переконайтесь, що imagePullSecret присутній і правильний (помилки auth можуть маскуватись)

cr0x@server:~$ kubectl -n payments get secret regcred -o jsonpath='{.type}{"\n"}'
kubernetes.io/dockerconfigjson

Що це означає: Секрет існує і має правильний тип.
Рішення: Якщо все ще падає, розкодуйте його і підтвердіть, що він вказує на той самий хост реєстру, з якого ви тягнете.

Завдання 13: Розкодуйте dockerconfigjson і перевірте, чи hostname реєстру збігається

cr0x@server:~$ kubectl -n payments get secret regcred -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq -r '.auths | keys[]'
https://registry.example.com
https://registry-old.example.com

Що це означає: Існують кілька записів авторизації, включаючи старий endpoint.
Рішення: Приберіть застарілі записи; переконайтесь, що точний hostname у посиланні образу має облікові дані.

Завдання 14: Використайте skopeo для віддаленої інспекції без pull (добре для CI раннерів)

cr0x@server:~$ skopeo inspect --creds "$REG_USER:$REG_PASS" docker://registry.example.com/payments/api:1.9.0 | jq -r '.Digest,.Architecture,.Os'
sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
amd64
linux

Що це означає: Тег вирішується, і ви бачите дайджест та основну архітектуру із віддаленого маніфесту.
Рішення: Якщо skopeo бачить образ, а ваші ноди — ні, підозрюйте мережевий шлях, mirror або облікові дані ноди.

Завдання 15: Інспектуйте відповіді реєстру напряму (підтвердіть 404 vs 401)

cr0x@server:~$ curl -s -o /dev/null -w "%{http_code}\n" -u "$REG_USER:$REG_PASS" \
  -H 'Accept: application/vnd.oci.image.manifest.v1+json' \
  https://registry.example.com/v2/payments/api/manifests/1.8.4
404

Що це означає: Реєстр повертає 404 для маніфесту цього тега. Це узгоджується з тим, що «тег не існує» або «видалено».
Рішення: Перестаньте дебагувати ноди. Виправте референс або відновіть тег/маніфест у реєстрі.

Завдання 16: Доведіть, що політика збереження видалила тег (шукайте «untagged» образи)

cr0x@server:~$ skopeo inspect --creds "$REG_USER:$REG_PASS" docker://registry.example.com/payments/api:1.8.4-hotfix1 | jq -r '.Created,.Digest'
2026-01-02T17:11:09Z
sha256:11aa22bb33cc44dd55ee66ff77889900aabbccddeeff00112233445566778899

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

Завдання 17: Правильно пуште мультиарх образи (щоб уникнути часткових пушів)

cr0x@server:~$ docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/analytics/worker:2.1.0 \
  -t registry.example.com/analytics/worker:2.1 \
  --push .
[+] Building 214.3s (31/31) FINISHED
 => pushing manifest for registry.example.com/analytics/worker:2.1.0
 => pushing manifest for registry.example.com/analytics/worker:2.1

Що це означає: Ви запушили коректний мультиарх індекс. Тег має вирішуватися для обох платформ.
Рішення: Перевірте через imagetools inspect. Якщо з’являється лише одна платформа, ваш билдер, ймовірно, не мав QEMU/binfmt або збірка для однієї платформи провалилася.

Завдання 18: Зафіксуйте в Kubernetes по дайджесту (хід «зупинити кровотечу»)

cr0x@server:~$ kubectl -n payments set image deploy/api api=registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
deployment.apps/api image updated

Що це означає: Деплоймент тепер посилається на незмінний вміст.
Рішення: Робіть так для production-ворклоадів. Тримайте теги для людських потоків, але розгортайте по дайджестах.

Три корпоративні міні-історії з передової

Міні-історія 1: Інцидент через неправильне припущення (теги — це версії)

Середня фінтех-компанія мала «просте» правило: staging розгортання використовували :latest, production — семвер тег
на кшталт :2.6.1. Звучало зріло. Виглядало зріло в PowerPoint. Потім дрібний реліз перетворився на інцидент у п’ятницю ввечері.

Release engineer перебудував 2.6.1, додавши останнє оновлення CA bundle. Замість вирізати
2.6.2, вони перепризначили тег і запушили 2.6.1 знову. Ніхто не думав, що це має значення. «Та ж сама збірка», казали вони.
Їхній реєстр дозволяв перезапис тегу, і політик, що забороняють це, не було.

Прод виконували rolling update по кількох кластерах. Деякі ноди потягли оригінальний 2.6.1. Деякі — новий 2.6.1.
Міграції додатку виконалися один раз, але поведінка додатку відрізнялася через змінені налаштування TLS. Половина флету спілкувалася з upstream-залежністю; інша половина — падала. Команда на виклику дві години розслідувала «випадкові» збої, поки хтось не порівняв RepoDigests між нодами.

Виправлення було нудним і ефективним: прод-деплоя зафіксували по дайджесту, а релізний процес став трактувати теги як
незмінні. Якщо потрібна нова збірка — вирізайте новий тег. Без винятків. Зміна культури була більшою за технічну зміну:
«версія» тепер означала «артефакт, підтверджений дайджестом», а не «рядок, що схожий на версію».

Міні-історія 2: Оптимізація, що відкотилася (агресивне очищення)

В ентерпрайз SaaS компанії рахунок за зберігання реєстру зростав. Хтось запропонував політику очищення:
видаляти безтегові маніфести старші за 14 днів і агресивно збирати сміття blob-ів, щоб звільнити місце. Це подали як оптимізацію витрат без недоліків. Це мало бути вашим першим натяком.

Їхня стратегія деплойменту використовувала «промоцію через ретегування»: збірка :commit-abc123, тестування, потім ретегування того самого
дайджесту як :prod. Але CI іноді створював проміжні теги й потім видаляв їх. Деякий час маніфест був тимчасово «без тегу», перш ніж його знову тегнули під час промоції.

Джоб очищення спрацював у вікні. Він видалив маніфести, які вважав безтеговими й придатними до видалення. Пізніше промоція намагалася ретегнути дайджест, якого більше не було. Результат: manifest unknown для :prod посеред деплойменту. Так ви дізнаєтесь, що дефініція «untagged» не тотожна «unused».

Вони виправили це, розділивши обов’язки: не видаляти недавній безтеговий вміст, виконувати промоцію атомарно, коли можливо, і уникати запуску джобів очищення в робочий час. Також команда реєстру почала розглядати «storage» як залежність надійності, а не рядок для економії засобів за допомогою cron.

Міні-історія 3: Нудна, але правильна практика, що врятувала (пінування дайджестом + атестації артефактів)

Регульована медична компанія мала правило: кожен production-деплой має посилатися на образи по дайджесту, і дайджест повинен бути занотований у тікеті зміни. Люди нарікали. Здавалося бюрократією. Це уповільнювало «просто шипай» — і саме тому воно працювало.

Одного дня провайдер реєстру мав затримку реплікації між регіонами. Теги були видимі в одному регіоні раніше, ніж підставні маніфести повністю реплікувалися в інший. Кілька команд бачили переривчасті manifest unknown
при pull-ах у середовищі аварійного відновлення.

Команда на виклику не сперечалася, чи :3.4.7 — «правильна версія». У них уже був дайджест із останнього погодженого деплою. Вони витягли за дайджестом з регіону, де він існував, внутрішньо замірили його, і оновили DR-манифести, щоб посилатися на внутрішню копію по дайджесту. Системи відновилися з мінімальною плутаниною.

Не було ніякої героїки. Просто журнал: «цей дайджест — те, що ми запускаємо», що перетворило туманну проблему реєстру на звичайну проблему реплікації. Пізніше compliance ставив питання; відповіді вже були записані.

Поширені помилки: симптом → причина → виправлення

1) Симптом: «manifest unknown» для тега, який «точно існує»

  • Причина: Ви тягнете з іншого хоста/простору реєстру, ніж той, що перевіряли. Mirrors і схожі назви репозиторіїв люблять цю фішку.
  • Виправлення: Порівняйте повний референс образу в логах. Перевірте docker info на предмет mirror-ів. Верифікуйте через skopeo inspect проти точного хоста.

2) Симптом: Працює на ноуті, падає на нодах

  • Причина: Невідповідність платформи (arm64 vs amd64) або облікові дані рантайму ноди відрізняються від розробницьких.
  • Виправлення: Перевірте uname -m на нодах; інспектуйте платформи в manifest list; перевірте imagePullSecrets і IAM/credential helper-и нод.

3) Симптом: «manifest unknown» одразу після push

  • Причина: Eventual consistency/затримка реплікації, або ви пушили в один регіон, а тягнете з іншого, або кеш/mirror застаріли.
  • Виправлення: Тягніть з того самого endpoint, куди пушили; обійдіть mirror; повторіть з бекофом; після підтвердження — зафіксуйте по дайджесту.

4) Симптом: Тег зник за ніч

  • Причина: Політика збереження видалила теги/маніфести, або хтось запускав GC без розуміння залежностей.
  • Виправлення: Налаштуйте політики збереження; захистіть релізні теги; тримайте «золотий» внутрішній mirror; зберігайте дайджести в метаданих релізу.

5) Симптом: Kubernetes показує not found, але UI реєстру показує тег

  • Причина: UI може показувати метадані тегу з іншого бекенду або кешоване представлення; kubelet тягне з іншого endpoint або через проксі.
  • Виправлення: Тягніть з тієї ж мережної траси, що й нода; перевірте налаштування daemon mirror-ів; робіть прямі API-запити до реєстру з ноди.

6) Симптом: Після «multi-arch push» працює тільки одна архітектура

  • Причина: Ви запушили лише одну платформу, або запушили індекс без потрібної платформи через збій збірки/відсутність QEMU.
  • Виправлення: Використовуйте docker buildx build --platform ... --push і перевірте через imagetools inspect перед маркуванням як реліз.

7) Симптом: Випадкові ноди падають, випадкові — працюють

  • Причина: Змішані архітектури нод, різні конфіги mirror-ів або різні облікові дані (деякі ноди бачать приватні теги, інші — ні).
  • Виправлення: Уніфікуйте bootstrap нод; аудит конфігів daemon-ів; забезпечте консистентність секретів/credential provider-ів між пул-ами нод.

Жарт №2: «manifest unknown» — це ввічливий спосіб реєстру сказати «я не маю уявлення, про що ви», що також те, як аудитори ставляться до «latest».

Контрольні списки / покроковий план

Покроково: спочатку зупиніть outage

  1. Зафіксуйте точний фейлінг-референс з логів (включно з hostname реєстру та namespace).
  2. Спробуйте прямий pull з чистого хоста в тій же мережній траси, що й фейлячі ноди.
  3. Вирішіть до дайджесту за допомогою docker buildx imagetools inspect або skopeo inspect.
  4. Розгорніть по дайджесту (тимчасово або постійно). Це обминає дрейф тегів і дає вам час.
  5. Якщо дайджест теж відсутній, знайдіть останній відомий добрий дайджест у CI-логах, SBOM/атестаціях або історії деплойментів.
  6. Якщо невідповідність мультиарх, заплануйте на сумісні ноди або опублікуйте відсутню платформу негайно.

Покроково: виправте систему, щоб це не повторилось

  1. Зробіть релізні теги незмінними (політика). Якщо реєстр це підтримує — застосуйте. Якщо ні — забезпечте через CI-guard-и.
  2. Зберігайте дайджести як першокласні метадані релізу (в Git, метаданих артефактів, манифестах деплойментів, записах змін).
  3. Використовуйте теги для людей, дайджести — для машин. Промоція може використовувати теги, але прод має запускатися по дайджестах.
  4. Перевіряйте мультиарх результати в CI перед маркуванням збірки як релізної.
  5. Вирівняйте retention з реальністю релізів: захищайте релізні теги, відкладіть видалення безтегових артефактів, не запускайте GC агресивно без розуміння патернів посилань.
  6. Уніфікуйте конфігурацію mirror-ів і моніторьте їх як залежність проду. Якщо вони можуть повертати застарілі маніфести — це джерело відмов.

Чекліст: перед тим як звинувачувати Docker

  • Чи правильно написано тег і чи в правильному шляху репозиторію?
  • Чи тягнете ви з того ж endpoint реєстру, куди пушить ваш CI?
  • Чи за вами стоїть mirror, і чи він консистентний між нодами?
  • Чи присутня потрібна платформа в manifest list?
  • Чи зафіксовано референс по дайджесту для проду?
  • Чи могла політика retention/GC видалити або осиротити маніфест?
  • Чи збігаються облікові дані нод з тими, що використовує CI/розробник?

FAQ

1) Чи завжди «manifest unknown» означає відсутній тег?

Ні. Це «відсутній маніфест для референсу, який ви запросили». Це може бути відсутній тег, видалений дайджест, неправильний шлях репозиторію або ситуація з auth/mirror, яка ховає існування.

2) Чому Docker іноді каже «manifest unknown» замість «unauthorized»?

Деякі реєстри навмисно повертають 404/unknown для неавторизованих клієнтів, щоб не розкривати, які репозиторії/теги існують. Завжди перевіряйте облікові дані на тій самій машині/рантаймі, який фейлить.

3) У чому різниця між RepoTags і RepoDigests?

RepoTags — це імена, які ви використовували для посилання на образ локально (змінні). RepoDigests — це
прив’язані до реєстру посилання на вміст (незмінні щодо вмісту). Використовуйте дайджести для деплойментів.

4) Якщо дайджести незмінні, чому я все ще бачу помилки при pull за дайджестом?

Тому що незмінність не гарантує доступності. Якщо реєстр видалив маніфест (або ви запитуєте інший реєстр/регіон), дайджест може бути «unknown». Незмінний ≠ безсмертний.

5) Чи припинити взагалі використовувати теги?

Ні. Теги корисні для людей і процесів: «це release-2.1», «це prod», «це canary». Просто не дозволяйте коректності проду залежати від змінного вказівника. Деплойте по дайджесту, маркуйте тегами.

6) Як переконатися, що мультиарх образи включають і amd64, і arm64?

Будуйте з docker buildx build --platform ... --push, потім перевіряйте за допомогою
docker buildx imagetools inspect. Зробіть верифікацію частиною CI-шлюзу, а не надією.

7) Чому працює, коли я обминаю registry mirror?

Ваш mirror застарів, неправильно налаштований або не має облікових даних для приватних репозиторіїв. Mirrors — це системи, а не чарівний пил продуктивності. Моніторьте їх, версіонуйте конфіги й тестуйте відмовостійкість.

8) Kubernetes показує «not found», але я можу підтягти вручну з ноутбука. Чому?

Різні облікові дані, різний DNS, різний проксі/mirror, різна архітектура. Ноди Kubernetes тягнуть як нода-рантайм, а не як ви. Відтворіть проблему з мережної траси та контексту рантайму ноди.

9) Який найшвидший «фікс» під час інциденту?

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

10) Чи валідна стратегія промоції через ретегування?

Так, якщо ви робите це дисципліновано: теги, що репрезентують середовища, можуть переміщуватися, але релізний артефакт має зберігатись записаним дайджестом. Також захистіть релізні теги від перезапису, або ви рано чи пізно випустите дві різні «однакові версії».

Висновок: що змінити в понеділок

«manifest unknown» — це не містичне прокляття Docker. Це помилка пошуку вмісту, зазвичай спричинена тим, що люди використовують теги як ідентичності, або системи трактують видалення й кешування як нешкідливе прибирання.

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

  • Розгортайте по дайджесту в проді. Тримайте теги, але не покладайтеся на них для коректності.
  • Зробіть релізні теги незмінними політично й технічно.
  • Перевіряйте мультиарх образи в CI і відхиляйте збірки, що не публікують потрібні платформи.
  • Аудитуйте mirror-и та політики retention як залежності проду — бо вони такими і є.
  • Записуйте дайджест під час релізу. Майбутній ви буде вдячний за відсутність здогадок.
← Попередня
Debian 13: TRIM не виконується — увімкніть таймер fstrim і перевірте, що він працює
Наступна →
Підводний камінь mount propagation в Docker: чому ваш bind‑mount виглядає порожнім (і як виправити)

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