February 15, 2024

Launching StableBuild - freeze and pin all your dependencies

Launching StableBuild - freeze and pin all your dependencies

Launching StableBuild - freeze and pin all your dependencies

🎉 Today we’re launching StableBuild, a new set of tools that helps developers create reliable and deterministic builds - by letting them easily freeze and pin Docker images, operating system packages, Python packages, and arbitrary build dependencies.

Sign up for free at https://dashboard.stablebuild.com !

Docker and deterministic builds

Docker has made it infinitely easier to ship application code, by allowing you to bundle your application plus all its dependencies - from the operating system to your Python packages - in a single deployable container. And, through Dockerfiles you have a human-readable specification on how that application needs to be built.

There’s one big problem though. At first glance Dockerfiles look deterministic (the same Dockerfile creates the same Docker container), but in reality you're depending on a myriad of mirrors, package registries, and repositories - any of which can change at any moment. Here’s an example:

# Base this Dockerfile on Ubuntu 20.04
FROM ubuntu:20.04

# Install some OS level packages
RUN apt update && apt install -y curl software-properties-common

# We need a newer Python version, grab it from Deadsnakes PPA
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
    apt update && \
    apt install -y python3.9 python3.9-distutils

# Install pip (the Python package manager)
RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \
    python3.9 get-pip.py

# Install a Python package through pip
RUN pip3 install onnx==1.14.0

This Dockerfile actually depends on 5 separate services:

  1. It pulls a base image from Docker Hub. Images in Docker Hub are not immutable. This base image can be overwritten with a new OS version or even be removed at any point.
  2. It installs some packages through `apt` from the Ubuntu package registry. This registry is constantly updating, so this will install the latest available version of `curl` and `software-properties-common`.
  3. It grabs a newer Python version from a third-party package registry (PPA). This registry is also constantly updating, so you’ll get the latest available Python 3.9.x release. Plus, there’s no guarantee that PPAs remain available.
  4. It downloads `get-pip.py` from the internet. This URL can return a different file or be removed altogether at any point.
  5. It uses `pip` to install packages from the Python package registry. While you specify a specific version for onnx here, onnx depends on protobuf>=3.20.2 - so you’ll get whatever the latest version of protobuf is.

This all means that this same Dockerfile can produce wildly different containers depending on when the container is built. Today you write your Dockerfile, test your application, and all is well. Tomorrow you rebuild the container from the same Dockerfile (e.g. you needed to update some code, or add an extra dependency) and now you’ll get a new OS version, new package versions from `apt`, a new Python version, hopefully the get-pip URL still resolves, and new Python packages. Maybe it works, maybe it doesn’t.

This is a massive maintenance burden for larger software teams; especially because it's all reactive. Someone makes a seemingly small change to your codebase, the build server builds the container from scratch, and the build is broken with a completely unrelated error. Now you'll need to immediately go and update your application to work with the new dependency (the build is broken!).

StableBuild to the rescue

StableBuild is fixing this. StableBuild is a set of mirrors and package registries aimed to make building containers reliable and deterministic. In essence this means that you can freeze any dependency using StableBuild, so the same Dockerfile will yield the same Docker container. We currently do this for:

  • Docker base images, by operating an immutable Docker Hub mirror.
  • Any package from the Ubuntu, Debian and Alpine package registry (basically anything you install through `apt` or `apk`); by creating a daily mirror of the complete package registry.
  • The most popular third-party package registries (PPAs) like deadsnakes (for Python) and Nvidia’s CUDA registry by creating a complete daily mirror.
  • Python packages from PyPI; by caching the complete PyPI index every day, and caching any installed package on-demand.
  • Any arbitrary URL or file from the internet, through our file mirror.

Solving the problem in 5 minutes

StableBuild integrates in your existing build toolchain in 5 minutes. For example, here’s the same Dockerfile as before, but now fully cached and built through StableBuild:

# Pull ubuntu:20.04 from StableBuild's immutable Docker Hub mirror
FROM your-prefix.dockermirror.stablebuild.com/ubuntu:20.04

# Pin any `apt` packages to this specific date. As we create a full
# daily copy of the registry, this will always return the exact same
# packages.
ARG APT_PIN_DATE=2024-02-14T10:40:01Z
ARG SB_API_KEY=your-prefix

# Load the Ubuntu package registry and the deadsnakes PPA
COPY ./sb-apt.sh /opt/sb-apt.sh
RUN bash /opt/sb-apt.sh load-apt-sources ubuntu deadsnakes

# Install some OS level packages (same as always)
RUN apt update && apt install -y curl software-properties-common

# We need a newer Python version, grab it from Deadsnakes PPA
# (no need to manually add the repo anymore) - same as always
RUN apt update && apt install -y python3.9 python3.9-distutils

# Prefix any URL with StableBuild's file mirror URL, and it's automatically
# cached. Then use StableBuild's PyPI mirror for extra dependencies
RUN curl https://your-prefix.httpcache.stablebuild.com/my-first-tutorial/https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \
    python3.9 get-pip.py -i https://your-prefix.pypimirror.stablebuild.com/2024-02-14/​

# Install a Python package through pip, pinned to 2024-02-14,
# this will install the package list as it was on that specific date.
RUN pip3 install \
    -i https://your-prefix.pypimirror.stablebuild.com/2024-02-14/ \
    install onnx==1.14.0

That’s it. This container is now fully pinned; and will always install the same OS version, package list and Python dependencies. 🎉

Getting started with StableBuild

Excited? So are we!

Working with our first customers we’ve seen builds break due to shifting dependencies every few weeks. That doesn’t sound like a lot, but it’s always time critical - builds need to be fixed immediately as production deploys are broken; typically only very senior engineers can do the update; and you need rigorous testing to verify that updated dependencies don’t break something for which you don’t have automated tests.

StableBuild has fixed all of that for them. Of course you still want to update dependencies (we like security fixes too), but customers can now do that on their own time; without time pressure.

Want that too? To sign up for StableBuild head to https://dashboard.stablebuild.com - there's a free community plan available, which will give you full access to all mirrors and registries with a traffic/storage limit. Paid plans start at $199 a month for unlimited access. If there’s anything missing (e.g. you build on a different OS, or want another PPA) - just give us a shout at support@stablebuild.com and we’ll add it. Or if you want to read more about how StableBuild makes builds more stable and deterministic, see the docs at https://docs.stablebuild.com/.


The StableBuild team