I originally agreed that it would be nice to have options in the generator to customize the variant. My work asks me to ship alpine images where possible, so it’s a frequent concern.
For example, the following could explicitly generate the current state/default:
mix phx.gen.release \
--docker \
--elixir-version='1.18.3' \
--otp-version='27.3' \
--variant=debian \
--variant-version='bullseye-20250317-slim'
--variant
could specify the OS variant to base your build/run on and --variant-version
its version.
Then again, it seemed easy enough to tweak the default Dockerfile
to support all three OS options. But with this solution, I’m back to not needing new options in the generator.
Only 2 meaningful changes. First toward the top:
...
ARG VARIANT=debian
ARG VARIANT_VERSION=bullseye-20250317-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-${VARIANT}-${VARIANT_VERSION}"
ARG RUNNER_IMAGE="${VARIANT}:${VARIANT_VERSION}"
FROM ${BUILDER_IMAGE} AS builder
ARG VARIANT
# install build dependencies
RUN case "${VARIANT}" in \
"debian" | "ubuntu") \
apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_* \
;; \
"alpine") \
apk add --no-cache build-base git \
;; \
*) \
echo "Unsupported variant: ${VARIANT}" && exit 1 \
;; \
esac
...
Here, we:
- introduce the
VARIANT
build arg
- switch
DEBIAN_VERSION
to VARIANT_VERSION
- switch
debian
to ${VARIANT}
RUN
a case
statement to toggle OS package manager setup commands based on the value of VARIANT
Then toward the end of the file:
...
FROM ${RUNNER_IMAGE}
ARG VARIANT
RUN case "${VARIANT}" in \
"debian" | "ubuntu") \
apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
&& apt-get clean && rm -f /var/lib/apt/lists/*_* \
;; \
"alpine") \
apk add --no-cache libstdc++ openssl ncurses-dev musl-locales musl-locales-lang ca-certificates \
;; \
*) \
echo "Unsupported variant: ${VARIANT}" && exit 1 \
;; \
esac
# Set the locale
ENV MUSL_LOCPATH="/usr/share/i18n/locales/musl"
RUN if [ "${VARIANT}" = "debian" ] || [ "${VARIANT}" = "ubuntu" ]; then \
unset MUSL_LOCPATH && sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen ; \
fi
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
...
Here, we:
- use the same
case
strategy to set up OS deps on the runner image
- finish out alpine locale setup by assuming an alpine build and setting
MUSL_LOCPATH
- if not alpine, unset
MUSL_LOCPATH
and perform remaining locale setup command for ubuntu/debian
I’ve adapted the above solution from this post for setting up the locale in alpine. Tho this post directed me to musl-locales-lang
over lang
.
To get the various image types:
docker build \
-t my-ubuntu-app \
--build-arg VARIANT=ubuntu \
--build-arg VARIANT_VERSION=focal-20241011 \
.
docker build \
-t my-debian-app \
--build-arg VARIANT=debian \
--build-arg VARIANT_VERSION=buster-20240612 \
.
docker build \
-t my-alpine-app \
--build-arg VARIANT=alpine \
--build-arg VARIANT_VERSION=3.21.3 \
.
Available options for VARIANT
and VARIANT_VERSION
were found by browsing the available tags on Dockerhub.
Your mileage may vary, but this at least works for me on a freshly generated LiveView app using Elixir 1.18.3 on OTP 27.3.