One of the more common questions people ask when we are talking about Docker is, “How you would deploy the same image to both a development and production environment?” The conversation can head in a few different directions from this point, but all roads lead to separation of concerns and some form of configuration injection.
The volume container pattern is well known among Docker users. It is even mentioned in the official documentation on volumes. For the unfamiliar, volume containers are containers that simply act as a handle to a set of volumes. These are used as sources for other containers started with the volumes-from flag which copies volume handles from one container to another. It is common to see people create volume containers that simply echo some statement before stopping because containers maintain their handle after they’ve stopped.
I think this practice stops a bit short. I enhance this pattern in chapter 4 of my book, Docker in Action (use wm122214cc for 50% off until December 25th). What I call data packed volume containers are volume containers that make some static content available for other containers by copying content from their image to declared volumes at startup. Consider the following Dockerfile:
ADD . /shippedConfig
CMD cp /shippedConfig/* /config/
You could use this Dockerfile to make whatever configuration you want available to a volume mounted at /config. You could use it to package your dev environment configuration by building an image from this Dockerfile in a directory with dev versions of your configuration and tag the resulting image something like myApp/config:dev. Then make sure that myApp/config:dev is installed on every machine in your development fleet, and a container has been created from it. Lets walk through a brief example.
# setup some dev and production config
cp Dockerfile ./dev/
echo "This is dev" > ./dev/config.txt
cp Dockerfile ./prod/
echo "This is prod" > ./prod/config.txt# build the images
docker build -t test/config:dev .
docker build -t test/config:prod .# start the packed volume container for dev
docker run --name config test/config:dev
At this point the host has been primed. There is a container named “config” that we know provides the configuration we need. Consumers will not have specific visibility into which environment it is providing configuration for and that is a good thing. Start up a consumer, just to investigate the config.
# consume the configuration
docker run -—rm -it -—volumes-from config busybox:latest /bin/sh -c ‘cat /config/config.txt’This is dev
It looks like this host is configured for dev since the contents of the config file say so. You can change this without changing your application by simply creating the config container from the other image.
docker rm -v config# start the packed volume container for dev
docker run --name config test/config:prod
Now run the exact same consumer container…
# consume the configuration
docker run -—rm -it -—volumes-from config busybox:latest /bin/sh -c ‘cat /config/config.txt’This is prod
Boom. A simple decoupled mechanism for distributing your configuration. The real work in this is standardizing on a set of mount points where your application looks for its configuration and a container name (if you want to standardize your launch commands).
For some there won’t be anything new here, but my hope is that this pattern helps to show the kind of flexible structures you can build with the awesome primitives provided by Docker.
If you’re interested learning more the Docker in Action MEAP and all other Manning Publications are available for 50% off until December 25th at manning.com with promo code wm122214cc.