In the past two posts (part 1 and part 2) in this series, we have looked at what base images are available to us as developers. Then we tackled compiled languages with Golang. Now in this post, we are going to look at the options available to us using a dynamic language. Before we start we should get something out of the way. If you have been following the series we have spoken about the scratch base image. I have some bad news if you are using a dynamic language, you will not be able to use scratch as you need the runtime installed, unlike compiled languages. So in this post, we are going to use the Full OS, Slim OS and Alpine Linux images.
If you want to take advantage of multi-stage builds with a dynamic language it is a bit more nuanced. As we won't have a single binary to copy, in saying that we can still use multi-stage builds. Python is actually quite nice to use in multi-stage builds as it has virtualenv which gives use logical separation of dependencies we need to run our application. To use multi-stage build you would copy the virtualenv directory from the build container to the final image. Again as with the Golang example, the application we are using is fairly lightweight. Just a simple Python web server hosting a static HMTL file. If you would like to view the code just go here.
So as mentioned earlier we have three choices with a dynamic language so we will start with the full OS image.
FROM python:3.8.0a2-stretch as build RUN python3 -m venv /web COPY . /web FROM python:3.8.0a2-stretch COPY --from=build /web /web WORKDIR /web EXPOSE 8080 ENTRYPOINT [ "python", "web]
Once we build our base image with the Dockerfile above using multi-stage builds we have an image
945mb So as you can see dynamic language container images are much larger than compiled languages due to needing the runtime. Now remembering from our first post this image also contains critical vulnerabilities. So let’s move onto the slim image as see how much of the image size we can save.
FROM python:3.8.0a2-slim-stretch as build RUN python3 -m venv /web COPY . /web FROM python:3.8.0a2-slim-stretch COPY --from=build /web /web WORKDIR /web EXPOSE 8080 ENTRYPOINT [ "python", "web.py" ]
With this build using the slim OS as the base image, we get an image size of
159mb So as you can see that is a huge saving in size by swapping from a Full OS image to a slim version of the same OS. Unfortunately, this image still ships with vulnerable packages. So if we move our application to Alpine Linux what benefits do we yield?
FROM python:3.8.0a2-alpine3.9 as build RUN python3 -m venv /web COPY . /web FROM python:3.8.0a2-alpine3.9 COPY --from=build /web /web WORKDIR /web EXPOSE 8080 ENTRYPOINT [ "python", "web.py" ]
Now moving to Alpine Linux has given us the smallest image size coming in at
103mb with no critical vulnerabilities. Now we briefly spoke about this issue in part 1. Alpine does not use
glibc instead, it uses
musl libc now, this is much more important to a dynamic language as if you have dependencies that rely on
glibc you might have issues with Alpine.
If you want to look at any of the code for this series of posts, please visit my GitHub page. For further learning about containers or Kubernetes checkout out my OSS workshop or you can always ask me any question on Twitter @scottcoulton