As Docker rapidly gains adoption, an ecosystem of tools, patterns and practices has emerged. Here are some of the lessons we've learnt from optimizing our Dockerized build and deployment pipeline.
a PaaS company • Open sourced in Mar 2013 • Huge success; company pivots • Docker, Inc in Oct 2013 • Docker Machine, Swarm and Compose announced in Dec 2014
management and distribution • Incremental builds via a layered filesystem • Flattening the layers produces a snapshot • Docker Hub: central public image repository
from an image with individual parameters • Can be started and stopped • Containers will retain filesystem changes until they are removed using docker rm • Changes to a container can be persisted to a new image layer using docker commit
Can be a bind mount or a volume attached to a container • Typically used to share data across containers • Volumes are local to the host machine; they cannot be distributed like images
metadata [X-Fleet] • Schedule global units, specify constraints and dependencies, restart policies • Deploy units based on machine fleet metadata, e.g. role=api and role=worker
running api-discovery@master_123.service f54a4d78.../10.0.11.xxx active running api-discovery@master_123.service 320af1d0.../10.0.12.xxx active running api-proxy.service 75e1c8bd.../10.0.10.xxx active running api-proxy.service f54a4d78.../10.0.11.xxx active running api-proxy.service 320af1d0.../10.0.12.xxx active running api@master_123.service 75e1c8bd.../10.0.10.xxx active running api@master_123.service f54a4d78.../10.0.11.xxx active running api@master_123.service 320af1d0.../10.0.12.xxx active running logspout.service 75e1c8bd.../10.0.10.xxx active running logspout.service 17291bf6.../10.0.11.xxx active running logspout.service 320af1d0.../10.0.12.xxx active running logspout.service e1c8ca4c.../10.0.10.xxx active running logspout.service f54a4d78.../10.0.11.xxx active running logspout.service d28b5a20.../10.0.12.xxx active running logspout.service db206400.../10.0.10.xxx active running rabbitmq.service e1c8ca4c.../10.0.10.xxx active running rabbitmq.service 17291bf6.../10.0.11.xxx active running rabbitmq.service d28b5a20.../10.0.12.xxx active running [email protected] e1c8ca4c.../10.0.10.xxx active running [email protected] 17291bf6.../10.0.11.xxx active running [email protected] d28b5a20.../10.0.12.xxx active running
(scripts) vs long-lived (daemons) • Compilation tools and libraries should not be present in your production environment • Build your app in a “dev” or “builder” container and transfer it to a “runtime” container • Dedicated containers for common utilities (e.g. git)
that I need another container (or more) to build my app? • Script a build pipeline! • Process your source files in a shared volume with your build container(s) before loading it into your base runtime image as the final step • A popular approach is to use Makefiles
Third party CI services boot up a fresh environment each time • Fast bootstrapping • A new host in e.g. an autoscaling cluster has to download all images from scratch • Bandwidth / transfer • Especially if you’re running a private registry
popular base images on the Docker Hub • It provides an init system (runit) so you can run multiple processes in a container • Clocks in at 280 MB • How do we pare this down?
• SSH isn’t needed now that we have docker exec (addressed in a blog post) • Log management: dump process logs to stdout and use a collection container like progrium/logspout • Alternatively mount /dev/log into your container • With log management + sshd: 279 MB • Without: 244 MB (-35 MB)
a Gulp pipeline with gulp-ruby-sass • This requires “gem install sass”, which requires “apt-get install ruby-full rubygems-integration” • OR you could switch to gulp-sass and use native bindings to libsass (C implementation) • With gulp-ruby-sass: 487.2 MB • With gulp-sass: 386 MB (-101.2 MB)
instead of Ubuntu results in >100 MB savings off the bat • Some tweaks needed: different packages, python3 not installed by default, etc • Example: olberger/baseimage-docker • Before: 279 MB • After: 166.8 MB (-112.2 MB)
my_init script which bootstraps runit • Replace runit with s6, a process supervisor suite designed to run as PID 1, which removes the need for certain workarounds (e.g. environment variables) • Eliminates python3 as a dependency • Before: 166.8 MB • After: 144.3 MB (-22.5 MB)
(LFS) • The hard work has been done for you: Buildroot and BusyBox • You could also compile a statically linked binary, e.g. a Golang app and load it into the scratch image (0 MB)
2.4 MB (!!) • Batteries not included • A popular setup is to include opkg and piggyback on the OpenWrt package index • An example being progrium/busybox (4.8 MB) • Roll your own using progrium/rootbuilder • Before: 183 MB • After: 56 MB (-127 MB)
systems, hence it has a rather limited selection • Packages not available in OpenWrt (nodejs, redis, nginx, etc) usually have to be compiled from source, often with manual tweaks • Lots and lots of annoyances (tar missing -z, git missing https support, no python-dev, etc)
comes with a more general purpose package index (using apk) • Most package woes solved • App container: nodejs, php, python, etc available • Build container: apk-install build-base