DEV Community

Cover image for Fly.io registry for custom base Dockerfiles
Peter Csiba
Peter Csiba

Posted on • Edited on

1

Fly.io registry for custom base Dockerfiles

Intro

From the moment my friend Tomas recommended me Fly.io, I just in love with it for my side projects. Dubbed as the "Repacked Spirit of OG Heroku" - and from the past 6 months using I can testify to the truth of the statement!

Copy-pasted Dockerfiles taking over my project

This article focuses on how to re-use your custom Docker base across multiple Fly.io apps. My personal use case were deploying multiple batch jobs each having the same Python base but a few tweaks in the end for the entrypoint or config values. Making sure all 4 of those Dockerifles are the same was somewhat annoying to me so first I was like

TOOD(P1, devx): Have a common Docker base image". 
FROM python:3.12-slim

WORKDIR /app/backend

# `git` is required by python package `supawee`
# `postgresql-dev` and `libffi-dev` are for psycopg[binary,pool] (this is always such a pain to install)
RUN apt-get update && apt-get install -y \
    git \
    libpq-dev \
# ... blah blah blah
Enter fullscreen mode Exit fullscreen mode

The moment

But then a sub-package wanted to install tiktoken which for performance reasons requires rustup so now I had to copy-past that snippet to all my Dockerfiles and it takes like 1-2 mins to build I was aaargh if I only had one base image for my project I only have to build it once! So that's how it started.

Result

Pasting two of my Dockerfiles to get you an idea of how re-using the common Dockerfile.base can help you manage your deployment easier:

FROM registry.fly.io/web-scraping-batch-jobs-base-registry:latest

CMD ["python", "-m", "batch_jobs.scraper.daily_scrape"]
Enter fullscreen mode Exit fullscreen mode

and second

FROM registry.fly.io/web-scraping-batch-jobs-base-registry:latest
ENV YOLO=true
CMD ["python", "-m", "batch_jobs.upsert_plugins"]
Enter fullscreen mode Exit fullscreen mode

Solution

Well it took me a few hours to figure this out! :face-palm:
At the high level:

  • build the base image locally
  • push it to fly.io registry
  • fly deploy with app-specific Dockerfile referencing FROM:

Resulting script

Here I employ a new style of blog writing, context-based explaining of each (hopefully) self-describing command.

#!/bin/bash
# TODO(P1, blog): Write down my experience building this for fly.io

# Usage deploy_batch_job.sh scraper/fly.toml
FLY_CONFIG_PATH=$1

# Set variables
BASE_DOCKERFILE="<your-path-to>/Dockerfile.base"
# Here `<my-project>-base-registry` is a dummy app on fly.io which is just used for storing base docker images. 
# You can create such a dummy app by `fly apps create $REGISTRY_DUMMY_APP_NAME`
REGISTRY_DUMMY_APP_NAME="<my-project>-base-registry"
IMAGE_NAME="registry.fly.io/$REGISTRY_DUMMY_APP_NAME"
# we do --platform linux/amd64 to match the one fly.io builders have
#   https://github.com/superfly/rchab/blob/8d37d90dc7d418660b50a10f288715fda4a00b5d/build.sh#L7
PLATFORM="linux/amd64" 

echo "Logging into fly.io registry"
# Authentication successful. You can now tag and push images to registry.fly.io/{your-app}
fly auth docker

echo "Building batch jobs base image $DOCKERFILE ($PLATFORM) to {$IMAGE_NAME}"

# Build the image locally
docker build --platform $PLATFORM -t $IMAGE_NAME:latest -f $DOCKERFILE .

# Get the image ID (content hash), removing the "sha256:" prefix as a 
IMAGE_ID=$(docker inspect --format='{{.Id}}' $IMAGE_NAME:latest | cut -d: -f2)
echo "Image ID: $IMAGE_ID (content hash)"
FULL_IMAGE_NAME="$IMAGE_NAME:$IMAGE_ID"

# Tag the image with its content hash
docker tag $IMAGE_NAME:latest $FULL_IMAGE_NAME

# Check if the image already exists in the registry
if docker manifest inspect $FULL_IMAGE_NAME > /dev/null 2>&1; then
    echo "Image $FULL_IMAGE_NAME"
    echo " -- already exists in the registry."
    echo " -- Skipping push."
else
    echo "Pushing new image $FULL_IMAGE_NAME to the registry."
    docker push $IMAGE_NAME:$IMAGE_ID
    docker push $IMAGE_NAME:latest
fi

echo "Deploying the batch job $FLY_CONFIG_PATH"
# To debug problems with Fly.io app builders you can find them at https://fly.io/dashboard/<your-organization-snake-case>/builders
# or with CLI fly logs -a fly-builder-<assigned-builder-name> (get it from logs)
fly deploy --config $FLY_CONFIG_PATH
Enter fullscreen mode Exit fullscreen mode

Discussion

Was it worth it?

Well, I conclude that this effort to be net positive for humanity I had to share it! Cause truth to be told, continuing doing careful copy-pasting (a synonym to devops from a devops person I really appreciate) would been more effective for me. But hey, we lazy engineers who want to automate our job right?

Also #lesscodelessbugs

Feature: If the image is same, do not push it

It took a few Claude Sonet (I am sick of ChatGPT for programming) I managed to setup a content-hash based push if doesn't yet exist approach to save some deploy time. Cause you know, if deploy takes over 30 seconds you tab-out to some stupid YouTube video or visit MK and then it takes 5mins to deploy.

Some gotchas

  • There is the --platform flag to match your local build with the fly.io build. Some versions of Docker Client (like Docker Desktop) had problems with that. I personally use colima which is also much more battery efficient.

Possible improvements

  • Would be nice to build the Dockerfile.base using the fly.io workers; certainly possible with I guess fly.toml.
  • FROM <your-base-image> takes some while even when both imae

Appendix

The full git commit setting this up in my side-project to see a
"production" version linked here on my github.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

Try REST API Generation for Snowflake

DevOps for Private APIs. Automate the building, securing, and documenting of internal/private REST APIs with built-in enterprise security on bare-metal, VMs, or containers.

  • Auto-generated live APIs mapped from Snowflake database schema
  • Interactive Swagger API documentation
  • Scripting engine to customize your API
  • Built-in role-based access control

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay