├── Puppetfile ├── manifests └── init.pp ├── Rockerfile └── README.md /Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | -------------------------------------------------------------------------------- /manifests/init.pp: -------------------------------------------------------------------------------- 1 | class { 'nginx': } -> 2 | exec { 'Disable Nginx daemon mode': 3 | path => '/bin', 4 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 5 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 6 | } 7 | -------------------------------------------------------------------------------- /Rockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER Gareth Rushgrove "gareth@puppet.com" 3 | 4 | ENV PUPPET_AGENT_VERSION="1.5.0" UBUNTU_CODENAME="xenial" PATH=/opt/puppetlabs/server/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin:$PATH 5 | 6 | LABEL com.puppet.version="0.1.0" com.puppet.dockerfile="/Dockerfile" com.puppet.puppetfile="/Puppetfile" com.puppet.manifest="/manifests.init.pp" 7 | 8 | MOUNT /opt/puppetlabs /etc/puppetlabs /root/.gem 9 | 10 | RUN apt-get update && \ 11 | apt-get install -y wget=1.17.1-1ubuntu1 && \ 12 | wget https://apt.puppetlabs.com/puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 13 | dpkg -i puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 14 | rm puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 15 | apt-get update && \ 16 | apt-get install --no-install-recommends -y puppet-agent="$PUPPET_AGENT_VERSION"-1"$UBUNTU_CODENAME" && \ 17 | apt-get clean && \ 18 | rm -rf /var/lib/apt/lists/* 19 | 20 | RUN /opt/puppetlabs/puppet/bin/gem install r10k:2.2.2 --no-ri --no-rdoc 21 | 22 | COPY Puppetfile / 23 | COPY manifests /manifests 24 | RUN apt-get update && \ 25 | r10k puppetfile install --moduledir /etc/puppetlabs/code/modules && \ 26 | puppet apply manifests/init.pp --verbose --show_diff --summarize && \ 27 | apt-get clean && \ 28 | rm -rf /var/lib/apt/lists/* 29 | 30 | EXPOSE 80 31 | 32 | CMD ["nginx"] 33 | 34 | COPY Rockerfile /Dockerfile 35 | 36 | TAG puppet/puppet-rocker-example 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building Docker Images with Puppet 2 | 3 | The flexibility of Dockerfile means that it's long been possible to 4 | build Docker images using Puppet. This has several advantages in some 5 | situations: 6 | 7 | * Puppet provides a much richer set of high-level types than Dockerfile 8 | where you are reliant on bash and `RUN` 9 | * The capability to easy integrate data from different sources to affect 10 | the resulting images 11 | * The ability to reuse code between containers and more traditional 12 | infrastructure 13 | * A programming language capable of abstraction and with a strong suite 14 | of testing tools available 15 | 16 | The approach of running a `puppet apply` command in a Dockerfile was 17 | ably described in [this blog post](https://puppet.com/blog/building-puppet-based-applications-inside-docker) 18 | which is now more than 2 years old. 19 | 20 | However the approach described has several problems: 21 | 22 | * Software only used for building the image is left in at runtime, which 23 | increases the surface area for an attacker 24 | * Because of all that extra software the resulting image is much larger 25 | than it needs to be 26 | 27 | 28 | ## A Modern Alternative 29 | 30 | [Rocker](https://github.com/grammarly/rocker) is an alternative Docker 31 | build tool with some additions to the standard Dockerfile syntax. In 32 | particular Rocker supports mounting volumes at build time. These volumes 33 | are only available during build and do not become part of the resulting 34 | image. With that capability we can build Docker images using Puppet 35 | without the need to carry around Puppet at runtime. 36 | 37 | First install 38 | [Rocker](https://github.com/grammarly/rocker#installation). 39 | 40 | Similar to the original example we're going to use a `Puppetfile` to 41 | describe out dependencies: 42 | 43 | ``` 44 | forge 'https://forgeapi.puppetlabs.com' 45 | 46 | mod 'jfryman/nginx' 47 | mod 'puppetlabs/stdlib' 48 | mod 'puppetlabs/concat' 49 | mod 'puppetlabs/apt' 50 | ``` 51 | 52 | Rather than simply inline the actual Puppet code as in the original 53 | example we're storing it in the accompanying manifests directory. The 54 | code for our demonstration is saved as `manifests/init.pp`. 55 | 56 | ```puppet 57 | class { 'nginx': } -> 58 | exec { 'Disable Nginx daemon mode': 59 | path => '/bin', 60 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 61 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 62 | } 63 | ``` 64 | 65 | With Rocker installed lets now look at the `Rockerfile`. 66 | 67 | ``` 68 | FROM ubuntu:16.04 69 | MAINTAINER Gareth Rushgrove "gareth@puppet.com" 70 | 71 | ENV PUPPET_AGENT_VERSION="1.5.0" UBUNTU_CODENAME="xenial" PATH=/opt/puppetlabs/server/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin:$PATH 72 | 73 | LABEL com.puppet.version="0.1.0" com.puppet.dockerfile="/Dockerfile" com.puppet.puppetfile="/Puppetfile" com.puppet.manifest="/manifests.init.pp" 74 | 75 | MOUNT /opt/puppetlabs /etc/puppetlabs /root/.gem 76 | 77 | RUN apt-get update && \ 78 | apt-get install -y wget=1.17.1-1ubuntu1 && \ 79 | wget https://apt.puppetlabs.com/puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 80 | dpkg -i puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 81 | rm puppetlabs-release-pc1-"$UBUNTU_CODENAME".deb && \ 82 | apt-get update && \ 83 | apt-get install --no-install-recommends -y puppet-agent="$PUPPET_AGENT_VERSION"-1"$UBUNTU_CODENAME" && \ 84 | apt-get clean && \ 85 | rm -rf /var/lib/apt/lists/* 86 | 87 | RUN /opt/puppetlabs/puppet/bin/gem install r10k:2.2.2 --no-ri --no-rdoc 88 | 89 | COPY Puppetfile / 90 | COPY manifests /manifests 91 | RUN apt-get update && \ 92 | r10k puppetfile install --moduledir /etc/puppetlabs/code/modules && \ 93 | puppet apply manifests/init.pp --verbose --show_diff --summarize && \ 94 | apt-get clean && \ 95 | rm -rf /var/lib/apt/lists/* 96 | 97 | EXPOSE 80 98 | 99 | CMD ["nginx"] 100 | 101 | COPY Rockerfile /Dockerfile 102 | 103 | TAG puppet/puppet-rocker-demo 104 | ``` 105 | 106 | As mentioned, a `Rockerfile` is simply a `Dockerfile` with some new 107 | instructions. Of particular note is the `MOUNT` operation. This 108 | invocation mounts this directories from ephemeral containers during 109 | build, meaning the contents are available at build time but not saved in 110 | the resulting image. 111 | 112 | ``` 113 | MOUNT /opt/puppetlabs /etc/puppetlabs /root/.gem 114 | ``` 115 | 116 | This means we have access to the full set of Puppet and other tools for 117 | building our image, without needing them at runtime. 118 | 119 | Their are some other tricks in the Rockerfile which mirror current 120 | Dockerfile best practice, for example cleaning up the package cache and 121 | metadata to save space. 122 | 123 | If you'd like to try this demo out you can simply run the following from 124 | this directory: 125 | 126 | ``` 127 | rocker build 128 | ``` 129 | 130 | This should build the image from scratch, providing useful output 131 | regarding the different layer sizes as it goes. 132 | 133 | 134 | ## Results 135 | 136 | So, how successful is the experiment? Let's start by looking at the base 137 | images. We've used Ubuntu for both the examples for ease of comparison, 138 | although it's worth noting that the size of the base images has been 139 | decreasing too. So we save around 50 MB by just updating to the latest release. 140 | 141 | ``` 142 | $ docker images | grep ubuntu 143 | ubuntu 16.04 0b7735b9290f 7 weeks ago 123.7 MB 144 | ubuntu 12.10 3e314f95dcac 23 months ago 172.1 MB 145 | ``` 146 | 147 | The original example left all the required build tools in the image. For 148 | the nginx example this meant a whopping 400 MB+ image. 149 | 150 | ``` 151 | $ docker images | grep jamtur 152 | jamtur01/puppetbase latest 1842a4dc30c4 25 seconds ago 361.4 MB 153 | jamtur01/nginx latest 6c66777eb97f 9 seconds ago 408.2 MB 154 | ``` 155 | 156 | With the approach shown here we have reduced that size down to 185 MB. 157 | That's a 55% saving on the original. The real improvement is much 158 | larger though. The new image is only 62 MB larger than the base image compared 159 | with the originals 236 MB. 160 | 161 | ``` 162 | $ docker images | grep rocker 163 | puppet/puppet-rocker-demo latest cb051d97e72e 10 hours ago 185.5 MB 164 | ``` 165 | --------------------------------------------------------------------------------