Docker Config, how to always use base image with Docker Swarm!

Remember last time you say "It's a really awesome image ٩(◕‿◕。)۶ ! But I need only to change one thing in the configuration, and I make a new image, a new git repository, a build process, and push my new image to a registry (╥_╥)"

Now (ok, a few months ago), it's possible with Docker Swarm to just add a configuration to your image!

Let's try it with Prometheus, a monitoring system and time series database.

Prometheus scrape an endpoint and store the metric in the internal time series database. But, when you add a new service, you need to change the Prometheus configuration for adding it. Who says changing a configuration, says recreating an image.

But, no more!

This is the basic docker-compose file that we will change for adding the new configuration without changing the base image:

# prometheus.yml
version: "3.4"
services:
  prometheus:
    image: prom/prometheus:v2.2.0
    networks:
      - monitoring
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention=24h'

Note: Docker configs are only available to swarm services, not to standalone containers. To use this feature, consider adapting your container to run as a service with a scale of 1.

Adding a configuration to your container

For adding a configuration to Docker, we can create it by hand with docker config create <config name> <file name or stdin> and use the configuration name in our docker-compose file or we can manage it directly inside the docker-compose file:

version: "3.4"
configs:
  <config name>:
    file: <file name>
    external: true|false # If the configuration was created outside this docker-compose
                         #  file, we need to set it to true

services:
  <service name>:
    configs:
      - source: <config name from before>
        target: <path inside the container where the configuration will be mounted>

Now, if we update our previous stack:

# prometheus.yml
version: "3.4"
configs:
  prometheus_config:
    file: ./prometheus.yml

services:
  prometheus:
    [...]
    configs:
      - source: prometheus_config
        target: /etc/prometheus/prometheus.yml

Rotate a configuration

Keep in mind that configurations are immutable, so you can’t change the file for an existing service. Instead, you create a new configuration to use.

Why? Because with an immutable configuration, you can be sure that all your containers inside a service always have the same configuration, and this gives you the opportunity to change it everywhere at once.

To rotate a service, you need to:

  • create a new configuration with a different name
  • update the service by removing the old config
  • add the new one to your service and use the same mount point

You can do this by using the cli:

$ docker service update \
    --config-rm prometheus_config \
    --config-add source=prometheus_config_v2,target=/etc/prometheus/prometheus.yml \
    <stack-name>_prometheus

Or by updating the compose file and by redeploying the stack:

# prometheus.yml
version: "3.4"
configs:
- prometheus_config:
+ prometheus_config_v2:
    file: ./prometheus.yml

services:
  prometheus:
    [...]
    configs:
-     - source: prometheus_config
+     - source: prometheus_config_v2
        target: /etc/prometheus/prometheus.yml
$ docker stack deploy -c prometheus.yml <stack-name>

Conclusion

With Docker Configuration, you have a lot of base image that you can use without building and maintain a custom image like nginx, prometheus.

If you want to learn more about Docker configuration, you have a complete guide available within the docker documentation: Store configuration data using Docker Configs

If you find a typo, have a problem when trying what you see in this article, please contact me!