Should config be built into a Docker image (best practice)

Viewed 462

I am currently looking to deploy a Docker application that uses an Nginx proxy and a MySQL instance (amongst other things).

What is the best practice when it comes to configuration files (in the case of Nginx) or initial table set up and server configuration (in the case of SQL)?

Is it generally better to build this config into a custom image in the Docker file (so take the standard Nginx image as a parent and copy the config into the image and build it) or to build the standard Nginx file in the docker-compose file and use volumes and bind mounts to bring the config into the image at the time of deployment? (and the analogous questions for a SQL container).

1 Answers

There's a bit of "it depends" here. The big question is, how much do you expect the person running the container to need to modify the configuration?

If the configuration is totally fixed then just build it into the container. A good example of this is an nginx configuration file for proxying a set of other containers in a Docker Compose setup: if you think the host names for the other containers you will use will never change, it's easier to build it into the image.

If the configuration has a limited number of things that can change, but the configuration language allows variable substitutions, then compile a config file referencing environment variables into the image, and use environment variables to adjust the configuration. The prototypical example of this in my mind is a Rails database.yml.erb file where you can substitute

host: <%= ENV['MYSQL_HOST'] %>
docker run --net some_network -e MYSQL_HOST=mysql myimage

If the configuration has a limited number of things that can change, you can also apply variable substitutions at startup time. sed can do this fine; if you otherwise have the GNU tools available (perhaps your image is Debian or Ubuntu-based) envsubst can do this straightforwardly. An entrypoint script can do this before you start the main program.

#!/bin/sh

# Fill in runtime values for configuration
sed -e "s/MYSQL_HOST/$MYSQL_HOST/" < database.conf.tpl > database.conf

# Run the CMD from the Dockerfile
exec "$@"
...
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["myapp"]
docker run --net some_network -e MYSQL_HOST=mysql myimage

If there are many complex configuration choices or you expect the user to just wholesale replace the configuration, bind-mount it in.

docker run -v $PWD/application.conf:/app/application.conf myimage

If you're actually deploying into Kubernetes you can put the configuration file in a ConfigMap object, which essentially behaves the same way as the bind-mount option. If you're also using a tool like Helm to manage the deployment then you can use its templating layer to set the ConfigMap content. The Helm templating language is fairly involved and has loops and conditionals, so you can build up a complex configuration file based on deploy-time settings.


You also mention databases. The standard SQL database containers support placing content in a /docker-entrypoint-initdb.d directory, and this will get run the very first time the database starts up. I'd minimize the use of this, and prefer creating tables via a database-migration system. Mostly this is because those scripts only get run when the database is created the very first time, and you'll need a migration system anyways; you don't want to need to delete all of your data every time you change your schema.

Related