The best practice is to create a mechanism that is reusable and can be changed on multiple levels. Many docker containers that are aimed for wide use follow this approach.
Hierarchical configuration
The module needs to support hierarchical configuration. What this means is that there will be more than one way to set-up variable and there will be a strict hierarchy, based on priority, which value will be the final value. Best practice for the hierarchy is as follows:
- Set the value to a default value
- Check if there is new value in the configuration file, and if so overwrite the value
- Check if there is new value from the environment variables, and if so overwrite the value
- Check if there is there is new value in the startup flags, and if so overwrite the value.
This approach is applicable to the node CLI arguments, with the exception that the hierarchy is shallower and stops at the environment variable setup.
After this chain of checks, you will have the final value for the variable.
Application configuration variables
Node.js has a good library called nconf that you can find on github. The library is allowing you to do hierarchical configuration. Here is simple example (taken from the examples in the library, trimmed for space)
var nconf = require('nconf');
// 1. any overrides
nconf.overrides({
'always': 'be this value'
});
// 2. `process.env`
// 3. `process.argv`
nconf.env().argv();
// 4. Values in `config.json`
nconf.file('/path/to/config.json');
// 5. Any default values
nconf.defaults({
'if nothing else': 'use this value'
});
Wrap this in your own module and add the additional logic you need for the handling of the configurations
var myConfig = require('nconf');
..
...
..
module.exports = myConfig;
then use the configuration from your code as follows
var config = require('./myConfig');
config.get('hello');
This will allow you to set the default configurations in the image and overwrite them in subsequent images. Here is an example, the first image base will have the hello flag set from the file during the build and the second image will use ENV to overwrite the setting:
FROM debian:latest as base
COPY ./hello_config.json /path/to/config.json
...
...
FROM base AS second
ENV hello="Hello world from second image!"
...
...
Now when you create a container, you can overwrite the changes either by passing new environment file (--env-file) or a singe flag (-e or --env)
docker run --env hello="Hi from container!" --env-file ./new_conf.list secod
Note that the environment file should be in a shell format FLAG=VALUE and not in json format.
Node CLI Arguments
The Node CLI arguments can also be handled with this approach (the only difference is that the hierarchy is stops at the CLI). The container should define the NODE_OPTIONS environment variable with the required flags in the Dockerfile as follows:
...
ENV NODE_OPTIONS="--max_semi_space_size 64 --inspect=0.0.0.0"
...
This can be enhanced to accept build option as well as to build the variable gradually
...
ARG NODE_OPTIONS=""
ENV NODE_OPTIONS="${NODE_OPTIONS} --max_semi_space_size 64"
ENV NODE_OPTIONS="${NODE_OPTIONS} --inspect=0.0.0.0"
...
now, during the build we can specify additional options as follow
docker build --build-arg NODE_OPTIONS="--cpu-prof --heapsnapshot-near-heap-limit=3" .
Same as the application configuration, you can use the -env or --env-file file in order to modify it for specific container.