Reduce image sizes and improve startup times by switching your base image to Alpine Linux.
Before proceeding, a caution. This is an engineering trade-off. Test carefully before deploying to production.
By the end of this blog post you should have the information you need to make an informed decision.
Introduction
Alpine Linux is a Linux distribution that advertises itself as Small. Simple. Secure.
It is indisputably smaller than the alternatives – when measured by image size. More on that in a bit. Some claim that this results in less memory usage and better performance. Others dispute these claims. For these, it is best that you test the results for yourself with your application.
Simple is harder to measure. Some of the larger differences, like OpenRC vs SystemD, are less relevant in container environments. Others, like BusyBox are implementation details. Essentially what you get is a Linux distribution with perhaps a number of standard packages (e.g., bash) not installed by default, but these can be easily added if needed.
Secure is definitely an important attribute. The alternatives make comparable claims in this area. Do your own research in this area and come to your own conclusions.
Not mentioned is the downside: Alpine Linux has a smaller ecosystem that the alternatives, particularly when compared to Debian.
Baseline
Let’s start with a baseline consisting of the Dockerfiles produced by fly launch
for some of the most popular
frameworks:
FROM fideloper/fly-laravel:${PHP_VERSION}
FROM hexpm/elixir:1.12.3-erlang-24.1.4-debian-bullseye-20210902-slim
FROM node:${NODE_VERSION}-slim
FROM oven/bun:${BUN_VERSION}-slim
FROM python:${PYTHON_VERSION}-slim-bullseye
FROM ruby:$RUBY_VERSION-slim
What may not be obvious to the naked eye from these results is that the base image for these is one of the following:
- Debian Bookworm (the current “stable” distribution)
- Debian Bullseye (the previous “stable” distribution)
- Ubuntu Focal Fossa (the previous LTS release of Ubuntu)
Once you factor in that Ubuntu is based on Debian, the conclusion is that Debian is effectively the default distribution for fly IO. Rest assured that this isn’t the result of a devious conspiracy by Fly.io, but rather a reflection of the default choices made independently by the developers of a number of frameworks and runtimes. Beyond this, all Fly.io is doing is choosing the “slim” version of the default distribution for each framework as the base.
What’s likely going on here is a virtuos circle: people choose Debian because of the ecosystem, and ecosystem grows because people chose Debian.
Now lets compare base image sizes:
Alpine | Debian slim | |
---|---|---|
Bun 1.0.18 | 43.10M | 63.84M |
Node 21.4.0 | 46.83M | 70.08M |
Python 3.12.1 | 17.59M | 45.36M |
Ruby 3.2 | 40.14M | 74.36M |
And these numbers are just the for the base images. I’ve measured a minimal Rails/Postgresql/esbuild application at 304MB on Alpine and 428MB on Debian Slim. A minimal Bun application at 110MB on Alpine and 173MB on Debian Slim. And a minimal Node application at 142MB on Alpine and 207MB on Debian Slim.
In each case, corresponding Alpine images are consistently smaller than their Debian slim equivalent.
Switching Distributions
Switch distributions (and switching back!) is easy.
The first change is to replace -slim
with -alpine
in FROM
statements in your Dockerfile
.
Next is to replace apt-get update
with apk update
and apt-get install
with apk add
. Delete any options you may have like -y
and --no-install-recommends
- they aren’t needed.
Now review the names of the packages you are installing. Many are named the same. A few are different. You can use alpine packages to look for ones to use. Some examples of differences:
Debian | Alpine |
---|---|
build-essential | build-base |
chromium-sandbox | chromium-chromedriver |
default-libmysqlclient-dev | mysql-client |
default-mysqlclient | mysql-client |
freedts-bin | freedts |
libicu-dev | icu-dev |
libjemalloc | jemalloc-dev |
libjpeg-dev | jpeg-dev |
libmagickwand-dev | imagemagick-libs |
libsqlite3-0 | sqlite-dev |
libtiff-dev | tiff-dev |
libvips | vips-dev |
node-gyp | gyp |
pkg-config | pkgconfig |
python | python3 |
python-is-python3 | python3 |
sqlite3 | sqlite |
Note: the above is just an approximation. For example, while libsqlite3-0
and sqlite-dev
include everything
you need to build an application that uses sqlite3, all that is needed at runtime is sqlite-lib
. This relentless attention to detail contributes to smaller final image sizes.
Note: For Bun, Node, and Rails users, knowledge of how to apply the above changes are included in recent versions of the dockerfile generators that we provide. After all, computers are good at if
statements:
bunx dockerfile --alpine
npx dockerfile --alpine
bin/rails generate dockerfile --alpine
Potential issues
Over time, we’ve noted a number of issues.
- Alpine uses musl for a runtime library. Debian uses glibc. Software tested on glibc may not work as expected on musl. And there are other potential compatibility issues like DNS.
- Debian includes both
adduser
anduseradd
. Alpine, by default, only includesadduser
. This can be addressed by installing package like shadow, or switching toadduser
. - Packages like node-build require
bash
which isn’t included by default. Adding it back in allowsnode-build
to run to completion, but the end result is that a precompiled Debian executable is installed that won’t run on Alpine. An alternative is to download an unofficial build. - Release candidates for Alpine may not get the same level of testing as Debian resulting in problems like sqlite3-ruby not working on Alpine 3.19. In cases like this, stay back on previous versions of Alpine for a short while, or compile the gem for yourself. These issues are temporary.
- Some packages, like Chrome, are not available for Alpine. Alternatives like Chromium may be necessary.
Conclusion
While not as large a community as Debian, there is a substantial number of happy Alpine users.
For the forseeable future, the default for both frameworks and there fly.io will remain Debian, but we make it easy to switch.
Try it out! Hopefully this blog has provided insight into what you should evaluate for before you switch.