BootUI
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
  • Get started

    • Try the sample app
    • Setup
    • Spring WebFlux
    • Quarkus
    • Java agent
    • Activation and safety
    • Non-standard runtimes
    • Troubleshooting
  • Workshop

    • Workshop overview
    • 00 - Runtime understanding
    • 01 - Setup and tooling
    • 02 - Configuration and wiring
    • 03 - Runtime journal
    • 04 - Runtime Insights
    • 05 - Java instrumentation
    • 06 - Assessment and impact
    • 07 - Agent change loop
    • 08 - Going further
    • Participant worksheet
    • A - Prompts
    • B - Troubleshooting
    • C - Frameworks
    • D - Extensions
  • Features

    • All features
    • Scorecard
    • Live Activity
    • Runtime Insights
    • Advisors
    • Runtime
    • Configuration
    • Database
    • Security
    • Services
    • Diagnostics
    • Java Agent
    • Developer tools
  • Reference

    • Properties
    • Framework support
    • AI agents
    • Command line
    • BootUI family
    • Known limitations
  • Diagnostic checks

    • Architecture
    • REST API
    • Spring
    • Hibernate
    • Database
    • Security
    • Vulnerabilities
    • Memory
    • Pentesting
    • GraalVM readiness
    • CRaC readiness
    • Quarkus
    • Quarkus security
  • Contributing

    • Repository
    • Specification
    • Implementation plan
    • v2 plan
    • Try 2.0 early
    • v2 validation report
    • Releasing 2.0
    • Quarkus design notes
    • WebFlux design notes
  • Privacy

Non-standard runtimes

BootUI assumes a locally reachable web application. Command-line applications and containers each need one small adjustment.

Command-line (non-web) applications

The starter brings no web stack. To serve BootUI from a command-line application, add spring-boot-starter-webmvc (or spring-boot-starter-web) as well, providing Spring MVC and an embedded servlet container. When BootUI is active and both are on the classpath, it starts a servlet web server even if your application declares spring.main.web-application-type=none, or calls SpringApplication#setWebApplicationType(NONE). Your CommandLineRunner and ApplicationRunner beans still run as usual, and the application then keeps running so the console stays reachable.

Without Spring MVC and a supported embedded container, BootUI leaves the application non-web and exposes no console.

Set bootui.force-web=false to opt out and keep your declared web-application type. Applications that are already servlet web applications, or that are explicitly reactive, are left untouched, and because BootUI activates only in development by default, production is unaffected.

Spring Cloud bootstrap contexts are never forced

The transient bootstrap context created by spring-cloud-starter-bootstrap has no embedded web server, so forcing it would fail startup with MissingWebServerFactoryBeanException. BootUI detects that context, leaves it alone, and forces the servlet web type on your main application as usual.

Running inside a Docker container

When you publish a port and browse to http://localhost:8080/bootui, the request reaches the application from the Docker gateway, which is not a loopback address. BootUI fails closed and rejects it. Two settings fix that:

  1. Activate BootUI inside the container. A repackaged jar strips DevTools, and activation reads the active profiles rather than spring.profiles.default. Set SPRING_PROFILES_ACTIVE=dev or BOOTUI_ENABLED=ON. Without this you get a 404 on /bootui, not a rejection.
  2. Trust the container gateway. Set bootui.trust-container-gateway=AUTO. BootUI then detects the gateway addresses that published-port traffic arrives from and trusts only those /32 or /128 hosts as loopback-equivalent, on any Docker flavor.
docker run -p 127.0.0.1:8080:8080 \
  -e SPRING_PROFILES_ACTIVE=dev \
  -e BOOTUI_TRUST_CONTAINER_GATEWAY=AUTO \
  your-image

Then open http://localhost:8080/bootui from the host. Use ON instead of AUTO to trust a detected gateway even when the container heuristics are inconclusive.

How detection works, and what stays enforced

On Linux Docker Engine, BootUI reads the bridge default gateway from /proc/net/route, typically 172.17.0.1. On Docker Desktop the SNAT source is 192.168.65.1, which is not the route-table gateway, so BootUI resolves the gateway.docker.internal name that Docker Desktop injects into every container. The lookup is resolved once and cached.

Detection relaxes only the source-address check. The Host allow-list and cross-site-write protection stay in force, and sibling containers are not trusted, because their traffic carries their own address rather than the gateway's.

It also fails closed. On Linux Docker Engine and on bare metal, gateway.docker.internal does not resolve, which means no extra gateway. If that name is unavailable on Docker Desktop, verify the observed gateway and trust that single address through bootui.trusted-proxies, for example 192.168.65.1/32.

The published-port bind address matters

-p 8080:8080 binds 0.0.0.0:8080 on the host, so a LAN client reaching hostLanIP:8080 is SNAT'd to the same gateway. Trusting the gateway /32 therefore trusts anything that can reach the published port, which in this bind mode includes the LAN. That is why the feature is off by default. For strict loopback equivalence, bind the port to localhost: docker run -p 127.0.0.1:8080:8080 ….

Custom proxies, bridges, or LAN setups

If you front the application with a reverse proxy, use a custom Docker network, or otherwise reach BootUI from a source other than the detected gateway, use bootui.trusted-proxies. It trusts additional source ranges in CIDR notation while keeping the same Host and cross-site-write defenses. Pick the range that matches your Docker flavor:

# Linux Docker Engine: verify the actual gateway before trusting this one host
bootui.trusted-proxies=172.17.0.1/32
# Docker Desktop (macOS/Windows): if the observed gateway is 192.168.65.1
#bootui.trusted-proxies=192.168.65.1/32
# Accept the hostname you browse with (localhost is already a built-in loopback name)
bootui.allowed-hosts=localhost

Or as environment variables on the container:

docker run -p 127.0.0.1:8080:8080 \
  -e SPRING_PROFILES_ACTIVE=dev \
  -e BOOTUI_TRUSTED_PROXIES=172.17.0.1/32 \
  your-image

On Docker Desktop, use -e BOOTUI_TRUSTED_PROXIES=192.168.65.1/32 instead when that is the observed gateway.

Scope bootui.trusted-proxies as narrowly as you can. Prefer the observed gateway's /32 or /128 over a whole network, and keep it limited to trusted local sources. Check your own setup with docker network inspect bridge, under IPAM.Config.Gateway, or read the source address from the BootUI rejection log line. Reserve bootui.allow-non-localhost=true as a last resort.

Persisting console state across image rebuilds

BootUI keeps two developer-local files under .bootui/ in the application's working directory:

FileHolds
application-bootui.propertiesRuntime overrides created from the Configuration panel.
boot-ui.ymlAdvisor findings you dismissed, under a dismissedRules: node.

Inside a container that directory belongs to the image, so a rebuild starts from a clean slate: toggles return to their configured value and dismissed findings reappear. bootui.overrides-file fixes both at once, because BootUI resolves boot-ui.yml in the same directory as the configured overrides file on all three stacks. Point it at a mounted path:

services:
  app:
    environment:
      SPRING_PROFILES_ACTIVE: dev
      BOOTUI_TRUST_CONTAINER_GATEWAY: AUTO
      BOOTUI_OVERRIDES_FILE: /var/bootui/application-bootui.properties
    volumes:
      - bootui-state:/var/bootui

volumes:
  bootui-state:

BootUI creates the directory if it does not exist. Both files now survive docker compose up --build.

Set it from the environment, not from application.properties

An EnvironmentPostProcessor reads the overrides file before your configuration files are loaded, while the console's writes resolve the path later, from the bound properties. A bootui.overrides-file declared in application.properties therefore moves where overrides are written but not where they are read at startup, so the values you saved are silently not applied. Use the environment variable or a -D system property, as shown above, so both ends agree on one directory.

A value you want to hold across every environment belongs in configuration rather than in a file the console rewrites. BOOTUI_MCP_ENABLED=ON states that intent explicitly and is reapplied at every start, so the MCP Server panel's toggle — which is in-memory only and is never written to either file — cannot quietly become the new default.

The volume is the right tool for what a developer discovers while using the console, dismissals above all.

Committing a baseline of accepted findings

Because boot-ui.yml is a small, stable file, a team can commit it next to the application configuration and copy it into the image, so every rebuild starts from the same "these findings are known and accepted here" baseline:

# .bootui/boot-ui.yml
dismissedRules:
  - RAPI-MAP-004
  - HIB-FETCH-001
COPY .bootui/boot-ui.yml /var/bootui/boot-ui.yml

Two things to know. Dismissing from the console rewrites the whole file, so a read-only mount makes the Dismiss button fail. Either keep the directory writable, since a rebuild still restores the committed baseline, or set the advisor panels read-only with bootui.panels.<id>.read-only=true, which disables the dismiss and restore controls. Vulnerability dismissals are also keyed <vulnerability id>::<group:artifact> rather than by a bare rule id. See dismissing a vulnerability.

Any other top-level section in the file is preserved when BootUI rewrites it.

Prev
Activation and safety
Next
Troubleshooting

© 2026 Julien Dubois