Configure docker-compose files for multiple environments
I want to share an interesting solution I’ve used to configure my docker-compose setup for multiple environments. While I’m not using Kubernetes nor Swarm here, a pure docker-compose deploy is fine for small projects IMO.
The problem I’ve faced so far is specifying how each YAML block of the docker-compose.yml
file should be used for
different environments: which values should be merged and which values should be completely rewritten.
Problematic
There’s actually a build-in possibility to use multiple compose files and extend one from the other like so:
But I don’t really like how the things work with this type of inheritence. The YAML blocks are actually merged instead of being rewritten:
For the multi-value options ports, expose, external_links, dns, dns_search, and tmpfs, Compose concatenates both sets of values
Example: Merging instead of overriding
Just suppose that you want to rewrite ports
section of the docker-compose.yml
file in the
docker-compose.production.yml
.
So you’ll redefine it completely and populate it with new values:
...
myawesomeservice:
ports:
- "443:8080"
But now you’ll suddenly get surprised. Try running the config
command to see the resulting configuration set:
$ docker-compose -f docker-compose.yml -f docker-compose.production.yml config
networks:
...
services:
myawesomeservice:
...
ports:
- "80:8080"
- "443:8080"
...
The ports
section now contains the ports from the both files. That isn’t something we’ve actually supposed to see.
Solution
If you really want to have these sections completely different for both environments, you’ll need to create
three files: docker-compose.yml
, docker-compose.dev.yml
and docker-compose.production.yml
and so your
configuration will become a bit more complex than.
You can also use docker-compose.override.yml
instead of the docker-compose.dev.yml
so you can start your
local environment with a simple:
$ docker-compose up -d
Cause it is the default name for the configuration overrides:
By default, Compose reads two files, a docker-compose.yml and an optional docker-compose.override.yml file
But you’all still finish with three files and you’ll have to change all of them time to time.
Another Solution With ERB Templates
I have implemented a quick and dirty solution to manage it. Which from the other side does exactly what I wanted to have.
Setup
So. I have created just one file called docker-compose.yml.erb
inside of my composed project root directory
and added docker-compose.yml
and docker-compose.production.yml
files to .gitignore
to make them not trackable by
the source control anymore.
---
<%
@compose_env = %w[production prod p].include?(ENV['COMPOSE_TEMPLATE_ENV'].downcase) ? 'production' : 'development'
def production?
@compose_env == 'production'
end
def development?
@compose_env == 'development'
end
%>
version: '3'
services:
myawesomeservice:
ports:
<% if development? %>
- "80:8080"
<% else %>
- "443:8080"
<% end %>
...
And I also have such a section in the Makefile
:
compile:
COMPOSE_TEMPLATE_ENV=production erb docker-compose.yml.erb > docker-compose-production.yml
COMPOSE_TEMPLATE_ENV=development erb docker-compose.yml.erb > docker-compose.yml
Now I can work with the docker-compose.yml.erb
template just in the manner we’ve used to do it in the era
of Puppet templates
Usage
Now just run:
$ make compile
to build a separate docker-compose.yml
and a docker-compose.production.yml
files.
That’s how you get you app running within the development
environment now:
$ docker-compose up -d
And for the production
environment just use:
$ docker-compose -f docker-compose.production.yml up -d
Conclusion
No complex chaining needed anymore. Only one file needs to be changed to make everything work as expected.
Probably this kind of setup is not ideal and it is not convenient for large projects, it is also not consistent with compose best practices, but I find it pretty suitable for my needs in some cases.