In various scenarios, it is common practice to read configuration information from environment variables, rather than configuration files. This document details the scenarios in which this practice is used, its advantages and how to do so.
1) Typical case and good practice using Docker Containers
One of the main benefits of the use of containers is that they can be mounted on any boat.
In technical terms: The idea is that the docker image that you use for testing could be used for production; this way you have certainty that what you tried, is what you execute in production.
The solution that the industry uses, in this case, is Environment Variables.
In practice: When the container is instantiated, it is said to execute with these and those values for these and those environment variables. And the application that runs in the container reads those environment variables.
Without this, the connection options to the databases are hardcoded into a connection file that is part of the web app that goes into the docker image (the container). So the container that you use in your testing environment is not the one you can use in your production environment, where it has to be attached to another database with other credentials.
2) Security, Independence of development and production teams: Case of Deployment of a war or zip
Without the use of Environment Variables, the only way to reuse the deployment package for production is to know the Database credentials and other properties of production when you create the package using the GeneXus IDE, something that is not viable in many companies or projects.
3) Cloud Native: In a PaaS environment, when connecting against a Database service, Cloud providers propose specific environment variables maintained by them. With this, they guarantee that if they later change the Database service URI (for example after a restore) requiring other credentials or connection options, the applications automatically take those new values without having to change other things in the web servers.
4) Agility in testing: If you run an application in a Docker container, you might want to run it with some configuration, and then with another; for example, to test how its behavior varies, or how it behaves depending to which Database is connected.
Each configuration entry of an application can be read from an environment variable. This environment variable must be prefixed with 'GX_' and it must be all in capital letters.
For the case of .NET, the variable must be called the same as it is defined in the web.config.
Suppose you have a Docker Image (named "environmenttest.netenvironment") with a web app that points to a production DB. Now you want to raise an instance of that image pointing to the Test DB (named "EnvTest"). For that you execute the following command:
docker run --rm -e GX_CONNECTION-DEFAULT-DB=EnvTest environmenttest.netenvironment
where the flag -e sets the environment variable GX_Connection-Default-DB (same name as it has in the web.config) to "EnvTest". When the application starts and wants to read the value of that property, it first checks if there is an environment variable with that name. As it exists, it takes that value.
If on the contrary, you want to raise the same image (environmenttest.netenvironment) against the production DB (EnvProd), execute the following command:
docker run --rm -e GX_CONNECTION-DEFAULT-DB=EnvProd environmenttest.netenvironment
The same works for any configuration property of the web.config. Whenever you are going to look for a configuration property, the application first searches for an environment variable with that name.
.NET Core works just like .NET, but the settings are stored in a file called appsettings.json
The case of Java is a bit different because Java has the client.cfg that works differently, it has sections.
For example, if you have a section called com.environmenttest|DEFAULT (which is the default DBMS of your environment) where there is a DB_URL entry that has the url to the DBMS, for example jdbc:jtds:sqlserver://172.16.3.21:1435/EnvProd.
If you want to change that value, you have to create an environment variable concatenating the section and the property, all in upper case, replacing points and pipes with an underscore. For example, if you create the variable GX_COM_ENVIRONMENTTEST_DEFAULT_DB_URL, you overwrite the value of the client.cfg: The value of that variable becomes the one that the app handles.
In this case, to launch the container with this variable you have to execute the following command to connect to the test DB:
docker run --rm -p 8890: 8080 -e GX_COM_ENVIRONMENTTEST_DEFAULT_DB_URL=jdbc:jtds:sqlserver://172.16.3.21:1435/EnvTest environmenttestjavaenvironment
and the following for the production DB:
docker run --rm -p 8890: 8080 -e GX_COM_ENVIRONMENTTEST_DEFAULT_DB_URL=jdbc:jtds:sqlserver://172.16.3.21:1435/EnvProd environmenttestjavaenvironment
This feature also works with Cloud Services properties. For instance, if you configured your application to use Microsoft Azure as your Storage Provider, you will have to set some properties like the account details and container names. These properties can also be modified at runtime via environment variables. These variables must be also prefixed with 'GX_' and must contain the type of the service and the name of the property all in capital letters. So, if you want to modify the Public Container Name, you can create a variable called GX_STORAGE_PUBLIC_CONTAINER_NAME with the value of your choice. For further reference of the Types and property names, you can open the generated CloudServices.config file.
There are cloud providers that already provide environment variables with the values to connect to the different services.
If the provider does not provide environment variables for the purpose, you can define your own.
In any case, to use those variables, a specific file allows you to declare the mapping between the environment variables defined at the cloud provider and the configuration property. This file is called confmapping.json and contains a json octopus (property value) with the necessary mapping.
Amazon provides the environment variables RDS_USERNAME and RDS_PASSWORD (among others) where the corresponding values of the RDS are already loaded. For that case, you can have the file confmapping.json in the WEB-INF directory of your web app with the following json:
When the web app wants to raise the USER_ID property of the com.environmenttest |DEFAULT section, it will read the value of the RDS_USERNAME environment property (and the same with the USER_PASSWORD / RDS_PASSWORD).
In the case of Net Core, and .Net, the confmapping.json should be like the following (where XXX and YYY are variables defined in the Cloud provider)
Note: To test what value is being taken into account or what environment variables are set, you may use ConfigurationManager external object
There is some configuration information that must not be changed through environment variables. The reason is that not all the program parts (eg. gxcfg.js) take into account those changes and inconsistencies may arise.
This is a list of those exceptions (the names of the following configuration entries correspond to .NET):
This feature is available as of GeneXus 15 upgrade 12
How to Deploy an Application to Docker