The cdo install pulls ~1 GB of transitive deps (netCDF/HDF5/ecCodes/ Fortran) and was exhausting CI's /var/cache/apt partition. Two fixes: - `APT::Keep-Downloaded-Packages=false` deletes each .deb immediately after it's extracted, so the archive cache doesn't retain every downloaded package through the end of the install. - Split cdo into its own RUN layer so the base runtime deps finish installing and clean before cdo's cache starts growing.
189 lines
6.8 KiB
Docker
189 lines
6.8 KiB
Docker
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
|
|
# instead of Alpine to avoid DNS resolution issues in production.
|
|
#
|
|
# https://hub.docker.com/r/hexpm/elixir/tags?name=ubuntu
|
|
# https://hub.docker.com/_/ubuntu/tags
|
|
#
|
|
# This file is based on these images:
|
|
#
|
|
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
|
|
# - https://hub.docker.com/_/debian/tags?name=trixie-20260316-slim - for the release image
|
|
# - https://pkgs.org/ - resource for finding needed packages
|
|
# - Ex: docker.io/hexpm/elixir:1.19.5-erlang-28.4.1-debian-trixie-20260316-slim
|
|
#
|
|
ARG ELIXIR_VERSION=1.19.5
|
|
ARG OTP_VERSION=28.4.1
|
|
ARG DEBIAN_VERSION=trixie-20260316-slim
|
|
|
|
ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
|
|
ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}"
|
|
|
|
# ---- wgrib2 build stage (cached independently) ----
|
|
FROM ${RUNNER_IMAGE} AS wgrib2-builder
|
|
|
|
ARG WGRIB2_VERSION=3.6.0
|
|
# g2c v2.1.0 is the first release where BUILD_G2C defaults ON, which
|
|
# is what wgrib2 v3.6.0 needs at link time (g2c_enc_jpeg2000 /
|
|
# g2c_dec_jpeg2000 come from the "new" file-based API). v1.9.0's
|
|
# same flag was OFF by default with a note about the API being
|
|
# experimental until 2.0.0.
|
|
ARG G2C_VERSION=2.1.0
|
|
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
build-essential gfortran cmake wget ca-certificates pkg-config \
|
|
zlib1g-dev libaec-dev libpng-dev libopenjp2-7-dev \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# NCEPLIBS-g2c: wgrib2 >= 3.x delegates GRIB2 decompression to this
|
|
# library via find_package whenever any of JASPER / OPENJPEG / PNG
|
|
# is enabled (wgrib2 CMakeLists:94). Build g2c with all the
|
|
# decoders we care about — PNG (MRMS PrecipRate / HRRR native),
|
|
# OpenJPEG (modern JPEG2000 replacement for the CVE-riddled Jasper),
|
|
# and AEC (CCSDS) — and disable Jasper so we don't need libjasper,
|
|
# which Debian dropped years ago.
|
|
WORKDIR /tmp/g2c
|
|
RUN wget -q --content-disposition "https://github.com/NOAA-EMC/NCEPLIBS-g2c/archive/refs/tags/v${G2C_VERSION}.tar.gz" \
|
|
-O g2c.tar.gz \
|
|
&& tar xzf g2c.tar.gz \
|
|
&& cd NCEPLIBS-g2c-${G2C_VERSION} \
|
|
&& mkdir build && cd build \
|
|
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DUSE_PNG=ON \
|
|
-DUSE_AEC=ON \
|
|
-DUSE_OpenJPEG=ON \
|
|
-DUSE_Jasper=OFF \
|
|
&& make -j$(nproc) \
|
|
&& make install
|
|
|
|
WORKDIR /tmp/wgrib2
|
|
|
|
# wgrib2 flags:
|
|
# * USE_PNG / USE_OPENJPEG / USE_AEC each turn on the matching
|
|
# encoder path inside wgrib2 and add the compression name to the
|
|
# -config output. With g2c v2.1.0 underneath (which exports the
|
|
# g2c_enc_* / g2c_dec_* new-API symbols) all three can be enabled
|
|
# together — the earlier build failure was not about the flag
|
|
# combination, it was g2c v1.9.0 shipping without BUILD_G2C.
|
|
RUN wget -q --content-disposition "https://github.com/NOAA-EMC/wgrib2/archive/refs/tags/v${WGRIB2_VERSION}.tar.gz" \
|
|
-O wgrib2.tar.gz \
|
|
&& tar xzf wgrib2.tar.gz \
|
|
&& cd wgrib2-${WGRIB2_VERSION} \
|
|
&& mkdir build && cd build \
|
|
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release \
|
|
-DUSE_PNG=ON -DUSE_OPENJPEG=ON -DUSE_AEC=ON \
|
|
&& make -j$(nproc) \
|
|
&& make install \
|
|
&& strip /usr/local/bin/wgrib2
|
|
|
|
# ---- Elixir build stage ----
|
|
FROM ${BUILDER_IMAGE} AS builder
|
|
|
|
# install build dependencies
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends build-essential git \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# prepare build dir
|
|
WORKDIR /app
|
|
|
|
# install hex + rebar
|
|
RUN mix local.hex --force \
|
|
&& mix local.rebar --force
|
|
|
|
# set build ENV
|
|
ENV MIX_ENV="prod"
|
|
|
|
# install mix dependencies (changes only when mix.exs/mix.lock change)
|
|
COPY mix.exs mix.lock ./
|
|
COPY vendor vendor
|
|
RUN mix deps.get --only $MIX_ENV
|
|
|
|
# copy compile-time config before compiling deps
|
|
RUN mkdir config
|
|
COPY config/config.exs config/${MIX_ENV}.exs config/
|
|
RUN mix deps.compile
|
|
|
|
# install esbuild + tailwind binaries (cached until mix.exs changes)
|
|
RUN mix assets.setup
|
|
|
|
# create priv dir so :code.priv_dir works at compile time
|
|
# (actual contents copied after compile to avoid cache busting)
|
|
RUN mkdir -p priv/models priv/static
|
|
|
|
# copy application code and compile
|
|
COPY lib lib
|
|
COPY algo.md algo.md
|
|
# agent-skills markdown is read at compile time by AgentSkillsController
|
|
# (sha256 digests are baked into the module attribute)
|
|
COPY priv/agent-skills priv/agent-skills
|
|
RUN mix compile
|
|
|
|
# copy assets and build (JS/CSS changes don't require recompilation)
|
|
COPY assets assets
|
|
RUN mix assets.deploy
|
|
|
|
# copy priv contents (migrations, ML model, static files)
|
|
# after compile so model retraining doesn't bust the compile cache
|
|
COPY priv priv
|
|
|
|
# runtime config doesn't require recompilation
|
|
COPY config/runtime.exs config/
|
|
|
|
COPY rel rel
|
|
RUN mix release
|
|
|
|
# ---- Final runtime stage ----
|
|
FROM ${RUNNER_IMAGE} AS final
|
|
|
|
# Build timestamp baked in by CI (--build-arg BUILD_TIMESTAMP=...).
|
|
# Only consumed in this final stage, so it never busts the earlier
|
|
# compile cache. Read at runtime by Microwaveprop.Application.build_timestamp/0.
|
|
ARG BUILD_TIMESTAMP=unknown
|
|
|
|
# Base runtime deps, without cdo. `APT::Keep-Downloaded-Packages=false`
|
|
# deletes each .deb immediately after install so apt's archive cache
|
|
# doesn't pile up and OOM the /var/cache/apt disk partition the CI
|
|
# builder runs on.
|
|
RUN apt-get update \
|
|
&& apt-get -o APT::Keep-Downloaded-Packages=false install -y --no-install-recommends \
|
|
libstdc++6 openssl libncurses6 locales ca-certificates snmp \
|
|
libgfortran5 libaec0 zlib1g libpng16-16t64 libopenjp2-7 \
|
|
&& apt-get clean \
|
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
|
|
|
|
# cdo is ~1 GB of transitive deps (netCDF, HDF5, ecCodes, Fortran
|
|
# runtime). Splitting into its own RUN layer means the base deps above
|
|
# are already extracted + pruned before the cdo cache starts filling,
|
|
# which is what kept the image under the 2 GB CI disk cap.
|
|
RUN apt-get update \
|
|
&& apt-get -o APT::Keep-Downloaded-Packages=false install -y --no-install-recommends cdo \
|
|
&& apt-get clean \
|
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
|
|
|
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
|
|
|
ENV LANG=en_US.UTF-8
|
|
ENV LANGUAGE=en_US:en
|
|
ENV LC_ALL=en_US.UTF-8
|
|
ENV MIX_ENV="prod"
|
|
|
|
WORKDIR "/app"
|
|
RUN chown nobody /app
|
|
|
|
COPY --from=wgrib2-builder /usr/local/bin/wgrib2 /usr/local/bin/wgrib2
|
|
# wgrib2 links dynamically against libg2c.so from the builder stage —
|
|
# carry it across and refresh the loader cache so /usr/local/lib is
|
|
# searched at exec time.
|
|
COPY --from=wgrib2-builder /usr/local/lib/libg2c.so* /usr/local/lib/
|
|
RUN ldconfig
|
|
|
|
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/microwaveprop ./
|
|
|
|
RUN echo "${BUILD_TIMESTAMP}" > /app/BUILD_TIMESTAMP \
|
|
&& chown nobody /app/BUILD_TIMESTAMP
|
|
|
|
USER nobody
|
|
|
|
CMD ["/app/bin/server"]
|