5 min read

Spring Boot application.properties in Kubernetes

What is the best way to keep environment-specific variables when migrating a Spring Boot application to Kubernetes? Should you create different “application.properties” files and use profiles? Use environment variables? What about security?

A common friction point between developers and DevOps is around injecting specific environment settings to the application. Some developers don’t think ahead, and DevOps rarely provides clear guidance on what should go where.

We’re finally past the “hardcoded” connection strings in the codebase days (are we really?).
In Java Spring Boot, the application.properties, or application.yaml files, give you a few options for customization:

Profiles

One way of supporting multiple environments is to keep multiple application.properties files in the project’s “resource” folder. You will keep one copy of the file for each environment, and specify a profile when running the Spring Boot application.

applicaiton.properties
development-application.properties
staging-application.properties

To specify a profile when running the app, use the file prefix in the profile option:

java -Dspring.profiles.active=staging -jar unicornapp.jar 

Once you compile the application, the files are bundled in the JAR and ready for use by specifying the profile name.

That works fine for “simple” and straightforward deployments but doesn’t cut it for modern dockerized environments.
On top of that, you don’t want to keep credentials, secrets, or any sensitive information in your source control. This is why this solution should only be used for local development environments.

Environment Variables

Spring Boot allows you to use environment variables in the application.properties file and even use default values in case they are not set.

spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/dbname

I In the above example, you can see that the hostname is set to an environment variable “MYSQL_HOST” with a default value of “localhost”.

So if a developer runs the app without setting any environment variables, the connection string would be:

jdbc:mysql://localhost:3306/dbname

And if in production you will set the MYSQL_HOST environment variable to mysql-prod the connection string will be:

jdbc:mysql://mysql-prod:3306/dbname

When you assign values to properties, try to figure out if they would change in different environments and avoid hard coding them. That includes connection strings, file locations, names, etc.

External application.properties files

Next, there’s a handy feature of the Spring Boot application.properties file you can use - You can override the original values by placing an external application.propeties file next to the JAR on the server.

your-unicorn-app.jar
application.properties 

When you do that, Spring Boot will override merge the two files and will give precedence to the values in the external file.

So if the bundled application.properties file has the following values:

logging.level.org.springframework=INFO
logging.level.root=WARN
logging.level.com.baeldung=TRACE

## Database
spring.datasource.url=jdbc:mysql://localhost:3306/dbname
spring.datasource.username=user
spring.datasource.password=password

spring.jpa.hibernate.ddl-auto=create

And the application.properties file you placed next to the jar has the following values:

logging.level.root=ERROR
spring.datasource.url=jdbc:mysql://msql-prod:3306/dbname
spring.datasource.username=dbusername
spring.datasource.password=FancyPassword

server.port=8085

Then the effective settings will be:

logging.level.org.springframework=INFO
logging.level.root=ERROR
logging.level.com.baeldung=TRACE

## Database
spring.datasource.url=jdbc:mysql://msql-prod:3306/dbname
spring.datasource.username=dbusername
spring.datasource.password=FancyPassword

spring.jpa.hibernate.ddl-auto=create
server.port=8085

As you can see, Spring Boot merged the values.

What’s the best way to set values when we deploy in Kubernetes?

application.properties in Kubernetes

There are various ways to set environment specific settings in Kubernetes, and my tendency is to use environment variables when possible.
I do prefer using the mounted application.properties files in the form of ConfigMaps and Secrets in the following cases:

  1. If there are many values to override - otherwise, the configuration can quickly get unreadable.
  2. I don’t like having credentials, tokens, etc., in environment variables. At one point, I even got some pushbacks from a few large enterprises.
  3. When you don’t have a choice - the developers didn’t allow for overriding the values from environment variables. That happens as well.

Passing environment variables to the container in Kubernetes

Let’s say that you want to update the spring.datasource.url by using an environment variable.
If the original entry in application.properties is set to:
jdbc:mysql://${MYSQL_HOST:localhost}:3306/dbname

Where MYSQL_HOST is the environment variable for the hostname. Just add the following to the deployment YAML:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demowebapp
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: demowebapp
        image: registry.gitlab.com/unicorn/unicornapp:1.0
        ports:
        - containerPort: 8080
        imagePullPolicy: Always
        env:
          - name: MYSQL_HOST
            value: mysql-prod

Now, suppose that we want to use a secret to update the spring.datasource.password.
First, we need to create a secret:

apiVersion: v1
kind: Secret
metadata:
  name: datasource-credentials
type: Opaque
data:
  dbuser: ZGJ1c2VybmFtZQ==
  dbpassword: RmFuY3lQYXNzd29yZA==
 

Note that the values in the YAML file are base64 encoded.
Apply that to your cluster to create the secret.
Then, we can mount the secrets as environment variables:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demowebapp
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: demowebapp
        image: registry.gitlab.com/unicorn/unicornapp:1.0
        ports:
        - containerPort: 8080
        imagePullPolicy: Always
        env:
          - name: MYSQL_HOST
            value: mysql-prod
	      - name: MYSQK_USER
	        valueFrom:
	          secretKeyRef:
	            name: datasource-credentials
	            key: dbuser
	      - name: MYSQL_PASSWORD
	        valueFrom:
	          secretKeyRef:
	            name: datasource-credentials
	            key: dbpassword

Mounting an application.properties file next to the JAR

Kubernetes comes with two options we can use to store our application.properties file:

  1. ConfigMap
  2. Secret

The difference between them is that ConfigMaps are not supposed to contain sensitive information, and are a tiny bit easier to work with.
Secrets, on the other hand, are meant for storing sensitive information and offer better security.

Because in most cases, the values that you will need to inject to the application.properties files will contain sensitive environment-specific information, I would recommend choosing secrets.
If you want to use ConfigMaps instead, read this document for more information

For this example, lets assume that the applicaiton.properties file content for the production environment is:

spring.datasource.url=jdbc:mysql://msql-prod:3306/dbname
spring.datasource.username=dbusername
spring.datasource.password=FancyPassword

I will base64 encode the file and copy the result, then paste it as the value for the application.properties key:

apiVersion: v1
kind: Secret
metadata:
  name: application.properties
type: Opaque
data:
  application.properties: c3ByaW5nLmRhdGFzb3VyY2UudXJsPWpkYmM6bXlzcWw6Ly9tc3FsLXByb2QzMzA2L2RibmFtZQpzcHJpbmcuZGF0YXNvdXJjZS51c2VybmFtZT1kYnVzZXJuYW1lCnNwcmluZy5kYXRhc291cmNlLnBhc3N3b3JkPUZhbmN5UGFzc3dvcmQK

Don’t commit this file to your source control “as is” thinking that the values encrypted.

An alternative way of creating this secret is to load it directly from a file. You can read more about it here.

We now want to mount the secret as a file and place it next to the JAR. So assuming that the JAR is located at /opt/unicorn/your-unicorn-app.jar :

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demowebapp
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: demowebapp
        image: registry.gitlab.com/unicorn/unicornapp:1.0
        ports:
        - containerPort: 8080
        imagePullPolicy: Always
		volumeMounts:
            - name: application-properties
              mountPath: "/opt/unicorn/application.properties"
              readOnly: true
              subPath: application.properties
  	  volumes: 
		- name: application-properties
          secret:
            secretName: application.properties

I would highly recommend checking out Helm, as it allows you to use templates, value files, and other neat features that will help you create a better workflow.

Feeling overwhelmed with all the different tools and concepts in the DevOps world?

An email that dives deep into subjects that are all DevOps

    We won't send you spam. Unsubscribe at any time.
    Powered By ConvertKit