7

We are using spring boot to develop Java backend services. application.properties file has the database configurations. We are deploying in 4 different environments (dev, test, UAT, and production). The properties files will be different for different environments. Where should we keep these files and how should do build promotion using Jenkins? We are using Apache Tomcat to deploy our WAR file.

Now we are keeping the properties file in C:\Program Files\Apache Software Foundation\Tomcat 7.0\conf directory and accessing using the following code.

@PropertySource(value = {"file:${catalina.base}/conf/application_dev.properties"})

Each time we deploy in different environments, we need to replace the respective file name application_test/uat/production.properties, commit the code and let Jenkins build and deploy. Now manually we have to keep the files in different environments too.

Please suggest how should we do it.

3 Answers 3

2

I disagree with @levi, you should stick to the 12 factor app method. Leaving configs in the repo scusk. Simple changes to a password or a small setting requires a commit and a pipeline rebuild. That's too much work for something so simple.

The last 3 companies I've worked for are using 'single branch' repos. There is only the master branch, and developers create 'feature' branches off of that. All the different environments are built from master.

In that case, we have a static config file, but all the values are injected by the build pipeline. You can pull them in from different sources: build server env variables, an external tool (Hashicorp Vault), or even another repo that has all the configs (this last one is least desirable).

2

The typical approach is to use a folder per environment that contains identically named files:

conf/dev/application.properties
conf/test/application.properties
conf/uat/application.properties
conf/prod/application.properties

Then parameterize the @PropertySource to use an environment variable such as "env":

@PropertySource("file:${catalina.base}/conf/${env}/application.properties") 

Now you simply need to ensure that in each environment there is an operating system environment variable defined called "env" that is set correctly (i.e, dev, test, uat or prod). You haven't stated how your launch your application (j2ee server launch script on linux? springboot docker container?) but it shouldn't be a problem to set the environment variable in each environment.

Rather than load files from "${catalina.base}" you can load files from the classpath with something like:

// see https://stackoverflow.com/a/26387933/329496
@PropertySource("classpath:${env}/application.properties")

If you are building with maven you can create the folders under src/main/resources and they will be copied into the classpath:

src/main/resources/dev/application.properties
src/main/resources/test/application.properties
src/main/resources/uat/application.properties
src/main/resources/prod/application.properties

That simplified things quite a lot. Jenkins doesn't need to do anything at all as the same build/release artefact can be run in any and every environment. It is usually an anti-pattern to have Jenkins have to do extra work to reconfigure an application to work in a particular environment. Rather you should aim to have a single Jenkins release job build an artefact that can be simply copied between environments.

There is one downside to such an approach. If you want to create a new environment you have to add a new file into source control and create a new release artefact. With legacy technologies setting up new environments is a lot of work (e.g., commissioning VMs and databases) such that this "extra step" of adding a file to the code doesn't seem like a problem.

With more modern cloud-native technologies like Kubernetes, you can set up environments in seconds, and "on-demand". In which case you don't want to be pre-specifying your environments within your codebase. Rather you should follow a 12factor.net approach and define every single property as an environment variable and have spring just use them directly. If you look at the current springboot documentation it uses environment variables by default. This means if you haven't defined a @PropertySource but you are using things like:

@Value("${name}")
private String name;

they will bbe set from the environment variable of the same name but using all caps. This then moves the problem to "how do we make sure that the environment variables are correctly set up in each environment". Technologies that make it easy to spin up environments in seconds (kubernetes, cloudfoundry, swarm) also typically make it easy to manage environment variables. With kubernetes you create a "ConfigMap" (e.g. "my-app-properties") and have it mounted as the environment variables where every key+value defines a unique environment variable for the application. Each logical environment can then be a separate Kubernetes namespace (possibly on a shared cluster for dev/test but a dedicated cluster for prod) that has its own "my-app-properties" configuration object that defines the environment variables for that logical environment .

You can put these settings under source control but not in the application source repo. Rather you can take an "infrastructure as code" approach where everything that runs the application (e.g., all scripts and yaml to set up the environments) is in it's own separate git repo. You can then set up a configuration deploy job that is triggered by a git webhook on the configuration repo. This can push out the new environment variables. That allows you to have continuous deployment of configuration changes that are independent of the continuous deployment of application code.

If you are using Kubernetes then Helmfile is an excellent choice for that is it won't update anything in kubernetes that hasn't changed within git. This means it is safe to reapply all the configuration in the git repo on every push to a protected master branch; helmfile will double check what is already deployed and only update Kubernetes configuration objects that need to be updated.

1
  • I should add that on the particular Kubernetes cluster and with the container images I am using I cannot set an environment variable named “ENV” I had to rename it “ENV_PREFIX”. I would recommend using something unique to your environment variables (such as a three char prefix) to avoid colliding built in environment variables or any logic that might wipe or block certain variables. It also makes it easy to filter out other people’s variables to see yours.
    – simbo1905
    Commented Dec 28, 2018 at 16:28
0

You could have a branch per environment. So a "dev" branch would build and deploy the "dev" app to the "dev" environment.

Though according to 12factor you should have your environment settings outside of your app repo. Though sometimes it's more work to do that than to just break that rule.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.