4 minutes
Utilizing puppet to deploy docker-compose stacks
Intro
As I dive deeper into learning Puppet, I’m aiming to use it more in my homelab for managing configurations. By using a central mono-repository, I can keep things in one place and easily tweak stuff without the hassle of connecting to each machine separately. Lately I’ve been trying to puppetize the way my compose-stacks are configured. I wanted to set it up myself as a learning experience so this is probably not the most optimized way of doing it.
Puppet manifest
First off I created a puppet manifest which will make a docker-compose file according to a pre-made .epp
template. This will create a general directory to place the stacks in. Every stack will get it’s own directory with a dedicated docker-compose.yaml
file.
class profile_docker::compose_files (
String $compose_version = '3',
String $directory = '/docker_stacks',
Hash[String, Array[Hash]] $stacks = {},
) {
file { $directory:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
$stacks.each |$stack_name, $containers| {
$stack_directory = "${directory}/${stack_name}"
file { $stack_directory:
ensure => directory,
owner => 'rein',
group => 'rein',
mode => '0755',
}
file { "${stack_directory}/docker-compose.yaml":
content => epp("${module_name}/docker-compose.yaml.epp", {
compose_version => $compose_version,
stack_name => $stack_name,
containers => $containers,
}),
owner => 'rein',
group => 'rein',
}
}
}
The .epp file
The docker-compose.yaml.epp
file is created to be as modular as possible. Some of my containers use docker volumes while others use local binds. This was circomvented with some simple conditionals.
<% | $compose_version, $stack_name, $containers | -%>
---
# File managed by puppet.
version: "<%= $compose_version %>"
<% if $containers.any |$container| { $container['volume_names'] } { -%>
volumes:
<% } -%>
<% $containers.each |$container| { -%>
<% if $container['volume_names'] { -%>
<% $container['volume_names'].each |$volume| { -%>
<%= $volume %>:
<% } -%>
<% } -%>
<% } -%>
services:
<% $containers.each |$container| { -%>
<%= $container['name'] %>:
image: <%= $container['image'] %>
container_name: <%= $container['container_name'] %>
<% if $container['environment'] { -%>
environment:
<% $container['environment'].each |$env| { -%>
<%= $env %>
<% } -%>
<% } -%>
<% if $container['ports'] { -%>
ports:
<% $container['ports'].each |$port| { -%>
- <%= $port %>
<% } -%>
<% } -%>
<% if $container['volumes'] { -%>
volumes:
<% $container['volumes'].each |$volume| { -%>
- <%= $volume %>
<% } -%>
<% } -%>
<% if $container['devices'] { -%>
devices:
<% $container['devices'].each |$device| { -%>
- <%= $device %>
<% } -%>
<% } -%>
<% if $container['healthcheck'] { -%>
healthcheck:
<% $container['healthcheck'].each |$check| { -%>
<%= $check %>
<% } -%>
<% } -%>
<% if $container['command'] { -%>
command: <%= $container['command'] %>
<% } -%>
<% if $container['stdin_open'] { -%>
stdin_open: <%= $container['stdin_open'] %>
<% } -%>
<% if $container['tty'] { -%>
tty: <%= $container['tty'] %>
<% } -%>
<% if $container['env_file'] { -%>
env_file:
<% $container['env_file'].each |$env_file| { -%>
- <%= $env_file %>
<% } -%>
<% } -%>
<% if $container['depends_on'] { -%>
depends_on:
<% $container['depends_on'].each |$depends_on| { -%>
- <%= $depends_on %>
<% } -%>
<% } -%>
<% if $container['cap_add'] { -%>
cap_add:
<% $container['cap_add'].each |$cap_add| { -%>
- <%= $cap_add %>
<% } -%>
<% } -%>
<% if $container['sysctls'] { -%>
sysctls:
<% $container['sysctls'].each |$sysctls| { -%>
- <%= $sysctls %>
<% } -%>
<% } -%>
restart: <%= $container['restart'] %>
<% } -%>
Puppet hiera
The most important part is, of course, the parameters needed for the .epp
file. These parameters are defined like this: Hash[String, Array[Hash]] $stacks = {}
. When looping through this parameter one by one, it gathers all the options and puts them in the .epp
file.
For example:
profile_docker::compose_files::stacks:
container_maintenance:
- name: cadvisor
image: gcr.io/cadvisor/cadvisor-arm64:0.99-porterdavid
container_name: cadvisorzzz
ports:
- 8055:8080
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
restart: unless-stopped
- name: watchtower
image: containrrr/watchtower
container_name: watchtower
ports:
- 8080:8080
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
This new way to create my stacks makes it easier for me to quickly adjust a stack and redeploy it. The redeploy step still needs to be implemented and tested but then I’m set!