0
# Layer Support for Docker Optimization
1
2
Advanced layering system for optimizing Docker image builds by strategically separating application code, dependencies, and Spring Boot loader components into distinct layers. This enables efficient caching and faster container image builds.
3
4
## Capabilities
5
6
### Layer Definition
7
8
Core class representing a named layer for organizing archive content.
9
10
```java { .api }
11
public class Layer {
12
/**
13
* Create a layer with the specified name.
14
*
15
* @param name the layer name
16
*/
17
public Layer(String name);
18
19
/**
20
* Check if this layer equals another object.
21
*
22
* @param obj the object to compare
23
* @return true if equal
24
*/
25
@Override
26
public boolean equals(Object obj);
27
28
/**
29
* Get the hash code for this layer.
30
*
31
* @return the hash code
32
*/
33
@Override
34
public int hashCode();
35
36
/**
37
* Get the string representation of this layer.
38
*
39
* @return the layer name
40
*/
41
@Override
42
public String toString();
43
}
44
```
45
46
### Layer Interface
47
48
Primary interface for providing layer information during the packaging process.
49
50
```java { .api }
51
public interface Layers extends Iterable<Layer> {
52
/**
53
* Get an iterator over all layers.
54
*
55
* @return iterator for layers
56
*/
57
@Override
58
Iterator<Layer> iterator();
59
60
/**
61
* Get a stream of all layers.
62
*
63
* @return stream of layers
64
*/
65
default Stream<Layer> stream() {
66
return StreamSupport.stream(spliterator(), false);
67
}
68
69
/**
70
* Get the layer for an application resource.
71
*
72
* @param applicationResource the application resource path
73
* @return the layer for this resource
74
*/
75
Layer getLayer(String applicationResource);
76
77
/**
78
* Get the layer for a library.
79
*
80
* @param library the library
81
* @return the layer for this library
82
*/
83
Layer getLayer(Library library);
84
85
/**
86
* Implicit layers implementation that uses default layer assignment.
87
*/
88
Layers IMPLICIT = /* default implementation */;
89
}
90
```
91
92
### Layer Index
93
94
Index file that describes which layer each entry belongs to, used by Docker image builders.
95
96
```java { .api }
97
public class LayersIndex {
98
/**
99
* Create a layers index with the specified layers.
100
*
101
* @param layers the layers to include in the index
102
*/
103
public LayersIndex(Layer... layers);
104
105
/**
106
* Create a layers index from an iterable of layers.
107
*
108
* @param layers the layers to include
109
*/
110
public LayersIndex(Iterable<Layer> layers);
111
112
/**
113
* Add an entry to the specified layer.
114
*
115
* @param layer the layer to add the entry to
116
* @param name the entry name/path
117
*/
118
public void add(Layer layer, String name);
119
120
/**
121
* Write the index to an output stream.
122
* The index format is compatible with Spring Boot's layered JAR structure.
123
*
124
* @param out the output stream to write to
125
* @throws IOException if writing fails
126
*/
127
public void writeTo(OutputStream out) throws IOException;
128
}
129
```
130
131
### Standard Layers
132
133
Pre-defined layer implementation following Spring Boot conventions for optimal Docker caching.
134
135
```java { .api }
136
public abstract class StandardLayers implements Layers {
137
/**
138
* Layer for external dependencies.
139
* Changes infrequently, provides good caching.
140
*/
141
public static final Layer DEPENDENCIES = new Layer("dependencies");
142
143
/**
144
* Layer for Spring Boot loader classes.
145
* Changes only with Spring Boot version updates.
146
*/
147
public static final Layer SPRING_BOOT_LOADER = new Layer("spring-boot-loader");
148
149
/**
150
* Layer for snapshot dependencies.
151
* Separated from stable dependencies for better cache efficiency.
152
*/
153
public static final Layer SNAPSHOT_DEPENDENCIES = new Layer("snapshot-dependencies");
154
155
/**
156
* Layer for application code.
157
* Changes frequently, placed in top layer.
158
*/
159
public static final Layer APPLICATION = new Layer("application");
160
}
161
```
162
163
### Implicit Layer Resolver
164
165
Default implementation that automatically assigns layers based on content analysis.
166
167
```java { .api }
168
public class ImplicitLayerResolver extends StandardLayers {
169
// Automatically determines appropriate layer for each component
170
// Uses heuristics to optimize Docker image layer efficiency
171
}
172
```
173
174
### Custom Layers
175
176
User-defined layer configuration with custom selection logic for fine-grained control over layer assignment.
177
178
```java { .api }
179
public class CustomLayers implements Layers {
180
/**
181
* Create custom layers with specified selectors.
182
*
183
* @param layers the ordered list of layers
184
* @param applicationSelectors selectors for application resources
185
* @param librarySelectors selectors for library resources
186
*/
187
public CustomLayers(List<Layer> layers,
188
List<ContentSelector<String>> applicationSelectors,
189
List<ContentSelector<Library>> librarySelectors);
190
}
191
```
192
193
### Content Selection
194
195
Strategy interface for determining which layer content should be assigned to.
196
197
```java { .api }
198
public interface ContentSelector<T> {
199
/**
200
* Get the target layer for selected content.
201
*
202
* @return the layer
203
*/
204
Layer getLayer();
205
206
/**
207
* Check if this selector contains the specified item.
208
*
209
* @param item the item to check
210
* @return true if the item belongs to this selector's layer
211
*/
212
boolean contains(T item);
213
}
214
```
215
216
### Include/Exclude Content Selector
217
218
Pattern-based content selector using include and exclude rules.
219
220
```java { .api }
221
public class IncludeExcludeContentSelector<T> implements ContentSelector<T> {
222
// Implementation uses pattern matching for flexible content selection
223
// Supports wildcards and regular expressions
224
}
225
```
226
227
### Content Filters
228
229
Filter interfaces for different types of content.
230
231
```java { .api }
232
public interface ContentFilter<T> {
233
// Base interface for content filtering
234
}
235
236
public class ApplicationContentFilter implements ContentFilter<String> {
237
// Filter for application resources (class files, properties, etc.)
238
}
239
240
public class LibraryContentFilter implements ContentFilter<Library> {
241
// Filter for library dependencies
242
}
243
```
244
245
## Usage Examples
246
247
### Basic Layer Configuration
248
249
```java
250
import org.springframework.boot.loader.tools.*;
251
import java.io.File;
252
253
// Use standard layers for optimal Docker caching
254
File sourceJar = new File("myapp.jar");
255
Repackager repackager = new Repackager(sourceJar);
256
257
// Enable layered packaging
258
repackager.setLayers(Layers.IMPLICIT);
259
260
// Repackage with layer support
261
repackager.repackage(Libraries.NONE);
262
263
// The resulting JAR will include BOOT-INF/layers.idx
264
// Docker buildpacks can use this for efficient layer creation
265
```
266
267
### Custom Layer Configuration
268
269
```java
270
import org.springframework.boot.loader.tools.*;
271
import org.springframework.boot.loader.tools.layer.*;
272
import java.io.File;
273
import java.util.List;
274
275
// Define custom layers
276
Layer infrastructureLayer = new Layer("infrastructure");
277
Layer businessLogicLayer = new Layer("business-logic");
278
Layer configurationLayer = new Layer("configuration");
279
280
// Create content selectors
281
List<ContentSelector<String>> applicationSelectors = List.of(
282
new IncludeExcludeContentSelector<>(configurationLayer,
283
List.of("**/*.properties", "**/*.yml", "**/*.xml"),
284
List.of()),
285
new IncludeExcludeContentSelector<>(businessLogicLayer,
286
List.of("**/service/**", "**/business/**"),
287
List.of()),
288
new IncludeExcludeContentSelector<>(infrastructureLayer,
289
List.of("**"), // everything else
290
List.of())
291
);
292
293
List<ContentSelector<Library>> librarySelectors = List.of(
294
new IncludeExcludeContentSelector<>(infrastructureLayer,
295
List.of("org.springframework.*", "org.hibernate.*"),
296
List.of()),
297
new IncludeExcludeContentSelector<>(businessLogicLayer,
298
List.of("com.example.business.*"),
299
List.of())
300
);
301
302
// Create custom layers configuration
303
Layers customLayers = new CustomLayers(
304
List.of(infrastructureLayer, businessLogicLayer, configurationLayer),
305
applicationSelectors,
306
librarySelectors
307
);
308
309
// Apply to repackager
310
Repackager repackager = new Repackager(new File("myapp.jar"));
311
repackager.setLayers(customLayers);
312
repackager.repackage(Libraries.NONE);
313
```
314
315
### Layer Index Generation
316
317
```java
318
import org.springframework.boot.loader.tools.*;
319
import java.io.File;
320
import java.io.FileOutputStream;
321
import java.io.IOException;
322
323
// Manually create and write a layer index
324
Layer appLayer = new Layer("application");
325
Layer libLayer = new Layer("dependencies");
326
Layer loaderLayer = new Layer("spring-boot-loader");
327
328
LayersIndex index = new LayersIndex(appLayer, libLayer, loaderLayer);
329
330
// Add entries to layers
331
index.add(appLayer, "BOOT-INF/classes/com/example/Application.class");
332
index.add(appLayer, "BOOT-INF/classes/com/example/service/UserService.class");
333
index.add(libLayer, "BOOT-INF/lib/spring-boot-3.2.0.jar");
334
index.add(libLayer, "BOOT-INF/lib/logback-classic-1.4.5.jar");
335
index.add(loaderLayer, "org/springframework/boot/loader/launch/JarLauncher.class");
336
337
// Write index to file
338
File indexFile = new File("layers.idx");
339
try (FileOutputStream fos = new FileOutputStream(indexFile)) {
340
index.writeTo(fos);
341
}
342
343
// The index file can be included in the JAR at BOOT-INF/layers.idx
344
```
345
346
### Integration with Docker Buildpacks
347
348
```java
349
import org.springframework.boot.loader.tools.*;
350
import java.io.File;
351
352
// Configure layers for optimal buildpack performance
353
public class BuildpackOptimizedLayers extends StandardLayers {
354
// Additional custom layers for buildpack optimization
355
public static final Layer BUILDPACK_CACHE = new Layer("buildpack-cache");
356
public static final Layer CONFIGURATION = new Layer("configuration");
357
358
@Override
359
public Layer getLayer(String applicationResource) {
360
// Buildpack-specific caching frequently accessed resources
361
if (applicationResource.endsWith(".properties") ||
362
applicationResource.endsWith(".yml") ||
363
applicationResource.endsWith(".yaml")) {
364
return CONFIGURATION;
365
}
366
367
// Use standard logic for other resources
368
return super.getLayer(applicationResource);
369
}
370
371
@Override
372
public Layer getLayer(Library library) {
373
LibraryCoordinates coords = library.getCoordinates();
374
if (coords != null) {
375
// Separate buildpack-specific libraries
376
if (coords.getGroupId().startsWith("io.buildpacks") ||
377
coords.getGroupId().startsWith("org.cloudfoundry")) {
378
return BUILDPACK_CACHE;
379
}
380
}
381
382
return super.getLayer(library);
383
}
384
}
385
386
// Use with repackager
387
Repackager repackager = new Repackager(new File("myapp.jar"));
388
repackager.setLayers(new BuildpackOptimizedLayers());
389
repackager.repackage(Libraries.NONE);
390
```
391
392
### Layer Analysis and Inspection
393
394
```java
395
import org.springframework.boot.loader.tools.*;
396
import java.io.IOException;
397
import java.util.Map;
398
import java.util.concurrent.ConcurrentHashMap;
399
import java.util.concurrent.atomic.AtomicLong;
400
401
// Analyze layer distribution for optimization
402
public class LayerAnalyzer {
403
private final Map<Layer, AtomicLong> layerSizes = new ConcurrentHashMap<>();
404
private final Map<Layer, AtomicLong> layerCounts = new ConcurrentHashMap<>();
405
406
public void analyzeLayers(Layers layers, Libraries libraries) throws IOException {
407
// Analyze library distribution
408
libraries.doWithLibraries(library -> {
409
Layer layer = layers.getLayer(library);
410
long size = library.getFile().length();
411
412
layerSizes.computeIfAbsent(layer, k -> new AtomicLong(0)).addAndGet(size);
413
layerCounts.computeIfAbsent(layer, k -> new AtomicLong(0)).incrementAndGet();
414
});
415
416
// Print analysis results
417
System.out.println("Layer Analysis:");
418
System.out.println("==============");
419
420
for (Layer layer : layers) {
421
long size = layerSizes.getOrDefault(layer, new AtomicLong(0)).get();
422
long count = layerCounts.getOrDefault(layer, new AtomicLong(0)).get();
423
424
System.out.printf("Layer: %s%n", layer);
425
System.out.printf(" Size: %,d bytes (%.2f MB)%n", size, size / 1024.0 / 1024.0);
426
System.out.printf(" Count: %,d items%n", count);
427
System.out.printf(" Avg Size: %,d bytes%n", count > 0 ? size / count : 0);
428
System.out.println();
429
}
430
}
431
}
432
433
// Usage
434
LayerAnalyzer analyzer = new LayerAnalyzer();
435
analyzer.analyzeLayers(Layers.IMPLICIT, libraries);
436
```
437
438
### Dynamic Layer Assignment
439
440
```java
441
import org.springframework.boot.loader.tools.*;
442
import org.springframework.boot.loader.tools.layer.*;
443
import java.util.List;
444
import java.util.function.Predicate;
445
446
// Dynamic layer assignment based on runtime criteria
447
public class DynamicLayers implements Layers {
448
private final List<Layer> layers;
449
private final Predicate<String> frequentlyChanging;
450
private final Predicate<Library> heavyLibraries;
451
452
public DynamicLayers(Predicate<String> frequentlyChanging, Predicate<Library> heavyLibraries) {
453
this.layers = List.of(
454
StandardLayers.SPRING_BOOT_LOADER,
455
new Layer("heavy-dependencies"),
456
StandardLayers.DEPENDENCIES,
457
new Layer("volatile-app"),
458
StandardLayers.APPLICATION
459
);
460
this.frequentlyChanging = frequentlyChanging;
461
this.heavyLibraries = heavyLibraries;
462
}
463
464
@Override
465
public Iterator<Layer> iterator() {
466
return layers.iterator();
467
}
468
469
@Override
470
public Layer getLayer(String applicationResource) {
471
if (frequentlyChanging.test(applicationResource)) {
472
return new Layer("volatile-app");
473
}
474
return StandardLayers.APPLICATION;
475
}
476
477
@Override
478
public Layer getLayer(Library library) {
479
if (heavyLibraries.test(library)) {
480
return new Layer("heavy-dependencies");
481
}
482
483
LibraryCoordinates coords = library.getCoordinates();
484
if (coords != null && coords.getGroupId().startsWith("org.springframework")) {
485
return StandardLayers.SPRING_BOOT_LOADER;
486
}
487
488
return StandardLayers.DEPENDENCIES;
489
}
490
}
491
492
// Usage with intelligent predicates
493
DynamicLayers dynamicLayers = new DynamicLayers(
494
// Frequently changing application resources
495
resource -> resource.contains("/controller/") ||
496
resource.contains("/service/") ||
497
resource.endsWith(".properties"),
498
499
// Heavy libraries (>10MB)
500
library -> library.getFile().length() > 10 * 1024 * 1024
501
);
502
503
Repackager repackager = new Repackager(new File("myapp.jar"));
504
repackager.setLayers(dynamicLayers);
505
repackager.repackage(Libraries.NONE);
506
```
507
508
## Docker Integration
509
510
### Layer Index Format
511
512
The layers index file (`BOOT-INF/layers.idx`) uses the following format:
513
514
```
515
- "dependencies":
516
- "BOOT-INF/lib/spring-boot-3.2.0.jar"
517
- "BOOT-INF/lib/logback-classic-1.4.5.jar"
518
- "spring-boot-loader":
519
- "org/springframework/boot/loader/"
520
- "application":
521
- "BOOT-INF/classes/"
522
- "META-INF/"
523
```
524
525
### Dockerfile Integration
526
527
```dockerfile
528
# Multi-stage build with layer extraction
529
FROM eclipse-temurin:17-jre as builder
530
WORKDIR application
531
ARG JAR_FILE=target/*.jar
532
COPY ${JAR_FILE} application.jar
533
RUN java -Djarmode=layertools -jar application.jar extract
534
535
# Final image with optimized layers
536
FROM eclipse-temurin:17-jre
537
WORKDIR application
538
539
# Copy layers in order of change frequency (least to most)
540
COPY --from=builder application/dependencies/ ./
541
COPY --from=builder application/spring-boot-loader/ ./
542
COPY --from=builder application/snapshot-dependencies/ ./
543
COPY --from=builder application/application/ ./
544
545
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
546
```
547
548
This layering strategy ensures that:
549
1. **Dependencies layer** - Cached until dependencies change
550
2. **Spring Boot loader layer** - Cached until Spring Boot version changes
551
3. **Snapshot dependencies layer** - Rebuilt when snapshot versions change
552
4. **Application layer** - Rebuilt on every application code change
553
554
The result is faster Docker builds and more efficient image distribution.