Fly With Alpine

A drone delivering a printer
Image by Annie Ruygt

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

Choose your own Linux Distribution

Deploy your project in a few minutes with Fly Launch. Then do more with Fly Machines.

Run your entire stack near your users

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 and useradd. Alpine, by default, only includes adduser. This can be addressed by installing package like shadow, or switching to adduser.
  • Packages like node-build require bash which isn’t included by default. Adding it back in allows node-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.