Skip to content

Instantly share code, notes, and snippets.

Created January 14, 2016 16:36
Show Gist options
  • Save lerrigatto/21063a81d4de9eea92d9 to your computer and use it in GitHub Desktop.
Save lerrigatto/21063a81d4de9eea92d9 to your computer and use it in GitHub Desktop.
Create a docker image based on debootstrap(-ed) debian jessie, suitable to run odoo
# debootstrap distro parameter
# docker build tag
# override if you want to build to a non-temp directory
rootfs_chroot() {
# "chroot" doesn't set PATH, so we need to set it explicitly to something our new debootstrap chroot can use appropriately!
# set PATH and chroot away!
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
chroot "$rootfsDir" "$@"
if [ -z "$dir" ]; then
dir="$(mktemp -d ${TMPDIR:-/var/tmp}/docker-mkimage.XXXXXXXXXX)"
( set -x; mkdir -p "$rootfsDir" )
debootstrap --arch=amd64 --variant=minbase $suite $rootfsDir
cp requirements.txt "$rootfsDir/tmp"
# prevent init scripts from running during install/update
echo >&2 "+ echo exit 101 > '$rootfsDir/usr/sbin/policy-rc.d'"
cat > "$rootfsDir/usr/sbin/policy-rc.d" <<-'EOF'
# For most Docker users, "apt-get install" only happens during "docker build",
# where starting services doesn't work and often fails in humorous ways. This
# prevents those failures by stopping the services from attempting to start.
exit 101
chmod +x "$rootfsDir/usr/sbin/policy-rc.d"
# prevent upstart scripts from running during install/update
set -x
rootfs_chroot dpkg-divert --local --rename --add /sbin/initctl
cp -a "$rootfsDir/usr/sbin/policy-rc.d" "$rootfsDir/sbin/initctl"
sed -i 's/^exit.*/exit 0/' "$rootfsDir/sbin/initctl"
# shrink a little, since apt makes us cache-fat (wheezy: ~157.5MB vs ~120MB)
( set -x; rootfs_chroot apt-get clean )
# this file is one APT creates to make sure we don't "autoremove" our currently
# in-use kernel, which doesn't really apply to debootstraps/Docker images that
# don't even have kernels installed
rm -f "$rootfsDir/etc/apt/apt.conf.d/01autoremove-kernels"
# Ubuntu 10.04 sucks... :)
if strings "$rootfsDir/usr/bin/dpkg" | grep -q unsafe-io; then
# force dpkg not to call sync() after package extraction (speeding up installs)
echo >&2 "+ echo force-unsafe-io > '$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup'"
cat > "$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup" <<-'EOF'
# For most Docker users, package installs happen during "docker build", which
# doesn't survive power loss and gets restarted clean afterwards anyhow, so
# this minor tweak gives us a nice speedup (much nicer on spinning disks,
# obviously).
if [ -d "$rootfsDir/etc/apt/apt.conf.d" ]; then
# _keep_ us lean by effectively running "apt-get clean" after every install
aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";'
echo >&2 "+ cat > '$rootfsDir/etc/apt/apt.conf.d/docker-clean'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-clean" <<-EOF
# Since for most Docker users, package installs happen in "docker build" steps,
# they essentially become individual layers due to the way Docker handles
# layering, especially using CoW filesystems. What this means for us is that
# the caches that APT keeps end up just wasting space in those layers, making
# our layers unnecessarily large (especially since we'll normally never use
# these caches again and will instead just "docker build" again and make a brand
# new image).
# Ideally, these would just be invoking "apt-get clean", but in our testing,
# that ended up being cyclic and we got stuck on APT's lock, so we get this fun
# creation that's essentially just "apt-get clean".
DPkg::Post-Invoke { ${aptGetClean} };
APT::Update::Post-Invoke { ${aptGetClean} };
Dir::Cache::pkgcache "";
Dir::Cache::srcpkgcache "";
# Note that we do realize this isn't the ideal way to do this, and are always
# open to better suggestions (
# remove apt-cache translations for fast "apt-get update"
echo >&2 "+ echo Acquire::Languages 'none' > '$rootfsDir/etc/apt/apt.conf.d/docker-no-languages'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-no-languages" <<-'EOF'
# In Docker, we don't often need the "Translations" files, so we're just wasting
# time and space by downloading them, and this inhibits that. For users that do
# need them, it's a simple matter to delete this file and "apt-get update". :)
Acquire::Languages "none";
echo >&2 "+ echo Acquire::GzipIndexes 'true' > '$rootfsDir/etc/apt/apt.conf.d/docker-gzip-indexes'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-gzip-indexes" <<-'EOF'
# Since Docker users using "RUN apt-get update && apt-get install -y ..." in
# their Dockerfiles don't go delete the lists files afterwards, we want them to
# be as small as possible on-disk, so we explicitly request "gz" versions and
# tell Apt to keep them gzipped on-disk.
# For comparison, an "apt-get update" layer without this on a pristine
# "debian:wheezy" base image was "29.88 MB", where with this it was only
# "8.273 MB".
Acquire::GzipIndexes "true";
Acquire::CompressionTypes::Order:: "gz";
# update "autoremove" configuration to be aggressive about removing suggests deps that weren't manually installed
echo >&2 "+ echo Apt::AutoRemove::SuggestsImportant 'false' > '$rootfsDir/etc/apt/apt.conf.d/docker-autoremove-suggests'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-autoremove-suggests" <<-'EOF'
# Since Docker users are looking for the smallest possible final images, the
# following emerges as a very common pattern:
# RUN apt-get update \
# && apt-get install -y <packages> \
# && <do some compilation work> \
# && apt-get purge -y --auto-remove <packages>
# By default, APT will actually _keep_ packages installed via Recommends or
# Depends if another package Suggests them, even and including if the package
# that originally caused them to be installed is removed. Setting this to
# "false" ensures that APT is appropriately aggressive about removing the
# packages it added.
Apt::AutoRemove::SuggestsImportant "false";
echo "deb $suite/updates main" >> "$rootfsDir/etc/apt/sources.list"
set -x
sed -i "s/ $suite main$/ $suite main contrib non-free/" "$rootfsDir/etc/apt/sources.list"
set -x
# make sure we're fully up-to-date
rootfs_chroot sh -xc 'apt-get update && apt-get dist-upgrade -y'
rootfs_chroot sh -xc 'apt-get install -y --fix-missing \
apt-utils \
python2.7 \
python2.7-dev \
python-dev \
python-setuptools \
libevent-dev \
libsasl2-dev \
libldap2-dev \
libyaml-dev \
libxml2 \
libxml2-dev \
libxslt-dev \
zlib1g-dev \
libjpeg-dev \
libfreetype6-dev \
libpng12-dev \
libpq-dev \
npm \
rootfs_chroot sh -xc 'ln -s /usr/bin/nodejs /usr/bin/node'
rootfs_chroot sh -xc 'npm install -dd -g less less-plugin-clean-css'
rootfs_chroot sh -xc 'easy_install pip'
rootfs_chroot sh -xc 'pip install -r /tmp/requirements.txt --no-cache-dir'
rootfs_chroot sh -xc 'apt-get clean'
rootfs_chroot sh -xc 'rm -rf /var/lib/apt/lists/*'
rootfs_chroot sh -xc 'npm cache clean'
# Docker mounts tmpfs at /dev and procfs at /proc so we can remove them
rm -rf "$rootfsDir/dev" "$rootfsDir/proc"
mkdir -p "$rootfsDir/dev" "$rootfsDir/proc"
touch "$tarFile"
set -x
tar --numeric-owner --create --auto-compress --file "$tarFile" --directory "$rootfsDir" --transform='s,^./,,' .
echo >&2 "+ cat > '$dir/Dockerfile'"
cat > "$dir/Dockerfile" <<EOF
FROM scratch
ADD $(basename "$tarFile") /
# if our generated image has a decent shell, let's set a default command
for shell in /bin/bash /usr/bin/fish /usr/bin/zsh /bin/sh; do
if [ -x "$rootfsDir/$shell" ]; then
( set -x; echo 'CMD ["'"$shell"'"]' >> "$dir/Dockerfile" )
( set -x; rm -rf "$rootfsDir" )
if [ "$tag" ]; then
( set -x; docker build -t "$tag" "$dir" )
elif [ "$delDir" ]; then
# if we didn't specify a tag and we're going to delete our dir, let's just build an untagged image so that we did _something_
( set -x; docker build "$dir" )
if [ "$delDir" ]; then
( set -x; rm -rf "$dir" )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment