The Docker downloader pattern
Fetch-and-copy is way better than `apt install`
DevOps
Docker
The package manager way
Instead of running apt install ... in your Dockerfile, download pre-built binaries.
It’s faster, easier to cache, and easier to troubleshoot when network stuff goes wrong.
For example, for this blog, I had always added Hugo with apt in the build step.
Something like:
FROM node:25-bookworm-slim AS build
ARG HUGO_VERSION=0.153.2
RUN sudo apt update && sudo apt install hugo=${HUGO_VERSION}
WORKDIR /app
COPY . /app
RUN hugo --gc --minify
# ...
This works. It also takes (what feels like) an eternity when starting without a layer cache.
The fetch-and-copy way
Now I just download the binary with the curl image and copy the hugo binary to the final image:
FROM curl:latest AS downloader
ARG HUGO_VERSION=0.153.2
ARG HUGO_CHECKSUM=e16e356bfcd549ab9838dee87b24b7f7b14d65b880a6b9f2d5509a4304487eb1
WORKDIR /deps
RUN curl -L \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz" \
-o hugo.tar.gz \
&& echo "${HUGO_CHECKSUM} hugo.tar.gz" | sha256sum -c - \
&& tar -xzf hugo.tar.gz hugo
FROM node:25-bookworm-slim AS build
COPY --from=downloader /deps/hugo /usr/local/bin/hugo
WORKDIR /app
COPY . /app
RUN hugo --gc --minify
# ...
This also works. And it’s blazing fast when starting without a layer cache (e.g., when standing up a new branch deployment).
Here’s a short list of tools that I’m now installing this way: