Skip to content

SERVER_PORT=$PORT not injected for Spring Boot JAR apps #1255

@stokpop

Description

@stokpop

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):

  1. App binds to the configured port instead of CF's $PORT
  2. CF routes traffic to $PORT, which nothing is listening on
  3. Health check fails → app is repeatedly killed and restarted, never becoming healthy

Port < 1024 (e.g. server.port: 443, server.port: 80):

  1. CF containers run as unprivileged users — binding to ports below 1024 requires root
  2. JVM throws java.net.BindException: Permission denied immediately on startup
  3. App crashes before it can serve any traffic

Steps to Reproduce

Case 1 — wrong port:

  1. Create a Spring Boot JAR application with the following in application.yml:
server:
  port: 9090
  1. Push with the Go-migrated buildpack
  2. Observe the app fails the health check and never starts

Case 2 — privileged port:

  1. Create a Spring Boot JAR application with the following in application.yml:
server:
  port: 443
  1. Push with the Go-migrated buildpack
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions