A cartoon of me in a blue captain's uniform behind the helm of a ship at sea. Underneath is the title 'one chart to rule them all'

One Chart To Rule Them All

spring boot Mar 23, 2025

So you've written a Spring Boot application. You want to deploy it to a Kubernetes cluster so you Google how to go about it. You'll probably find solutions using Helm charts, which works for me. But all the suggestions/variations I found have one problem: they cloud the separation between Spring Boot and Kubernetes domains. In this post I will explain what I mean by this and show a solution that works for both domains without having to have much knowledge about the other.

For whom?

  • Spring Boot developers that don't (want to) know much about Kubernetes/helm charts
  • Helm (OPS) developers that don't know much about Spring Boot
  • DevOps people that are comfortable working in both domains, I think I can still simplify your work

The app

I've created an example app that has two REST endpoints that simply return a (configured) message. It also contains two extra endpoints that are switched on/off by feature toggles. You can find the git repo here:

GitHub - sanderv/one-chart: One chart to rule them all
One chart to rule them all. Contribute to sanderv/one-chart development by creating an account on GitHub.

See the README file for instructions how to run it.

Spring Boot configuration

When writing a Spring Boot application I like to start of with an application.yaml with my main configuration without any local config. If I want to talk to a local Postgres database, have debug logging etc. I don't want it in application.yaml. I add an application-local.yaml for this with my database config, feature toggles, log settings, etc.

demoApp:
  features:
    alpha: false
    beta: false
  messages:
    messageOfTheDay: "Today's a good day for coffee!"

application.yaml

Both feature toggles are set to 'off' (sensible defaults) and I've not yet thought of a demoApp.messages.quote which is missing from this file. Running the app with only this will result in an error (like missing a database URL).

For local development I do want to check the alpha feature and remind myself of important stuff. And of course fix the missing value for quote:

demoApp:
  features:
    alpha: true
  messages:
    messageOfTheDay: "Go outside and enjoy the sun!"
    quote: "May al your threads be lightweight"

application-local.yaml

If you run this app with the Spring profile local enabled, you will see the new messageOfTheDay when calling http://localhost:8080/messages/motd. Also the AlphaController is enabled, giving you http://localhost:8080/alpha/awesomeResult

Application tested, we can now move on to Kubernetes and we need a Helm chart for this.

How not to create your Helm chart

As Helm charts as a whole are another domain and not the focus of this post (I want to give you one chart, remember?) I won't go into much detail and just point out the flaws I see with the Google suggested approaches and present you with my solution.

The internet suggests these two solutions when creating a Helm chart for Spring Boot: use environment variables or define a ConfigMap. Both are shown in this blogpost:

Mastering Helm Charts for Spring Boot: Managing Common Properties
As a developer working with Spring Boot applications in Kubernetes, you’ve likely encountered the…

My problems with these solutions are:

  • Your Spring Boot configurable properties are now a part of the Helm domain (either in the Deployment or ConfigMap)
  • The actual values will be in a values.yaml that might be in the same layout as your application.yaml, but no guarantee
  • The Spring Boot properties and the values are now separated and must be glued back together with Helm 'coding'
  • Adding a property to your project involves changing Helm files and have you type more glue Helm code

All in all a complex process with a real possibility of errors.

Let Spring Boot be Spring Boot

Why create something new when we have something that we know and already works? Your carefully crafted Spring Boot application.yaml has a nice structure and sensible defaults. You've created an application-local.yaml with overrides/additions to those defaults. Let's just use this override approach in the Helm chart!

First, lets bring your application-local.yaml over to Helm, because the sensible defaults are available in application.yaml which is packaged with the Spring Boot app.

You can just copy your application-local.yaml contents into Helm's values.yaml, but not in the root of the yaml, we'll create an extra key (let's say springConfig) to set it apart from the actual Kubernetes stuff like replicas, resource limits etc.

replicas: 1

springConfig:
  demoApp:
    features:
      alpha: true
    messages:
      quote: "Kubernetes all the way!"

values.yaml

And of course you can add/remove properties here or change values, just like you would in application*.yaml.

The one element to bring these worlds together

Spring Boot has a lot of ways to bind configuration to your application as shown in the Spring Boot documentation. The best choice for this is the environment variable SPRING_APPLICATION_JSON which is explained here.

Now we need to get the springConfig from values.yaml into this environment variable, this can be done in one go:

    spec:
      containers:
        - name: helm-demo
          env:
            - name: SPRING_APPLICATION_JSON
              value: {{ .Values.springConfig | toJson | quote }}
  • This creates an env variable with the name SPRING_APPLICATION_JSON for the container running our Spring Boot app
  • The value of the variable is taken from the springConfig entry of the .Values
    • which is converted to JSON
    • and then surrounded with quotes (to prevent error messages from mixing JSON and YAML)

Deploy this chart using helm install helm-demo . -f values.yaml (for detailed instructions see the README in the git repo)

More than one environment?

In Kubernetes you'll probably have a test, acceptance and production environment (maybe more). For Spring we can use Spring Profiles to define different values for different profiles.

In Helm you would take a similar approach: just create a values-test.yaml and because the solution I've shown above is so generic, you can just override one property for test:

springConfig:
  features:
    alpha: true

application-test.yaml

To deploy this chart for your test environment, simply add the new values file to your commandline: helm install helm-demo . -f values.yaml -f values-test.yaml

Why this works

Of course there are things not covered in this post that require a bit more in-depth Helm/Kubernetes knowledge (e.g. password handling), but overall this solution has brought simplicity for several reasons:

  • Your whole Spring Boot configuration is kept together in values.yaml
  • It uses the same structure as your application*.yaml during development
  • Adding properties to your app configuration can be copy/pasted to values.yaml in your Helm chart
  • You only need very basic knowledge of Helm to change your configuration: the location of the values.yaml file
  • No accidental complexity or indirection/glue code (see the 'How not to...' above)
  • The Helm templates don't contain part(s) of your Spring Boot configuration, so the domains are nicely separated
  • Changes to your Spring Boot config in values.yaml will replace your pods automatically when applied to your cluster, no need to restart anything to activate your new config

And to summarize: I've promised you "one chart". All the magic is in these lines:

  env:
    - name: SPRING_APPLICATION_JSON
      value: {{ .Values.springConfig | toJson | quote }}

Just save this snippet, copy/paste it to make any Helm chart work with any Spring Boot app you write!

Tags