# Multi-stage build for the Shadix docs site (the dev harness compiled for # production as the `shadix_docs` release, MIX_ENV=docs). # # Stage 1 builds assets + the OTP release; stage 2 is a slim runtime that only # carries the assembled release. Both stages are Alpine (musl): the release is # dynamically linked against the builder's libc, so the runner libc must match. # Base image versions track mise.toml (Erlang 29.0.2 / Elixir 1.20.1). ARG ELIXIR_IMAGE="hexpm/elixir:1.20.1-erlang-29.0.2-alpine-3.21.7" ARG RUNNER_IMAGE="alpine:3.21" ARG NODE_IMAGE="node:24-alpine" # ---- Stage 1: build ---------------------------------------------------------- FROM ${ELIXIR_IMAGE} AS builder # Node + npm are needed only to install the assets/ JS deps (@floating-ui/dom) # that esbuild bundles. Copy the musl build from node:alpine so it runs here. COPY --from=node:24-alpine /usr/local/bin/node /usr/local/bin/node COPY --from=node:24-alpine /usr/local/lib/node_modules /usr/local/lib/node_modules RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm # build-base/git for any native build steps; libstdc++ + libgcc let the copied # node binary and the esbuild/tailwind standalone CLIs run on musl. RUN apk add --no-cache build-base git ca-certificates libstdc++ libgcc WORKDIR /app # Build in the dedicated docs environment. ENV MIX_ENV="docs" # Give hex more headroom on slow/flaky networks (default is 15s per request). ENV HEX_HTTP_TIMEOUT=120 RUN for i in 1 2 3 4 5; do mix local.hex --force && mix local.rebar --force && break; \ echo "local.hex/rebar attempt $i failed; retrying..."; sleep 5; done # Fetch deps first (cached unless mix.exs/mix.lock/config change). Retry to ride # out transient hex registry timeouts. COPY mix.exs mix.lock ./ COPY config config RUN for i in 1 2 3 4 5; do mix deps.get --only $MIX_ENV && break; \ echo "deps.get attempt $i failed; retrying..."; sleep 5; done RUN mix deps.compile # Install the esbuild/tailwind standalone binaries used by `mix assets.build`. # On musl these resolve to the -musl builds automatically. RUN mix esbuild.install --if-missing && mix tailwind.install --if-missing # Install assets JS deps (esbuild resolves @floating-ui/dom from here). COPY assets assets RUN cd assets && npm install # Copy source and build assets + the release. COPY lib lib COPY website website COPY priv priv RUN mix assets.build RUN mix compile RUN mix release shadix_docs # ---- Stage 2: runtime -------------------------------------------------------- FROM ${RUNNER_IMAGE} AS runner # Runtime libs the BEAM links against on Alpine. RUN apk add --no-cache libstdc++ openssl ncurses-libs libgcc ca-certificates # musl ships a built-in UTF-8 locale; just point the BEAM at it. ENV LANG=C.UTF-8 WORKDIR /app RUN chown nobody /app ENV MIX_ENV="docs" # Fly maps its proxy to this port; runtime.exs also defaults to 8080. ENV PORT=8080 COPY --from=builder --chown=nobody:root /app/_build/docs/rel/shadix_docs ./ USER nobody EXPOSE 8080 CMD ["/app/bin/shadix_docs", "start"]