Java Ecosystem, Kotlin, Distributed Systems, Sociology of Software Development

Install Cairo and CairoSVG on an Alpine Docker Image

Posted on Jun 25, 2018

I just wanted to convert SVG to PNG files with Python and the library CairoSVG. That was no problem on my Ubuntu system. But running the SVG converter script within a lightweight Alpine Docker container turned out to be problematic. Figuring out which libraries have to be installed up front took me some time. That’s why I like to share my findings here. Hopefully, it’ll save your time.

Install Cairo and CairoSVG on an Alpine Docker Image

TL;DR

Dockerfile:

FROM python:3.6.4-alpine3.7

RUN apk add --no-cache \
    build-base cairo-dev cairo cairo-tools \
    # pillow dependencies
    jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev

RUN pip install "flask==1.0.1" "CairoSVG==2.1.3"

COPY circle.svg /
COPY svg-converter-service.py /
CMD python3 /svg-converter-service.py

svg-converter-service.py:

import cairosvg
from flask import Flask, Response

app = Flask(__name__)

@app.route('/image')
def convert_image():
    png_data = cairosvg.svg2png(url="circle.svg", parent_width=300, parent_height=300)
    return Response(png_data, mimetype='image/png')

if __name__ == '__main__':
    app.run(debug=True, port=5000, host='0.0.0.0')

You can see the converted PNG by opening http://localhost:5000/image in the browser.

Source Code

The complete source code can be found on GitHub in the project cairosvg-on-alpine.

The Journey

I started my journey with a quite plain Python & Alpine Docker Image without any apk command:

FROM python:3.6.4-alpine3.7

RUN pip install "flask==1.0.1" "CairoSVG==2.1.3"

COPY circle.svg /
COPY svg-converter-service.py /
CMD python3 /svg-converter-service.py

That leaded me directly to the first error when pip tries to install cairocffi:

Collecting cairocffi (from CairoSVG==2.1.3)
...
Running setup.py (path:/tmp/pip-build-7l3xzt_6/cairocffi/setup.py) egg_info for package cairocffi
  Running command python setup.py egg_info

      No working compiler found, or bogus compiler options passed to
      the compiler from Python's standard "distutils" module.  See
      the error messages above.  Likely, the problem is not related
      to CFFI but generic to the setup.py of any Python package that
      tries to compile C code.  (Hints: on OS/X 10.8, for errors about
      -mno-fused-madd see http://stackoverflow.com/questions/22313407/
      Otherwise, see https://wiki.python.org/moin/CompLangPython or
      the IRC channel #python on irc.freenode.net.)
...
unable to execute 'gcc': No such file or directory

The GNU Compiler Collection aka gcc is missing. We can fix that by installing build-base via apk.

RUN apk add --no-cache build-base

Still, pip can’t install cairocffi:

Running setup.py (path:/tmp/pip-build-3ryf8sen/cairocffi/setup.py) egg_info for package cairocffi
...
  to the PKG_CONFIG_PATH environment variable
  Package 'libffi', required by 'virtual:world', not found
  c/_cffi_backend.c:15:17: fatal error: ffi.h: No such file or directory
    #include <ffi.h>
                    ^
  compilation terminated.
  Traceback (most recent call last):
    File "/usr/local/lib/python3.6/distutils/unixccompiler.py", line 118, in _compile
      extra_postargs)
    File "/usr/local/lib/python3.6/distutils/ccompiler.py", line 909, in spawn
      spawn(cmd, dry_run=self.dry_run)
    File "/usr/local/lib/python3.6/distutils/spawn.py", line 36, in spawn
      _spawn_posix(cmd, search_path, dry_run=dry_run)
    File "/usr/local/lib/python3.6/distutils/spawn.py", line 159, in _spawn_posix
      % (cmd, exit_status))
  distutils.errors.DistutilsExecError: command 'gcc' failed with exit status 1

Now, we need to know that CairoSVG is a Python wrapper around the C library Cairo. So we need to install Cairo additionally on our Docker image:

RUN apk add --no-cache build-base cairo-dev cairo cairo-tools

Now we get the following error:

  Running setup.py bdist_wheel for pillow: started
  Running setup.py bdist_wheel for pillow: finished with status 'error'
  ...
 The headers or library files could not be found for jpeg,
  a required dependency when compiling Pillow from source.

  Please see the install instructions at:
     https://pillow.readthedocs.io/en/latest/installation.html

  Traceback (most recent call last):
    File "/tmp/pip-build-g2zllesg/pillow/setup.py", line 794, in <module>
      zip_safe=not (debug_build() or PLATFORM_MINGW), )
    File "/usr/local/lib/python3.6/site-packages/setuptools/__init__.py", line 129, in setup
      return distutils.core.setup(**attrs)
    File "/usr/local/lib/python3.6/distutils/core.py", line 148, in setup
      dist.run_commands()
    File "/usr/local/lib/python3.6/distutils/dist.py", line 955, in run_commands
      self.run_command(cmd)
    File "/usr/local/lib/python3.6/distutils/dist.py", line 974, in run_command
      cmd_obj.run()
    File "/usr/local/lib/python3.6/site-packages/wheel/bdist_wheel.py", line 204, in run
      self.run_command('build')
    File "/usr/local/lib/python3.6/distutils/cmd.py", line 313, in run_command
      self.distribution.run_command(command)
    File "/usr/local/lib/python3.6/distutils/dist.py", line 974, in run_command
      cmd_obj.run()
    File "/usr/local/lib/python3.6/distutils/command/build.py", line 135, in run
      self.run_command(cmd_name)
    File "/usr/local/lib/python3.6/distutils/cmd.py", line 313, in run_command
      self.distribution.run_command(command)
    File "/usr/local/lib/python3.6/distutils/dist.py", line 974, in run_command
      cmd_obj.run()
    File "/usr/local/lib/python3.6/distutils/command/build_ext.py", line 339, in run
      self.build_extensions()
    File "/tmp/pip-build-g2zllesg/pillow/setup.py", line 582, in build_extensions
      raise RequiredDependencyException(f)
  __main__.RequiredDependencyException: jpeg

Pillow is a Python Imaging library and a dependency of CairoSVG. Pillow, in turn, requires that some libraries are installed on the system. This can be done by adding some dependencies to our apk command (I looked them up in the Alpine Docker Image in the Pillow GitHub Repo):

RUN apk add --no-cache \
    build-base cairo-dev cairo cairo-tools \
    # pillow dependencies
    jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev

Yes! We finally made it! Our Alpine Docker container with CairoSVG and Cairo is up and running. Opening http://localhost:5000/image in the browser will show the converted PNG.

Extra: Local Development (under Ubuntu)

During development, we don’t want to rebuild the Alpine Docker image after every change in the Python script. Fortunately, it’s easy to execute the script locally because, on Ubuntu, we don’t have to install all these native C libraries (tested with Ubuntu 17.04).

I like to use pipenv for local development. We only need a Pipfile containing our dependencies flask and CairoSVG:

[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]

flask = "==0.12.2"
CairoSVG = "==2.1.3"

[dev-packages]

[requires]

python_version = "3.6"

Start the Python script:

# pip install pipenv
pipenv install
pipenv shell
cd src
python svg-converter-service.py

All files can be found on GitHub.

This worked for me the last time. However, I somehow used to get the error ModuleNotFoundError: No module named 'PIL':

Traceback (most recent call last):
  File "svg-converter-service.py", line 3, in <module>
    import cairosvg
  File "/home/pha/.local/share/virtualenvs/cairosvg-on-alpine--SwTTCnJ/lib/python3.6/site-packages/cairosvg/__init__.py", line 29, in <module>
    from . import surface
  File "/home/pha/.local/share/virtualenvs/cairosvg-on-alpine--SwTTCnJ/lib/python3.6/site-packages/cairosvg/surface.py", line 34, in <module>
    from .image import image
  File "/home/pha/.local/share/virtualenvs/cairosvg-on-alpine--SwTTCnJ/lib/python3.6/site-packages/cairosvg/image.py", line 25, in <module>
    from PIL import Image
ModuleNotFoundError: No module named 'PIL'

Most likely, this means that Pillow couldn’t be installed by pip because the mentioned native C libraries are missing. Try sudo apt-get install python3-dev python3-setuptools libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev.