In my Spring Boot app I am wanting to externalise the properties to run in a Docker container. When first deployed, the properties that are currently in my-server/src/main/resources/application.yml
are loaded and used by the application as expected. All works fine.
However, my problem is that I need these properties to be updatable as needed, so I need access to the application.yml
file once on the Docker container. But at this point, it's not included in the build/docker/
directory before running the buildDocker
task, so won't be copied over or accessible after first deployment.
So, what I have tried is to copy the Yaml file into the docker/
build directory, copy it to an accessible directory (/opt/meanwhileinhell/myapp/conf
), and use the spring.config.location
property to pass a location of the config to the Jar in my Dockerfile:
ENTRYPOINT ["java",\
...
"-jar", "/app.jar",\
"--spring.config.location=classpath:${configDirectory}"]
Looking at the Command running on the Docker container I can see that this is as expected:
/app.jar --spring.config.location=classpath:/opt/meanwhileinhell/myapp/conf]
However, when I update a property in this file and restart the Docker container, it isn't picking up the changes. File permissions are:
-rw-r--r-- 1 root root 618 Sep 5 13:59 application.yml
The documentation states:
When custom config locations are configured, they are used in addition to the default locations. Custom locations are searched before the default locations.
I can't seem to figure out what I'm doing wrong or misinterpreting, but probably more importantly, is this the correct way to externalise the config for this type of Docker scenario?
Personally I'd use Spring Cloud Config Server instead of trying to set up properties files all over the place.
tl;dr it allows you to hold properties in git (which allows version control, branching etc) at a per environment/profile level in a centralised location, which are then served up by REST. Spring Boot has full support for it; in effect it's just another property source that ends up in your Environment.
https://spring.io/guides/gs/centralized-configuration/
Your approach is definitely a viable solution, however it is not recommended, since it makes your image not portable between different production and dev environments. Containers should be immutable and all environment configuration should be externalized.
For spring boot, there is very powerful project that allows you to externalize configuration. Its called Spring Cloud Config. The config server allows you to save your environment specific configuration in a git repository and serve the configuration to applications that need it. You basically just save the same application.yml in git and point the config server to the repository location.
Following this approach you can define multiple configuration files for different environments and keep your docker container immutable.
So I managed to get it working. Rather than passing the classpath to the directory in my DockerFile:
I instead tried passing the full location of the file:
This now updates upon restart of the Docker container.
I personally would consider two options:
Using an environment variable
Using
SPRING_APPLICATION_JSON
A variation on Xtreme Biker's answer, this time for deployment of a Spring boot war into a dockerized TomCat…
I recommend including a nominal
application.yml
in your app, but use Docker environment variables to override any individual keys which need environment-specific variation.The reason I recommend this approach (using Docker environment variables) is:
Spring Boot's Externalized Configuration docs explain two ways to supply environment via command-line:
SPRING_DATASOURCE_USERNAME=helloworld
)-Dspring.datasource.username=helloworld
)I prefer Java options, because they express an explicit intent: "this is intended for the following Java process, and only for that Java process".
Finally: I would use TomCat's
CATALINA_OPTS
as the mechanism for passing those Java options. Documentation fromcatalina.sh
:Because
CATALINA_OPTS
is an easier route than making your Docker image responsible for creating asetenv.sh
and passing the appropriate Docker env declarations into it.Build your
.war
artefact like so:We expect a
.war
artefact to be output by Gradle tobuild/libs/api-0.0.1-SNAPSHOT.war
.Use such a Dockerfile:
Build your Docker image like so:
Pass
CATALINA_OPTS
to your container like so:And a docker-compose variant looks like this:
DOCKER IMAGE CONFIGURATION
If you look to the way Spring recommends to launch a Spring Boot powered docker container, that's what you find:
That means your image extends openjdk and your container has its own environment. If you're doing like that, it would be enough to declare what you want to override as environment properties and Spring Boot will fetch them, since environment variables take precedence over the yml files.
Environment variables can be passed in your docker command too, to launch the container with your desired configuration. If you want to set some limit for the JVM memory, see the link below.
DOCKER COMPOSE SAMPLE
Here you have an example of how I launch a simple app environment with docker compose. As you see, I declare the
spring.datasource.url
property here as an environment variable, so it overrides whatever you've got in yourapplication.yml
file.See also: