Summary
The Go-migrated buildpack does not inject SERVER_PORT=$PORT into the runtime environment for Spring Boot JAR applications. This was done explicitly in the Ruby buildpack. As a result, any Spring Boot app that sets server.port in application.yml (or any Spring config file) will bind to the wrong port at startup, causing the CF health check to fail and the app to be killed. When the configured port is a privileged port (below 1024, e.g. 443), the JVM throws a java.net.BindException: Permission denied immediately on startup.
Background
In the Ruby buildpack, lib/java_buildpack/container/spring_boot.rb always injected the following in its release method:
@droplet.environment_variables.add_environment_variable 'SERVER_PORT', '$PORT'
This ensured that SERVER_PORT was always set to CF's assigned $PORT at runtime, regardless of what server.port was set to in the app's configuration. Spring Boot's property priority means SERVER_PORT (env var) always beats server.port (config file).
The Go buildpack's src/java/containers/spring_boot.go has no equivalent. Only spring_boot_cli.go sets SERVER_PORT, so Spring Boot CLI apps are unaffected.
Impact
Spring Boot apps that define server.port in their configuration (a very common pattern in multi-environment codebases) will fail in one of two ways depending on the configured port:
Port ≥ 1024 (e.g. server.port: 9090):
- App binds to the configured port instead of CF's
$PORT
- CF routes traffic to
$PORT, which nothing is listening on
- Health check fails → app is repeatedly killed and restarted, never becoming healthy
Port < 1024 (e.g. server.port: 443, server.port: 80):
- CF containers run as unprivileged users — binding to ports below 1024 requires root
- JVM throws
java.net.BindException: Permission denied immediately on startup
- App crashes before it can serve any traffic
Steps to Reproduce
Case 1 — wrong port:
- Create a Spring Boot JAR application with the following in
application.yml:
- Push with the Go-migrated buildpack
- Observe the app fails the health check and never starts
Case 2 — privileged port:
- Create a Spring Boot JAR application with the following in
application.yml:
- Push with the Go-migrated buildpack
- Observe the app crashes immediately with:
java.net.BindException: Permission denied
at java.net.PlainSocketImpl.socketBind(Native Method)
...
Expected Behaviour
Same as with the Ruby buildpack: SERVER_PORT=$PORT is injected at runtime so the app always binds to CF's assigned port, regardless of any server.port value in the app's own configuration. The privileged port crash is also prevented since CF's $PORT is always an unprivileged port.
Proposed Fix
A likely fix would be to write SERVER_PORT pointing to $PORT during the finalize phase in src/java/containers/spring_boot.go, mirroring the Ruby buildpack:
if err := s.context.Stager.WriteEnvFile("SERVER_PORT", "$PORT"); err != nil {
return fmt.Errorf("failed to write SERVER_PORT: %w", err)
}
spring_boot_cli.go already does this and could serve as a reference. The same approach would probably need to be applied to spring_boot.go, which handles standard Spring Boot JAR and exploded JAR deployments.
Related
- Ruby buildpack reference:
lib/java_buildpack/container/spring_boot.rb (release method)
- Go buildpack gap:
src/java/containers/spring_boot.go (Finalize method)
spring_boot_cli.go line 112 — correct implementation for CLI apps
Summary
The Go-migrated buildpack does not inject
SERVER_PORT=$PORTinto the runtime environment for Spring Boot JAR applications. This was done explicitly in the Ruby buildpack. As a result, any Spring Boot app that setsserver.portinapplication.yml(or any Spring config file) will bind to the wrong port at startup, causing the CF health check to fail and the app to be killed. When the configured port is a privileged port (below 1024, e.g.443), the JVM throws ajava.net.BindException: Permission deniedimmediately on startup.Background
In the Ruby buildpack,
lib/java_buildpack/container/spring_boot.rbalways injected the following in itsreleasemethod:This ensured that
SERVER_PORTwas always set to CF's assigned$PORTat runtime, regardless of whatserver.portwas set to in the app's configuration. Spring Boot's property priority meansSERVER_PORT(env var) always beatsserver.port(config file).The Go buildpack's
src/java/containers/spring_boot.gohas no equivalent. Onlyspring_boot_cli.gosetsSERVER_PORT, so Spring Boot CLI apps are unaffected.Impact
Spring Boot apps that define
server.portin their configuration (a very common pattern in multi-environment codebases) will fail in one of two ways depending on the configured port:Port ≥ 1024 (e.g.
server.port: 9090):$PORT$PORT, which nothing is listening onPort < 1024 (e.g.
server.port: 443,server.port: 80):java.net.BindException: Permission deniedimmediately on startupSteps to Reproduce
Case 1 — wrong port:
application.yml:Case 2 — privileged port:
application.yml:Expected Behaviour
Same as with the Ruby buildpack:
SERVER_PORT=$PORTis injected at runtime so the app always binds to CF's assigned port, regardless of anyserver.portvalue in the app's own configuration. The privileged port crash is also prevented since CF's$PORTis always an unprivileged port.Proposed Fix
A likely fix would be to write
SERVER_PORTpointing to$PORTduring the finalize phase insrc/java/containers/spring_boot.go, mirroring the Ruby buildpack:spring_boot_cli.goalready does this and could serve as a reference. The same approach would probably need to be applied tospring_boot.go, which handles standard Spring Boot JAR and exploded JAR deployments.Related
lib/java_buildpack/container/spring_boot.rb(releasemethod)src/java/containers/spring_boot.go(Finalizemethod)spring_boot_cli.goline 112 — correct implementation for CLI apps