JavaRush /Курси /Docker for Spring /Layertools і jarmode=tools...

Layertools і jarmode=tools

Docker for Spring
Рівень 8 , Лекція 3
Відкрита

1. Старі приклади та канон курсу

Уявіть, що ви шукаєте рецепт борщу, а вам трапляється «класичний рецепт за книгою 1963 року». Загалом борщ — усе ще борщ, але там може бути дивина: «візьміть два кілограми буряків і дістаньте керогаз». Історія з layertools дуже схожа: ідея правильна, а синтаксис подекуди вже історичний.

layertools — це стара назва jarmode, яку Spring Boot використовував у прикладах для витягування шарів із executable jar. Дуже багато статей, доповідей і репозиторіїв писалися тоді, коли саме -Djarmode=layertools був «де-факто стандартом». Люди публікують Dockerfile, компанії копіюють їх у шаблони, потім ці шаблони живуть роками… і зрештою ви, студент Boot 4.0.3, бачите це слово та думаєте: «А в нас узагалі той самий Spring Boot?»

У нашому курсі ми фіксуємо канон: Boot 4.x → jarmode=tools. Причина не в тому, що layertools «поганий» як концепція, а в тому, що проєкту потрібен один стабільний робочий шлях. Новачку дуже важко одночасно тримати в голові два синтаксиси, два набори команд і два типи помилок. У результаті замість розуміння виходить колекція «магічних рядків, які іноді працюють».

Корисна думка тут така: старі приклади цінні як пояснення ідеї, але не як буквальний шаблон. І зараз ми зробимо собі «перекладач» зі старого синтаксису на сучасний.

Маркери старих прикладів

Коли ви читаєте чужий Dockerfile або статтю, мозок новачка зазвичай чіпляється за команди «як є» і намагається просто повторити їх. Це майже завжди закінчується тим, що ви повторили команду, отримали іншу помилку, ніж автор статті, і подумали, що «в мене щось не так із Docker». Насправді «не так» часто лише одне: приклад з іншого часовоґо шару.

Нижче — невеликий орієнтир. Це не «все підряд», а найчастіші маркери, які трапляються в реальності, коли ви шукаєте в Google layered images для Spring Boot.

Що ви бачите у статті/репозиторії Чому це маркер застарілого контексту Як перекладаємо в межах курсу (Boot 4.0.3, Gradle)
-Djarmode=layertools Історичний jar-mode для витягування шарів Міняємо на -Djarmode=tools
list У старих прикладах список шарів міг називатися інакше У нашому каноні використовуємо list-layers
extract --destination ... У старих режимах команда часто працювала з шарами за замовчуванням Пишемо явно extract --layers --destination layers (щоб Dockerfile був самоочевидним)
ARG JAR_FILE=target/*.jar Це Maven-вивід (target/), а не Gradle Міняємо на ARG JAR_FILE=build/libs/*.jar
mvn package Збірку проєкту показано в Maven-треці У курсі тримаємо Gradle-only шлях (усередині образу або зовні — залежно від стадії)
ENTRYPOINT ["java","org.springframework .boot.loader.JarLauncher"] Часто трапляється в прикладах Boot 2.x У сучасних версіях частіше потрібен org.springframework .boot.loader.launch.JarLauncher (про це нижче), але ідея залишається та сама
openjdk:8... Приклад писався під інший Java baseline У курсі тримаємо Java 25 baseline і той runtime image policy, який ви вже вибрали в Дні 7

Найважливіший висновок із таблиці такий: в інтернеті «застарілість» найчастіше видно не за одним рядком, а за зв’язкою рядків. Якщо ви побачили layertools, майже напевно поруч будуть і target/*.jar, і старий entrypoint-клас, і іноді навіть підхід «копіюємо jar, а потім запускаємо jar» — у стилі, який у layered-підході вже не підходить.

2. Переклад команд: layertoolstools

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

Почнемо з найтиповішого рядка, який ви побачите в блогах:

# Legacy-варіант зі старих статей: використовує jarmode=layertools
java -Djarmode=layertools -jar app.jar extract --destination extracted

У нашому курсі та сама думка має бути записана так:

# Сучасний варіант для Boot 4.x: jarmode=tools + явне витягування шарів
java -Djarmode=tools -jar app.jar extract --layers --destination layers

Тут важливі дві зміни. Перша — очевидна: layertools замінили на tools. Друга — менш очевидна: ми додали --layers, щоб команда читалася однозначно навіть через пів року, коли ви вже не пам’ятаєте, які параметри були “за замовчуванням” у конкретній версії.

Якщо ви хочете не просто «виконати extract», а спочатку переконатися, які шари взагалі є, — а це інженерно доросла звичка, — то в старих статтях могло бути щось на кшталт списку через list. Наш шлях виглядає так:

# У курсі використовуємо Gradle-артефакт із build/libs, а не Maven target/
java -Djarmode=tools -jar build/libs/*.jar list-layers

# Очікувано побачите список стандартних шарів Spring Boot:
# dependencies
# spring-boot-loader
# snapshot-dependencies
# application

Зверніть увагу на build/libs/*.jar: це не педантичність, а дисципліна. Коли ви переносите приклад з інтернету, ви майже завжди маєте «приземлити» його у вашу реальність. У нас реальність — Gradle і bootJar, а отже артефакт живе в build/libs.

І ще один шматок, який допомагає зробити Dockerfile «читабельним для людини», а не лише для комп’ютера. У Dockerfile ми майже завжди перейменовуємо jar у стабільне ім’я, наприклад application.jar, щоб не залежати від версії в назві файла.

# Указуємо шлях до jar у Gradle-проєкті
ARG JAR_FILE=build/libs/*.jar

# Копіюємо jar під стабільною назвою, щоб не залежати від версії в назві файла
COPY ${JAR_FILE} application.jar

Якщо ви цього не зробите, то на першому ж кроці, коли версія зміниться з 0.0.1-SNAPSHOT на 0.0.2-SNAPSHOT, ваш Dockerfile може перетворитися на пошук правильної назви jar-файла.

3. Переклад фрагмента Dockerfile

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

Ось як часто виглядає історичний фрагмент. Тут навмисно залишено Maven-шлях, щоб було видно типову зв’язку:

FROM eclipse-temurin:17-jre AS builder
WORKDIR /app

# Ознака застарілого прикладу: Maven-шлях target/*.jar
ARG JAR_FILE=target/*.jar

# Часто jar у прикладах перейменовують на application.jar для стабільності
COPY ${JAR_FILE} application.jar

# Застарілий режим: jarmode=layertools і extract без явного --layers
RUN java -Djarmode=layertools -jar application.jar extract --destination extracted

Якщо ви переносите його в наш курс, то робите два види змін: синтаксичні (jarmode, команди) і контекстні (шлях до jar, базовий образ за версією Java). Виходить більш чесний, придатний для Boot 4.0.3 варіант:

FROM eclipse-temurin:25-jre-jammy AS extract
WORKDIR /build

# У курсі використовуємо Gradle-вивід: build/libs/*.jar
ARG JAR_FILE=build/libs/*.jar

# Перейменування jar на стабільне ім'я — щоб Dockerfile не залежав від версії
COPY ${JAR_FILE} application.jar

# Сучасний режим: jarmode=tools і явне витягування шарів у фіксовану папку
RUN java -Djarmode=tools -jar application.jar extract --layers --destination layers

Це поки що лише extract-крок, але вже тут видно головне: ми перекладаємо не тільки layertools -> tools, ми перекладаємо весь робочий контекст на наш baseline.

Коли у старій статті після extract починається runtime-фрагмент, перевіряйте ще одну річ: назву launcher-класу. Після extraction контейнер уже працює з каталогами шарів, тому застосунок зазвичай стартує через JarLauncher, а не через початковий app.jar. Для Boot 4.x орієнтиром служить org.springframework.boot.loader.launch.JarLauncher; стара назва класу з legacy-прикладу легко дає Could not find or load main class, навіть якщо самі шари витягнулися правильно.

Тобто переклад старого прикладу — це зазвичай три правки одночасно: jarmode, шлях до jar і launcher-клас, якщо він узагалі фігурує в runtime-фрагменті. Якщо виправити лише одне, ви отримаєте «майже правильний» Dockerfile, який падає в найобразливішому місці.

4. Один канон у проєкті

У певний момент у студента з’являється дуже людське бажання: «А давайте залишимо і layertools, і tools — раптом знадобиться». Це звучить турботливо, але на практиці працює як шафа, у якій лежать чотири однакові чорні футболки, і ви все одно не можете вибрати, яку вдягнути. У контейнеризації, особливо навчальній, це перетворюється на постійну плутанину: що реально використовується, що застаріло, що «треба», а що «просто лежить».

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

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

Ось мінісхема, яку корисно тримати в голові, коли ви приносите шматок чужого Dockerfile у свій проєкт:

flowchart TD
    A["Знайшли приклад в інтернеті"] --> B{"Є `-Djarmode=layertools`?"}
    B -->|Так| C["Вважаємо це застарілим синтаксисом"]
    B -->|Ні| D["Перевіряємо інші ознаки (target/, старий launcher)"]
    C --> E["Перекладаємо на `-Djarmode=tools`"]
    E --> F["Нормалізуємо шлях до jar: build/libs/*.jar"]
    F --> G["Нормалізуємо extract: --layers --destination layers"]
    G --> H["Перевіряємо entrypoint (JarLauncher) під поточний Boot"]
    H --> I["У репозиторій потрапляє лише сучасний варіант"]

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

5. Типові помилки під час роботи з jarmode=tools

Ця тема виглядає простою рівно до того моменту, поки ви не копіюєте перший фрагмент зі статті, і він не падає у вашому середовищі. І це нормально: ми вчимося не «вгадувати», а діагностувати. Нижче — типові помилки, які трапляються майже в кожного, хто вперше стикається з layertools і намагається поєднати це з Boot 4.x.

Помилка № 1: сліпа заміна layertools на tools без перекладу команди цілком.
Іноді студент бачить у статті -Djarmode=layertools і просто міняє його на tools, але залишає навколо стару команду, старі прапорці й стару логіку. У результаті команда може відпрацювати не так, як ви очікували, або не відпрацювати взагалі. Лікується це дуже нудно, але ефективно: перекладайте не слово, а весь фрагмент, включно з list/list-layers, extract, --layers і --destination.

Помилка № 2: забули, що в курсі Gradle, і залишили target/*.jar.
Це одна з найобразливіших ситуацій, тому що Docker чесно покаже COPY failed: file not found, а ви почнете підозрювати, що «Docker не бачить мій файл». Docker усе бачить. Просто target/ — це Maven, а у вас артефакт лежить у build/libs/. Така помилка особливо часто трапляється, коли людина паралельно дивиться два туторіали й не помічає, що вони про різні build tool'и.

Помилка № 3: дістали layertools зі старої статті й тепер намагаєтеся тримати в проєкті два синтаксиси «про всяк випадок».
На папері це виглядає як «страховка». У реальності це перетворює Dockerfile і README на шум: який варіант правильний, який підтримується, який актуальний? Через тиждень ви самі почнете плутатися. Правильна звичка — тримати в репозиторії один канон, а історичний варіант залишити лише для розуміння того, що трапляється «в дикій природі».

Помилка № 4: витягування шарів робить результат непередбачуваним, тому що немає --destination.
Коли extract пише файли «десь поруч», Dockerfile швидко починає залежати від поточної робочої директорії та випадкових шляхів. У навчальному проєкті краще бути нудною людиною: завжди задавати --destination layers (або інший, але той самий каталог) і завжди задавати WORKDIR перед extract. Тоді наступні COPY --from=... будуть не «вгадайкою», а прямим посиланням на структуру.

Помилка № 5: неправильний JarLauncher у ENTRYPOINT.
Старі приклади іноді використовують org.springframework.boot.loader.JarLauncher, сучасні частіше використовують org.springframework.boot.loader.launch.JarLauncher. Якщо ви скопіювали entrypoint і отримали Could not find or load main class, це майже ніколи не означає, що «в мене шари не так витягнулися». Найчастіше це просто невідповідність класу launcherʼа вашій версії Boot. Лікується це тим, що ви тримаєте єдиний курс-бейзлайн (Boot 4.0.3) і перекладаєте приклад повністю під нього.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ