springdocker 1.0.2__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {springdocker-1.0.2/src/springdocker.egg-info → springdocker-1.1.0}/PKG-INFO +223 -24
  2. springdocker-1.1.0/README.md +305 -0
  3. springdocker-1.1.0/cli/README.md +381 -0
  4. {springdocker-1.0.2 → springdocker-1.1.0}/pyproject.toml +24 -4
  5. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/__init__.py +1 -1
  6. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/analyze.py +30 -17
  7. springdocker-1.1.0/src/springdocker/benchmarks/generate.py +400 -0
  8. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/benchmarks/runner.py +16 -10
  9. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/cli.py +163 -37
  10. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/commands.py +131 -47
  11. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/compare.py +1 -1
  12. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/config.py +301 -21
  13. springdocker-1.1.0/src/springdocker/config_audit.py +254 -0
  14. springdocker-1.1.0/src/springdocker/config_serializer.py +135 -0
  15. springdocker-1.1.0/src/springdocker/configure_wizard.py +193 -0
  16. springdocker-1.1.0/src/springdocker/digest_pins.py +141 -0
  17. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/dockerfile.py +315 -299
  18. springdocker-1.1.0/src/springdocker/dockerfile_explain.py +281 -0
  19. springdocker-1.1.0/src/springdocker/dockerfile_profiles.py +122 -0
  20. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/project_detect.py +173 -17
  21. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/regression.py +33 -21
  22. springdocker-1.1.0/src/springdocker/runtime_images.py +55 -0
  23. springdocker-1.1.0/src/springdocker/services/dockerfile_service.py +276 -0
  24. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/services/verify_service.py +56 -0
  25. {springdocker-1.0.2 → springdocker-1.1.0/src/springdocker.egg-info}/PKG-INFO +223 -24
  26. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker.egg-info/SOURCES.txt +7 -0
  27. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker.egg-info/requires.txt +1 -0
  28. springdocker-1.0.2/README.md +0 -160
  29. springdocker-1.0.2/cli/README.md +0 -183
  30. springdocker-1.0.2/src/springdocker/benchmarks/generate.py +0 -116
  31. springdocker-1.0.2/src/springdocker/services/dockerfile_service.py +0 -126
  32. {springdocker-1.0.2 → springdocker-1.1.0}/LICENSE +0 -0
  33. {springdocker-1.0.2 → springdocker-1.1.0}/setup.cfg +0 -0
  34. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/benchmarks/__init__.py +0 -0
  35. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/errors.py +0 -0
  36. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/plugins.py +0 -0
  37. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/services/__init__.py +0 -0
  38. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/services/benchmark_service.py +0 -0
  39. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/services/project_service.py +0 -0
  40. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/templates/_section.j2 +0 -0
  41. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker/templates/dockerfile.j2 +0 -0
  42. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker.egg-info/dependency_links.txt +0 -0
  43. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker.egg-info/entry_points.txt +0 -0
  44. {springdocker-1.0.2 → springdocker-1.1.0}/src/springdocker.egg-info/top_level.txt +0 -0
  45. {springdocker-1.0.2 → springdocker-1.1.0}/tests/test_support.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: springdocker
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: CLI for Spring Boot Dockerfile and benchmark workflows (Maven/Gradle).
5
5
  Author: springdocker contributors
6
6
  License: Apache License
@@ -220,6 +220,7 @@ Requires-Dist: pytest>=8.0; extra == "dev"
220
220
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
221
221
  Requires-Dist: ruff>=0.6.0; extra == "dev"
222
222
  Requires-Dist: mypy>=1.10; extra == "dev"
223
+ Requires-Dist: types-Jinja2; extra == "dev"
223
224
  Requires-Dist: requests>=2.32; extra == "dev"
224
225
  Dynamic: license-file
225
226
 
@@ -227,19 +228,20 @@ Dynamic: license-file
227
228
 
228
229
  CLI for Spring Boot Dockerfile and benchmark workflows across Maven and Gradle projects.
229
230
 
231
+ Product scope and CI-evidenced guarantees: [`docs/POSITIONING.md`](../docs/POSITIONING.md).
232
+
230
233
  ## Install
231
234
 
232
- ### Local editable
235
+ **Primary path:** install from PyPI and run against your Spring Boot project. Clone the repository only for benchmarks on the sample app or for development ([ADR 0006](../docs/adr/0006-pypi-first-distribution.md)).
233
236
 
234
- ```bash
235
- python3 -m pip install -e .
236
- ```
237
-
238
- ### pipx
237
+ ### pipx (recommended)
239
238
 
240
239
  ```bash
241
240
  pipx install springdocker
242
241
  springdocker --help
242
+
243
+ # optional: benchmark run/analyze (requires Docker)
244
+ pipx install 'springdocker[benchmark]'
243
245
  ```
244
246
 
245
247
  Upgrade:
@@ -254,20 +256,38 @@ pipx upgrade springdocker
254
256
  uv tool install springdocker
255
257
  uv tool upgrade springdocker
256
258
 
257
- # benchmark/evidence commands need optional extras
258
- python3 -m pip install -e '.[benchmark]'
259
+ # benchmark extra
260
+ uv tool install 'springdocker[benchmark]'
261
+ ```
262
+
263
+ ### pip
264
+
265
+ ```bash
266
+ python3 -m pip install springdocker
267
+ python3 -m pip install 'springdocker[benchmark]'
268
+ ```
269
+
270
+ ### From source (contributors)
271
+
272
+ ```bash
273
+ git clone https://github.com/mnafshin/springdocker.git
274
+ cd springdocker
275
+ python3 -m pip install -e ".[dev]"
259
276
  ```
260
277
 
261
278
  ## Quick usage
262
279
 
263
280
  ```bash
264
281
  springdocker init --project-root samples/java-spring-docker --build-tool maven --profile quick
282
+ springdocker configure --project-root samples/java-spring-docker --force # interactive → writes [dockerfile] in config
283
+ springdocker dockerfile generate --project-root samples/java-spring-docker # non-interactive; reads .springdocker.toml
265
284
  springdocker doctor --project-root samples/java-spring-docker
266
285
  springdocker inspect --project-root samples/java-spring-docker --format json
267
286
  springdocker explain --project-root samples/java-spring-docker Dockerfile.generated --format json
268
287
  springdocker benchmark compare --project-root samples/java-spring-docker benchmarks/03-custom-jre-jlink/results/raw.csv --baseline-variant with-jlink-runtime --format json
269
288
  springdocker dockerfile generate --project-root samples/java-spring-docker --output Dockerfile.generated --recipe jvm-balanced
270
289
  springdocker dockerfile generate --project-root samples/java-spring-docker --recipe spring-aot
290
+ # native-aot emits experimental scaffold output only (not a production workflow)
271
291
  springdocker dockerfile generate --project-root samples/java-spring-docker --recipe native-aot
272
292
  springdocker benchmark generate --project-root samples/java-spring-docker --java-version 25
273
293
  springdocker benchmark run --project-root samples/java-spring-docker --profile quick --runner-arg --skip-native
@@ -278,7 +298,89 @@ springdocker benchmark analyze --project-root samples/java-spring-docker benchma
278
298
  ```
279
299
 
280
300
  Benchmark commands are optional evidence workflows and require benchmark extras.
281
- Use `samples/java-spring-docker/benchmarks/reference/v1/summary.json` as a versioned baseline example.
301
+ Use `samples/java-spring-docker/benchmarks/06-base-image-choice/results/baseline.json` as the versioned CI regression baseline example (paired with committed `raw.csv`).
302
+ Scenario index: [README.md](../README.md#benchmark-scenario-index).
303
+
304
+ ## Dockerfile recipes
305
+
306
+ | Recipe | Status | Default runtime | Notes |
307
+ |---|---|---|---|
308
+ | `jvm-balanced` | Supported | **distroless** + jlink | Default production-oriented JVM Dockerfile. |
309
+ | `spring-aot` | Supported | **distroless** + jlink | Spring Boot AOT processing on a JVM runtime. |
310
+ | `native-aot` | Scaffold only | **distroless** base (static binary) | Experimental GraalVM native-image Dockerfile output. Not a production-ready workflow; see `docs/native-image-roadmap.md`. |
311
+
312
+ The generator sets `runtime_image = "distroless"` internally for JVM recipes. That means `distroless/base-debian*` plus a copied jlink runtime — not the prebuilt `distroless/java*` image and not a full OS layer unless you change generator options (benchmark scenario **06** compares OS bases).
313
+
314
+ ### Runtime bases and HEALTHCHECK
315
+
316
+ | Runtime | Generator behavior |
317
+ |---|---|
318
+ | `distroless` (default) | Non-root, minimal base + jlink; **no `HEALTHCHECK`** (no shell/`wget` in the image). Probe readiness from the orchestrator (e.g. Kubernetes `readinessProbe` on `/actuator/health/readiness`). |
319
+ | `debian-slim`, `alpine`, `ubuntu`, `temurin` | Full OS or vendor JRE paths; **`HEALTHCHECK` is emitted** when Spring Boot Actuator is on the classpath. |
320
+
321
+ Supported runtime names: `distroless`, `debian-slim`, `alpine`, `ubuntu`, `temurin` (plus aliases such as `debian-bookworm-slim`, `eclipse-temurin-jre`). Set via `[dockerfile].runtime_image` in `.springdocker.toml` or `--runtime-image` on `dockerfile generate`.
322
+
323
+ ## Config-first workflow
324
+
325
+ `.springdocker.toml` is the **single source of truth** for Dockerfile generation (see [ADR 0005](../docs/adr/0005-config-first-dockerfile-generation.md)). Team rollout guide: [docs/team-adoption.md](../docs/team-adoption.md).
326
+
327
+ ### Command matrix
328
+
329
+ | Command | Interactive? | Writes config? | Writes Dockerfile? | Typical user |
330
+ |---|---|---|---|---|
331
+ | `springdocker init` | No | Yes (skeleton) | No | Platform / first checkout |
332
+ | `springdocker init --interactive` | Yes (via configure) | Yes | No | New service bootstrap |
333
+ | `springdocker configure` | Yes | Yes (`[dockerfile]`) | Optional (`--generate`) | Strategy changes |
334
+ | `springdocker dockerfile generate` | No | No | Yes | Daily dev + CI |
335
+ | `springdocker explain --config-aware` | No | No | No | Audit / review (advisory — not a CI gate) |
336
+ | `springdocker verify --check-config-drift` | No | No | No | CI SSOT gate (pass/fail) |
337
+
338
+ ### Precedence
339
+
340
+ | Priority | Source |
341
+ |---:|---|
342
+ | 1 | CLI flags on `dockerfile generate` |
343
+ | 2 | Project `.springdocker.toml` |
344
+ | 3 | Built-in defaults |
345
+
346
+ Org policy (`SPRINGDOCKER_POLICY`) is planned ([#123](https://github.com/mnafshin/springdocker/issues/123)); not required today.
347
+
348
+ | Command | Purpose |
349
+ |---|---|
350
+ | `springdocker configure` | Interactive wizard that writes/updates `[dockerfile]` in config |
351
+ | `springdocker init --interactive` | Create config skeleton, then run configure |
352
+ | `springdocker dockerfile generate` | Deterministic generate from config (CI-safe, no prompts) |
353
+
354
+ Profiles (`production-balanced`, `smallest-image`, `fast-cold-start`, `build-speed`, `simplest`, `compliance`, `custom`) are selected in `configure` and expanded to explicit options in config.
355
+
356
+ ### `dockerfile generate` CLI overrides
357
+
358
+ Every `[dockerfile]` key is overridable from the CLI except file-backed keys (`must_have_modules_file`, `jlink_baseline_modules`), which stay in config only.
359
+
360
+ | Section | CLI flags | Config key(s) |
361
+ |---|---|---|
362
+ | General | `--output`, `--java-version`, `--recipe`, `--profile` | `output`, `java_version`, `recipe`, `profile` |
363
+ | Runtime | `--runtime-image`, `--non-root` / `--no-non-root`, `--platform-aware` / `--no-platform-aware`, `--healthcheck-path` | `runtime_image`, `non_root`, `platform_aware`, `healthcheck_path` |
364
+ | Build | `--use-buildkit-cache` / `--no-use-buildkit-cache`, `--use-jlink` / `--no-use-jlink`, `--use-layered-jar` / `--no-use-layered-jar` | `use_buildkit_cache`, `use_jlink`, `use_layered_jar` |
365
+ | Supply chain | `--include-oci-labels`, `--include-stopsignal`, `--include-embedded-sbom`, `--include-reproducible-controls`, `--pin-digests` (each with `--no-*` form) | matching `include_*` keys and `pin_digests` |
366
+ | JVM | `--enable-appcds`, `--enable-jep483-aot-cache`, `--tuned-jvm-flags`, `--jvm-flag` (repeatable) | `enable_appcds`, `enable_jep483_aot_cache`, `tuned_jvm_flags`, `jvm_flags` |
367
+
368
+ Example one-off CI override:
369
+
370
+ ```bash
371
+ springdocker dockerfile generate \
372
+ --project-root samples/java-spring-docker \
373
+ --runtime-image alpine \
374
+ --no-use-jlink \
375
+ --enable-jep483-aot-cache \
376
+ --no-include-embedded-sbom \
377
+ --pin-digests \
378
+ --jvm-flag "-XX:+UseZGC" # or --jvm-flag=-XX:+UseZGC when the flag starts with '-'
379
+ ```
380
+
381
+ The `dockerfile generate` `--help` output groups flags under **runtime**, **build**, **supply chain**, and **JVM** sections.
382
+
383
+ The `07-native-benchmark` scenario is generated with the `native-aot` scaffold recipe. The internal benchmark runner skips native scenarios by default (`--skip-native`).
282
384
 
283
385
  ## Config file (`.springdocker.toml`)
284
386
 
@@ -301,9 +403,16 @@ build_tool = "maven"
301
403
  output = "Dockerfile.generated"
302
404
  java_version = 25
303
405
  recipe = "jvm-balanced"
406
+ # profile = "production-balanced" # set by `springdocker configure`
407
+ # runtime_image = "distroless"
408
+ # use_jlink = true
409
+ # enable_jep483_aot_cache = false
410
+ # include_embedded_sbom = true
411
+ # pin_digests = true
412
+ # tuned_jvm_flags = true
413
+ # jvm_flags = ["-XX:MaxRAMPercentage=75", "-XX:+ExitOnOutOfMemoryError", "-Djava.io.tmpdir=/tmp"]
414
+ # Generator default runtime: distroless (gcr.io/distroless/base-* + jlink + layered JAR).
304
415
  must_have_modules_file = "must-have.txt"
305
- legacy_scripts = false
306
- wizard_args = []
307
416
 
308
417
  [benchmark.generate]
309
418
  java_version = 25
@@ -324,6 +433,28 @@ When `dockerfile.must_have_modules_file` is set, springdocker reads modules from
324
433
  (`must-have.txt` style, one module per line, `#` comments allowed) and injects them into
325
434
  the jlink module list for reflection/dynamic-loading edge cases.
326
435
 
436
+ When jlink is enabled, springdocker auto-merges **jlink baseline modules** when Spring Web starters
437
+ are detected (`spring-boot-starter-web`, `spring-boot-starter-webflux`, `spring-boot-starter-websocket`):
438
+
439
+ - `java.desktop` — JavaBeans and desktop-related APIs used by parts of the Spring Web stack
440
+ - `java.logging` — `java.util.logging` used by framework and library code
441
+ - `java.naming` — JNDI lookups that jdeps often misses on web apps
442
+
443
+ Non-web Spring Boot workloads get **no** auto baseline — use jdeps plus `must_have_modules_file` for
444
+ extra modules. Override or disable in `.springdocker.toml`:
445
+
446
+ ```toml
447
+ [dockerfile]
448
+ # Omit jlink_baseline_modules to auto-detect from Spring Web starters at generate time.
449
+ # Override defaults or set [] to disable baseline injection.
450
+ jlink_baseline_modules = ["java.desktop", "java.logging", "java.naming"]
451
+ ```
452
+
453
+ See [ADR 0007](../docs/adr/0007-jlink-baseline-modules-web-detection.md).
454
+
455
+ `springdocker explain` reports baseline and curated modules separately in JSON/table output.
456
+ Baseline modules are generator defaults; curated modules come from `must_have_modules_file`.
457
+
327
458
  Create template config:
328
459
 
329
460
  ```bash
@@ -331,24 +462,32 @@ springdocker init --project-root samples/java-spring-docker --build-tool gradle
331
462
  springdocker init --project-root samples/java-spring-docker --build-tool gradle --profile full --print
332
463
  ```
333
464
 
334
- ## Legacy compatibility mode
465
+ ### `init --interactive`
335
466
 
336
- Main command paths are internal and do not require project script files.
337
-
338
- To force script wrappers for compatibility:
467
+ Creates `.springdocker.toml` if missing, then runs the same wizard as `configure` (no Dockerfile write unless you chain commands yourself):
339
468
 
340
469
  ```bash
341
- springdocker dockerfile generate --use-legacy-scripts ...
342
- springdocker benchmark generate --use-legacy-scripts ...
343
- springdocker benchmark run --use-legacy-scripts ...
470
+ springdocker init --project-root . --build-tool maven --interactive
471
+ # equivalent to:
472
+ # springdocker init --project-root . --build-tool maven
473
+ # springdocker configure --project-root . --force
344
474
  ```
345
475
 
346
- or set:
476
+ Use `--force` on `init` to overwrite an existing skeleton; use `configure --force` to replace only the `[dockerfile]` section in an existing file.
477
+
478
+ See [docs/team-adoption.md](../docs/team-adoption.md) for first-time setup, CI examples, and migration from the retired `tools/dockerfile_wizard.py`.
479
+
480
+ ## Legacy benchmark scripts
481
+
482
+ Benchmark commands still support `--use-legacy-scripts` (or `SPRINGDOCKER_LEGACY_SCRIPTS=1`) to delegate to project scripts under `benchmarks/` when needed:
347
483
 
348
484
  ```bash
349
- export SPRINGDOCKER_LEGACY_SCRIPTS=1
485
+ springdocker benchmark generate --use-legacy-scripts ...
486
+ springdocker benchmark run --use-legacy-scripts ...
350
487
  ```
351
488
 
489
+ Dockerfile generation always uses the internal config-driven generator. Use `springdocker configure` for interactive setup instead of the retired `tools/dockerfile_wizard.py` script.
490
+
352
491
  ## Inspect command
353
492
 
354
493
  `springdocker inspect` prints static metadata about the target project:
@@ -364,16 +503,76 @@ Use `--format json` for machine-readable output.
364
503
 
365
504
  ## Explain command
366
505
 
367
- `springdocker explain` reads a springdocker-generated Dockerfile and describes the optimizations it contains:
506
+ `springdocker explain` reads a Dockerfile and **describes** optimizations it recognizes using static text heuristics (regex and keyword matching).
507
+
508
+ **Advisory only — not a CI gate.** Explain output helps humans review and document a Dockerfile. It does **not** perform a security audit, lint the file, scan images, or prove runtime correctness. Hand-written Dockerfiles may be misread; a missing feature in explain output does not mean that optimization is absent at runtime.
509
+
510
+ **Use `springdocker verify` for CI gates** — hadolint, trivy, SBOM checks, optional dive/cosign/smoke, and `--check-config-drift` for config SSOT compliance. Only `verify` uses pass/fail semantics suitable for blocking merges.
511
+
512
+ Recognized signals include:
368
513
 
369
514
  - multi-stage layout
370
515
  - BuildKit cache usage
371
516
  - jlink runtime stage
372
517
  - non-root runtime
373
518
  - tuned JVM flags
374
- - curated must-have modules
519
+ - jlink baseline modules (built-in defaults)
520
+ - curated must-have modules (from `must-have.txt`)
521
+
522
+ Use `--format json` when you want stable structured output. The JSON `notes` field repeats the advisory scope and points to `verify`.
523
+
524
+ Add `--config-aware` to include resolved `[dockerfile]` options from `.springdocker.toml`, per-option sources (`default` or `project`), and drift detection against `dockerfile generate`. Config drift in explain is informational; enforce SSOT in CI with `verify --check-config-drift`:
525
+
526
+ ```bash
527
+ springdocker explain --project-root . Dockerfile.generated --format json --config-aware
528
+ springdocker verify --project-root . --dockerfile Dockerfile.generated --check-config-drift
529
+ ```
530
+
531
+ ## Verify command
532
+
533
+ `springdocker verify` runs a battery of checks against a generated Dockerfile and optional runtime context. It is designed to work in CI without installing every external tool.
534
+
535
+ ```bash
536
+ springdocker verify --project-root tests/fixtures/maven-only Dockerfile.generated
537
+ springdocker verify --project-root tests/fixtures/maven-only Dockerfile.generated \
538
+ --image demo:latest \
539
+ --smoke-url http://127.0.0.1:8081/actuator/health \
540
+ --format junit \
541
+ --output reports/verify.junit.xml
542
+ springdocker verify --project-root . --dockerfile Dockerfile.generated --check-config-drift
543
+ ```
544
+
545
+ `--check-config-drift` adds config SSOT checks when `.springdocker.toml` is present:
546
+
547
+ | Check | Validates |
548
+ |---|---|
549
+ | `config-drift` | Dockerfile matches `dockerfile generate` output for current config |
550
+ | `config-embedded-sbom` | `/usr/share/sbom/spdx.json` present when `include_embedded_sbom = true` |
551
+ | `config-non-root` | unprivileged `USER` when `non_root = true` |
552
+ | `config-jvm-flags` | configured JVM flags appear in `ENTRYPOINT` |
553
+
554
+ ### Built-in checks
555
+
556
+ | Check | Requires | Missing prerequisite | Check failure |
557
+ |---|---|---|---|
558
+ | `hadolint` | `hadolint` on `PATH` | **skipped** (`hadolint not installed`) | non-zero exit |
559
+ | `trivy` | `trivy` on `PATH` | **skipped** (`trivy not installed`) | HIGH/CRITICAL findings |
560
+ | `dive` | `--image` and `dive` on `PATH` | **skipped** (`no image provided` or `dive not installed`) | non-zero exit |
561
+ | `cosign` | `--image` and `cosign` on `PATH` | **skipped** (`no image provided` or `cosign not installed`) | non-zero exit |
562
+ | `sbom` | `sbom.spdx.json` in project root | n/a (always runs) | **failed** if file missing, invalid JSON, or missing `spdxVersion` |
563
+ | `smoke` | `--smoke-url` | **skipped** (`no smoke URL provided`) | HTTP/network error or status ≥ 400 |
564
+
565
+ Verifier plugins registered under `springdocker.verifiers` run after the built-in checks. See `docs/extensions.md`.
566
+
567
+ ### Skip vs fail semantics
568
+
569
+ - **skipped** checks do not fail the command. They appear in table/JSON/JUnit/SARIF output for visibility.
570
+ - **failed** checks set the overall result to `failed` and make `springdocker verify` exit with code `1`.
571
+ - Only **failed** checks affect the exit code. A run where every external tool is missing but `sbom.spdx.json` is valid still exits `0`.
572
+
573
+ Optional tools are intentionally optional: install `hadolint`, `trivy`, `dive`, and `cosign` locally or in CI when you want those gates enforced.
375
574
 
376
- Use `--format json` when you want stable structured output.
575
+ Supported `--format` values: `table` (default), `json`, `junit`, `sarif`, plus plugin-provided formats.
377
576
 
378
577
  ## Security hardening
379
578
 
@@ -0,0 +1,305 @@
1
+ # springdocker
2
+
3
+ [![CI](https://github.com/mnafshin/springdocker/actions/workflows/ci.yml/badge.svg)](https://github.com/mnafshin/springdocker/actions/workflows/ci.yml)
4
+ [![Release](https://github.com/mnafshin/springdocker/actions/workflows/release.yml/badge.svg)](https://github.com/mnafshin/springdocker/actions/workflows/release.yml)
5
+ [![Lint](https://img.shields.io/badge/lint-ruff-blue)](https://github.com/astral-sh/ruff)
6
+ [![Coverage](https://img.shields.io/badge/coverage-%3E%3D80%25-brightgreen)](./CONTRIBUTING.md#coverage-policy)
7
+ [![Benchmark](https://img.shields.io/badge/benchmark-regression--gated-orange)](./docs/benchmark-methodology.md)
8
+
9
+ Developer toolkit for **production teams** containerizing Spring Boot — with optional benchmark evidence for tuning and conference demos.
10
+
11
+ `springdocker` is a Python CLI that helps teams inspect a Spring Boot project, commit Dockerfile strategy in `.springdocker.toml`, generate and verify Dockerfiles in CI, and (optionally) run benchmark suites for evidence-backed tuning.
12
+
13
+ See [`docs/POSITIONING.md`](docs/POSITIONING.md) for **who it is for**, CI-evidenced guarantees, and how the sample projects relate to shipped behavior.
14
+
15
+ ## Who it's for
16
+
17
+ Resolved in [#87](https://github.com/mnafshin/springdocker/issues/87) — see [`docs/adr/0008-target-audience.md`](docs/adr/0008-target-audience.md).
18
+
19
+ | Audience | Fit |
20
+ |---|---|
21
+ | **Production teams** (primary) | Adopt via PyPI on **your** Maven/Gradle service — config-first Dockerfile workflow, explain/verify in CI, Java **17+** on your project |
22
+ | **Conference / storytelling** (secondary) | Clone for presentations and benchmark evidence under `samples/java-spring-docker/` — numbers are sample-specific, not universal guarantees |
23
+ | **Personal lab only** (not primary) | Bleeding-edge sample (Boot 4 / Java 25) stress-tests the generator inside the repo; you do not need to match those versions to use the CLI |
24
+
25
+ **Not** a black-box image builder like Jib or Buildpacks — you own the Dockerfile. **Not** a research-only toy — CI gates the installable CLI; benchmarks and decks are optional depth.
26
+
27
+ ## Install
28
+
29
+ **Primary path: PyPI** — install the CLI and run it on your Spring Boot project. You do not need to clone this repository for Dockerfile generation, explain, or verify.
30
+
31
+ ```bash
32
+ # Recommended: isolated user install (Dockerfile workflow)
33
+ pipx install springdocker
34
+ # or
35
+ uv tool install springdocker
36
+
37
+ # Include benchmark run/analyze (optional; requires Docker on the host)
38
+ pipx install 'springdocker[benchmark]'
39
+ # or: python3 -m pip install 'springdocker[benchmark]' inside your project venv
40
+ ```
41
+
42
+ See [`cli/README.md`](cli/README.md#install) for pip/editable options and upgrade commands.
43
+
44
+ ### When to clone this repository
45
+
46
+ | Goal | What to do |
47
+ |---|---|
48
+ | Generate/explain/verify Dockerfiles for **your** service | Install from PyPI only |
49
+ | Reproduce benchmark evidence, presentations, or pinned CI baselines | Clone; work under `samples/java-spring-docker/` — see [`docs/presentation/README.md`](docs/presentation/README.md) for deck ownership and update policy |
50
+ | Contribute to the CLI | Clone; editable install — see [Contributing](#contributing) |
51
+
52
+ Resolved in [#97](https://github.com/mnafshin/springdocker/issues/97) — see [`docs/adr/0006-pypi-first-distribution.md`](docs/adr/0006-pypi-first-distribution.md).
53
+
54
+ ## Project naming
55
+
56
+ **springdocker** is the canonical name for this project — use it when searching GitHub or PyPI, installing the package, or running the CLI.
57
+
58
+ | Surface | Name |
59
+ |---|---|
60
+ | GitHub repository | [`mnafshin/springdocker`](https://github.com/mnafshin/springdocker) |
61
+ | PyPI package / `pip install` | `springdocker` |
62
+ | CLI command | `springdocker` |
63
+ | Config file | `.springdocker.toml` |
64
+
65
+ The string **`java-spring-docker`** appears only in the benchmark sample app, not in the CLI package:
66
+
67
+ | Surface | Path or coordinates | Role |
68
+ |---|---|---|
69
+ | Sample app directory | `samples/java-spring-docker/` | Full Spring Boot app for benchmark scenarios and evidence |
70
+ | Sample Maven/Gradle artifact | `io.github.mnafshin:java-spring-docker` | Demo application identity inside that sample |
71
+
72
+ Those sample names predate the **springdocker** product name. They do not affect installation (`pip install springdocker`) or CLI usage. Your local clone directory can be named anything.
73
+
74
+ ## Why springdocker instead of Jib or Buildpacks?
75
+
76
+ - **Jib** and **Buildpacks** optimize for build convenience and opaque image assembly.
77
+ - **springdocker** optimizes for teams that want a **real Dockerfile they can own, read, and edit**.
78
+ - It combines explicit Dockerfile generation with explainability and verification workflows.
79
+ - **`explain`** is advisory static analysis for human review; **`verify`** is the pass/fail command for CI gates.
80
+
81
+ See [`docs/POSITIONING.md`](docs/POSITIONING.md) for the detailed comparison and tradeoffs.
82
+
83
+ ## Architecture
84
+
85
+ ```mermaid
86
+ flowchart LR
87
+ dev[Developer] --> cli[springdocker CLI]
88
+ cli --> cfg[.springdocker.toml]
89
+ cli --> proj[Spring Boot project]
90
+ cli --> df[Generated Dockerfile]
91
+ cli --> bench[Benchmark variants + raw CSV]
92
+ bench --> report[Table / JSON analysis]
93
+ ```
94
+
95
+ See `docs/architecture.md` for the detailed module map and command lifecycle.
96
+
97
+ The repo is split into these main surfaces:
98
+
99
+ - `src/springdocker/` - installable CLI package and core implementation.
100
+ - `cli/README.md` - command reference and configuration details.
101
+
102
+ See [Sample project map](#sample-project-map) for which Spring Boot path to use.
103
+
104
+ ## What it does
105
+
106
+ **Shipped and CI-validated:** project detection, config, Dockerfile generation, explain/verify commands, and benchmark asset/analyzer plumbing (see [`docs/POSITIONING.md`](docs/POSITIONING.md#shipped-guarantees-ci-evidenced)).
107
+
108
+ **Optional / sample-anchored:** full benchmark runs, performance comparison tables, and reference evidence under `samples/java-spring-docker/`.
109
+
110
+ - Detects Maven or Gradle projects.
111
+ - Writes a starter `.springdocker.toml` config.
112
+ - Generates Dockerfiles with opinionated Spring Boot defaults (`jvm-balanced`: **distroless** runtime + custom **jlink** runtime + layered JAR).
113
+ - Pins generated base images by digest when known.
114
+ - Creates benchmark variants and runs benchmark suites (requires Docker and `[benchmark]` extra).
115
+ - Summarizes benchmark CSV output as a table or JSON.
116
+
117
+ Digest pins are centralized in `src/springdocker/digest_pins.py` and verified in CI.
118
+ Runbook: [`docs/digest-pin-runbook.md`](docs/digest-pin-runbook.md) · Renovate template: [`.github/renovate.json`](.github/renovate.json)
119
+
120
+ ## Sample project map
121
+
122
+ | Path | Role | Use when |
123
+ |---|---|---|
124
+ | `tests/fixtures/{maven-only,gradle-only}/` | Minimal Spring Boot apps for CLI walkthroughs and CI | Learning the CLI, trying Dockerfile generation, or extending tests ([`docs/golden-samples.md`](docs/golden-samples.md)) |
125
+ | `samples/java-spring-docker/` | Benchmark harness + evidence | Running benchmark scenarios and comparing `raw.csv` results |
126
+
127
+ Gradle walkthroughs use `tests/fixtures/gradle-only/` with the same commands below (Maven: `tests/fixtures/maven-only/`).
128
+
129
+ See [`docs/adr/0004-sample-project-strategy.md`](docs/adr/0004-sample-project-strategy.md) for why the former `examples/` walkthrough tree was removed.
130
+
131
+ ## Quick start
132
+
133
+ Install from PyPI first (see [Install](#install)). Then run against **your** Spring Boot project:
134
+
135
+ ```bash
136
+ cd /path/to/your-spring-boot-app
137
+ springdocker doctor --project-root .
138
+ springdocker init --project-root . --build-tool maven
139
+ springdocker configure --project-root . --force
140
+ springdocker dockerfile generate --project-root .
141
+ springdocker explain --project-root . Dockerfile.generated --config-aware # advisory review
142
+ springdocker verify --project-root . Dockerfile.generated --check-config-drift # CI gate
143
+ ```
144
+
145
+ To try the CLI without your own app, clone this repo and use the minimal fixtures (see [Sample project map](#sample-project-map)):
146
+
147
+ ```bash
148
+ git clone https://github.com/mnafshin/springdocker.git
149
+ cd springdocker
150
+ pipx install 'springdocker[benchmark]' # or: pip install -e '.[dev]' for contributing
151
+
152
+ springdocker doctor --project-root tests/fixtures/maven-only
153
+ springdocker init --project-root tests/fixtures/maven-only --build-tool maven
154
+ springdocker configure --project-root tests/fixtures/maven-only --force
155
+ springdocker dockerfile generate --project-root tests/fixtures/maven-only
156
+ ```
157
+
158
+ **Benchmark workflow** (optional; requires clone + Docker + `[benchmark]` extra) — use the full sample app under `samples/`:
159
+
160
+ ```bash
161
+ cd springdocker # repository root after clone
162
+ springdocker benchmark generate --project-root samples/java-spring-docker --java-version 25
163
+ springdocker benchmark run --project-root samples/java-spring-docker --profile quick
164
+ springdocker benchmark analyze --project-root samples/java-spring-docker samples/java-spring-docker/benchmarks/03-custom-jre-jlink/results/raw.csv --format table
165
+ springdocker benchmark compare --project-root samples/java-spring-docker samples/java-spring-docker/benchmarks/03-custom-jre-jlink/results/raw.csv --baseline-variant with-jlink-runtime
166
+ springdocker benchmark analyze --project-root samples/java-spring-docker samples/java-spring-docker/benchmarks/06-base-image-choice/results/raw.csv --baseline samples/java-spring-docker/benchmarks/06-base-image-choice/results/baseline.json
167
+ ```
168
+
169
+ **Default runtime:** `dockerfile generate` with `--recipe jvm-balanced` (the default) uses **`runtime_image = distroless`**: a digest-pinned `gcr.io/distroless/base-*:nonroot` stage plus a jlink-built JVM and layered Spring Boot JAR — not a full OS image. Distroless images have no shell, so generated Dockerfiles **omit `HEALTHCHECK`**; configure readiness probes in Kubernetes or your orchestrator. OS runtime bases are compared in benchmark scenario **06** — see [`cli/README.md`](cli/README.md#dockerfile-recipes).
170
+
171
+ ## CLI workflow
172
+
173
+ 1. `doctor` checks the project root and build tool.
174
+ 2. `init` writes a starter config file.
175
+ 3. `dockerfile generate` writes a Dockerfile to the requested path (default recipe: distroless + jlink layered JAR).
176
+ 4. `benchmark generate` creates benchmark scenarios.
177
+ 5. `benchmark run` executes the benchmark runner.
178
+ 6. `benchmark analyze` turns `raw.csv` into a table or JSON summary.
179
+
180
+ See `cli/README.md` for the command reference and config precedence rules.
181
+
182
+ ## Benchmark methodology
183
+
184
+ See `docs/benchmark-methodology.md` for the benchmark model, run profiles, and summary calculations.
185
+
186
+ Benchmarks are an optional evidence subsystem and require benchmark extras (`springdocker[benchmark]`).
187
+
188
+ The sample project keeps benchmark scenarios under `samples/java-spring-docker/benchmarks/`.
189
+ Generated variant Dockerfiles and most run output are **gitignored** — regenerate with `springdocker benchmark generate` and `springdocker benchmark run`.
190
+ The scenario 06 CI regression baseline lives under `samples/java-spring-docker/benchmarks/06-base-image-choice/results/`.
191
+ See `samples/java-spring-docker/benchmarks/README.md` for the full artifact policy.
192
+
193
+ ### Benchmark scenario index
194
+
195
+ Authoritative list of generated scenarios under `samples/java-spring-docker/benchmarks/`.
196
+ Regenerate directories with `springdocker benchmark generate`. Scenario **07** is listed after **06** and before **08** by id, even though the generator emits **08** before the native scaffold.
197
+
198
+ | ID | Directory | Purpose | Variants | Further reading |
199
+ |---|---|---|---|---|
200
+ | 01 | `01-multi-stage-build-structure` | Multi-stage Dockerfile layout vs a simpler two-stage build | `specialized-multi-stage`, `simple-two-stage` | [`benchmark-methodology.md`](docs/benchmark-methodology.md#current-sample-comparison-snapshot) |
201
+ | 02 | `02-buildkit-gradle-cache` | BuildKit cache mount on dependency rebuilds | `with-buildkit-cache`, `without-buildkit-cache` | [`benchmark-methodology.md`](docs/benchmark-methodology.md#current-sample-comparison-snapshot) |
202
+ | 03 | `03-custom-jre-jlink` | jlink vs vendor JRE on debian-slim vs stock Temurin image | `with-jlink-runtime`, `without-jlink-runtime`, `temurin-jre-image` | [`jvm-optimization.md`](docs/jvm-optimization.md), [`golden-samples.md`](docs/golden-samples.md) |
203
+ | 04 | `04-jep483-aot-cache` | JEP 483 ahead-of-time class-loading cache (Java 24+) | `with-aot-cache`, `without-aot-cache` | [`jvm-optimization.md`](docs/jvm-optimization.md) · extra runs: 8 (`quick`) / 15 (`full`) |
204
+ | 05 | `05-jvm-container-flags` | Tuned vs baseline JVM container flags | `tuned-flags`, `defaults-like` | [`jvm-optimization.md`](docs/jvm-optimization.md) |
205
+ | 06 | `06-base-image-choice` | Runtime base image tradeoffs (jlink on every base) | `alpine`, `debian-slim`, `ubuntu`, `distroless` (configurable) | [`benchmark-methodology.md`](docs/benchmark-methodology.md#configuring-base-image-variants-scenario-06) · CI regression baseline |
206
+ | 07 | `07-native-benchmark` | Native-image scaffold (`native-aot` recipe); skipped by default | _(single Dockerfile at scenario root — no variant dirs)_ | [`native-image-roadmap.md`](docs/native-image-roadmap.md) |
207
+ | 08 | `08-appcds` | AppCDS shared archive at build time | `with-appcds`, `without-appcds` | [`jvm-optimization.md`](docs/jvm-optimization.md) |
208
+
209
+ Example analyze commands (after `benchmark run`):
210
+
211
+ ```bash
212
+ springdocker benchmark analyze --project-root samples/java-spring-docker \
213
+ benchmarks/03-custom-jre-jlink/results/raw.csv --format table
214
+ springdocker benchmark analyze --project-root samples/java-spring-docker \
215
+ benchmarks/04-jep483-aot-cache/results/raw.csv --format json
216
+ springdocker benchmark compare --project-root samples/java-spring-docker \
217
+ benchmarks/03-custom-jre-jlink/results/raw.csv --baseline-variant with-jlink-runtime
218
+ ```
219
+
220
+ Current reports focus on:
221
+
222
+ - image size
223
+ - build duration
224
+ - startup latency
225
+ - success rate
226
+
227
+ Benchmark summaries can be rendered as:
228
+
229
+ - terminal tables
230
+ - JSON
231
+
232
+ ## Supported stack
233
+
234
+ | Layer | CI / fixtures | Reference sample (`samples/java-spring-docker/`) |
235
+ |---|---|---|
236
+ | Python CLI | 3.10–3.12 tested in CI | — |
237
+ | Build tools | Maven and Gradle (minimal fixtures + examples) | Maven and Gradle |
238
+ | Spring Boot | Projects with Spring Boot markers | 4.0.1 |
239
+ | Java in generated Dockerfiles | ≥17 (generator validation) | 25 default in sample config |
240
+
241
+ The reference sample uses bleeding-edge versions to stress-test generator output and benchmark scenarios. Your project does not need to match those versions to use the CLI.
242
+
243
+ ## Project docs
244
+
245
+ Documentation uses three status labels:
246
+
247
+ | Status | Meaning |
248
+ |---|---|
249
+ | **Implemented** | Shipped CLI behavior, runbooks, ADRs, or committed reference artifacts |
250
+ | **Experimental** | Scaffold or opt-in capability — documented with explicit limits; not production-ready |
251
+ | **Roadmap** | Planned capability or doc product not shipped yet |
252
+
253
+ ### Implemented
254
+
255
+ - `docs/project-detection.md` — Maven/Gradle detection boundaries and monorepo workflows
256
+ - `docs/POSITIONING.md` — product scope, CI guarantees, sample-tree strategy
257
+ - `docs/digest-pin-runbook.md`
258
+ - `docs/architecture.md`
259
+ - `docs/benchmark-methodology.md`
260
+ - `docs/golden-samples.md`
261
+ - `docs/extensions.md`
262
+ - `docs/security-hardening.md`
263
+ - `docs/observability.md`
264
+ - `docs/kubernetes.md`
265
+ - `docs/adr/README.md`
266
+ - `docs/multiarch.md`
267
+ - `docs/onboarding.md`
268
+ - `docs/team-adoption.md`
269
+ - `docs/troubleshooting.md`
270
+ - `docs/typing-roadmap.md` — gradual mypy strictness and module rollout plan
271
+ - `docs/jvm-optimization.md`
272
+ - `docs/distribution.md`
273
+ - `docs/presentation/README.md` — Reveal.js decks: ownership, update cadence, commit/publish policy
274
+ - `docs/example-gallery.md` — index of committed Dockerfile and benchmark JSON examples under `docs/examples/`
275
+ - `docs/compatibility-matrix.md` — descriptive support ranges (Java 17+, Python 3.10+, etc.)
276
+ - `SECURITY.md`
277
+ - `CONTRIBUTING.md`
278
+
279
+ ### Experimental
280
+
281
+ - `docs/native-image-roadmap.md` — `native-aot` recipe and scenario 07 scaffold; runner skips native by default; production native-image workflow remains roadmap
282
+
283
+ ### Roadmap
284
+
285
+ - `docs/benchmark-dashboard.md` — standalone trend dashboard (presentation decks and `benchmark analyze` JSON are substitutes today)
286
+
287
+ ## Comparison with adjacent tools
288
+
289
+ | Tool | Focus | What springdocker adds |
290
+ |---|---|---|
291
+ | Jib | Dockerless image build | benchmark-aware Dockerfile and runtime tuning workflows |
292
+ | Buildpacks | Opinionated platform build | explicit Dockerfile generation and benchmark artifacts |
293
+ | Manual Dockerfiles | Full control | project detection, config, and repeatable benchmark analysis |
294
+
295
+ ## Sample project docs
296
+
297
+ - `docs/golden-samples.md` - fixture walkthroughs, CI coverage, and variant coverage
298
+ - `samples/java-spring-docker/README.md` - full benchmark sample app
299
+ - `samples/java-spring-docker/HELP.md`
300
+ - `samples/java-spring-docker/k8s/kustomization.yaml`
301
+ - `samples/java-spring-docker/tools/README.md`
302
+
303
+ ## Contributing
304
+
305
+ Clone the repository and use an editable install — see [CONTRIBUTING.md](CONTRIBUTING.md). The main package is under `src/springdocker/`. Run `pytest` (≥80% line coverage gate), `ruff check src tests`, and `mypy src` before pushing changes.