Reduce Docker image sizes using Alpine

Here at Sandtable we love Docker because it makes our infrastructure so easy to deploy. However, it’s not great when you have 2GB Docker images that must be pushed and pulled over the network…

We noticed that Docker is now offering smaller images based on the security-oriented, lightweight Alpine Linux distribution. Images based on Alpine can be a lot smaller – for example, the python:2.7-alpine image is 19MB while the python:2.7 image is 260MB! We therefore decided to follow the same path and refactor our Dockerfiles.

Here is our guideline on how to proceed.

FROM alpine

The first step is to change the base image in your Dockerfile:
FROM ubuntu => FROM alpine
FROM python:2.7 => FROM python:2.7-alpine
FROM ruby:2.3 => FROM ruby:2.3-alpine

Go to hub.docker.com to check whether an alpine image is available (for example: search for “python”, select the official image, and go to the “Tags” section).

APK instead of APT or YUM

For Alpine the main difference is the package management.

In your Dockerfile, replace:

RUN apt-get update && apt-get install <package>

with:

RUN apk add --no-cache <package>

apk –no-cache

The --no-cache option allows you to not cache the index locally. First win!

ERROR: unsatisfiable constraints

The packages in Alpine are different and may have different names. You can search for available packages at this address: https://pkgs.alpinelinux.org/packages.

Package in the testing or community repository

Alpine package in testing repository

The package you need may not be available in the main repository but in the the testing or community repositories. Here is how you can add it to your Dockerfile:

RUN echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
RUN apk --update add --no-cache <package>

 

apk –virtual and del

There is a really cool feature that allows you to temporarily install packages (sort of). For example, you may need git or gcc to install your program but you don’t need them to execute it. In this case you will use the --virtual flag and give a name to this group of packages. Then you will be able to remove all of them.

For example:

RUN apk add --no-cache --virtual .build-deps \
    gcc \
    freetype-dev \
    musl-dev
RUN pip install --no-cache-dir <packages_that_require_gcc...> \
RUN apk del .build-deps

 

Reduce the number of layers

You can also reduce your image size by reducing the number of layers. If you have several RUN commands in a row, convert them into one RUN. For the previous example:

RUN apk add --no-cache --virtual .build-deps \
    gcc \
    freetype-dev \
    musl-dev
RUN pip install --no-cache-dir <packages_that_require_gcc...> \
RUN apk del .build-deps

 

Then becomes:

RUN apk add --no-cache --virtual .build-deps \
    gcc \
    freetype-dev \
    musl-dev \
    && pip install --no-cache-dir <packages_that_require_gcc...> \
    && apk del .build-deps

 
Note: this tip is valid for any image (not only those based on Alpine).

UPDATE 28/02/2017: Flatten the Docker image

Since the release of Docker 1.13, it’s possible to flatten your image. The --squash option squashes the image layers to the FROM image after successful builds. Just add --squash to your build command:

docker build --squash -t my_image .

 
This option is an experimental feature, so you need to start your Docker daemon with the --experimental flag to be able to use it.
In Ubuntu for example, add the line: DOCKER_OPTS="--experimental=true" to the /etc/default/docker file and restart Docker to be able to use this feature.

How to add a user on Alpine?

This will create a system user:

RUN adduser -S <username>

Documentation:

adduser [OPTIONS] user_name
Add a user
Options:
        -h DIR          Home directory
        -g GECOS        GECOS field
        -s SHELL        Login shell
        -G GRP          Add user to existing group
        -S              Create a system user
        -D              Do not assign a password
        -H              Do not create home directory
        -u UID          User id

 

How to enter an Alpine container?

Alpine doesn’t come with bash, so to enter a container forget:

docker run -ti my_image bash

Simply use shell:

docker run -ti my_image sh

Conclusion

By moving to Alpine we have reduced Docker images to a third of their size – speeding up deployments. We hope you’ll have the same success. Don’t hesitate to leave us a comment below and share your tips!

Comments

  1. Mark Newman says:

    Nice article. Very clear and simple steps that actually make a really big difference to the size of the image. Thanks

Leave a comment

Please prove that you are human: