Docker Build fails on phx.digest

Hi!

I’m trying to deploy a project to fly.io, and for that I need to create a docker file. However, it seems to fail on phx.digest. The project can be found at: monorepo/solo_leveling_go at master · JulianBuse/monorepo · GitHub

The Dockerfile:

# 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?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=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?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
#   - https://pkgs.org/ - resource for finding needed packages
#   - Ex: hexpm/elixir:1.12.3-erlang-24.1.4-debian-bullseye-20210902-slim
#
ARG BUILDER_IMAGE="hexpm/elixir:1.12.3-erlang-24.1.4-debian-bullseye-20210902-slim"
ARG RUNNER_IMAGE="debian:bullseye-20210902-slim"

FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git npm \
    && apt-get clean && rm -f /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
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

# note: if your project uses a tool like https://purgecss.com/,
# which customizes asset compilation based on what it finds in
# your Elixir templates, you will need to move the asset compilation
# step down so that `lib` is available.
COPY assets assets

# For Phoenix 1.6 and later, compile assets using esbuild
RUN mix assets.deploy

# For Phoenix versions earlier than 1.6, compile assets npm
# RUN cd assets && yarn install && yarn run webpack --mode production
# RUN mix phx.digest

# Compile the release
COPY lib lib

RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
  && apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
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

WORKDIR "/app"
RUN chown nobody /app

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/prod/rel ./

USER nobody

# Create a symlink to the application directory by extracting the directory name. This is required
# since the release directory will be named after the application, and we don't know that name.
RUN set -eux; \
  ln -nfs /app/$(basename *)/bin/$(basename *) /app/entry

CMD /app/entry start

mix.exs:

defmodule SoloLevelingGo.MixProject do
  use Mix.Project

  def project do
    [
      app: :solo_leveling_go,
      version: "0.1.1",
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: [:gettext] ++ Mix.compilers() ++ [:surface],
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {SoloLevelingGo.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(:dev), do: ["lib"] ++ catalogues()
  defp elixirc_paths(_), do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.6.2"},
      {:phoenix_html, "~> 3.0"},
      {:esbuild, git: "https://github.com/phoenixframework/esbuild", runtime: Mix.env() == :dev},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.16.0"},
      {:floki, ">= 0.30.0", only: :test},
      {:phoenix_live_dashboard, "~> 0.5"},
      {:swoosh, "~> 1.3"},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.18"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"},
      {:surface, "~> 0.6.0"},
      {:surface_formatter, "~> 0.6.0"},
      {:surface_catalogue, "~> 0.2.0"},
      {:ecstatic, "~> 0.1.0"},
      {:libcluster, "~> 3.3"},
      {:horde, "~> 0.8.5"}
    ]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to install project dependencies and perform other setup tasks, run:
  #
  #     $ mix setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    [
      setup: ["deps.get"],
      "assets.deploy": [
        "esbuild default --minify",
        "phx.digest"
      ]
    ]
  end

  def catalogues do
    [
      "priv/catalogue"
    ]
  end
end

By The way, this is the console output:

#22 [builder 12/17] RUN mix assets.deploy
#22 sha256:f961d3e48fde4f638baab4e7a42196426613a107d43a8377ef98ece2b05fe7af
#22 0.534
#22 0.534 20:21:26.468 [debug] Downloading esbuild from https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.12.18.tgz
#22 3.433
#22 3.433   ../priv/static/assets/app.js   79.2kb
#22 3.433   ../priv/static/assets/app.css  10.8kb
#22 3.433
#22 3.433 ⚡ Done in 11ms
#22 3.486 Generated solo_leveling_go app
#22 4.141 ** (ArgumentError) unknown application: :solo_leveling_go
#22 4.141     (elixir 1.12.3) lib/application.ex:893: Application.app_dir/1
#22 4.141     (surface 0.6.1) lib/mix/tasks/compile/surface.ex:137: Mix.Tasks.Compile.Surface.app_modules/1
#22 4.141     (surface 0.6.1) lib/mix/tasks/compile/surface.ex:57: anonymous fn/2 in Mix.Tasks.Compile.Surface.get_colocated_assets/0
#22 4.141     (elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
#22 4.141     (surface 0.6.1) lib/mix/tasks/compile/surface.ex:14: Mix.Tasks.Compile.Surface.run/1
#22 4.141     (mix 1.12.3) lib/mix/task.ex:394: anonymous fn/3 in Mix.Task.run_task/3
#22 4.141     (mix 1.12.3) lib/mix/tasks/compile.all.ex:92: Mix.Tasks.Compile.All.run_compiler/2
#22 4.141     (mix 1.12.3) lib/mix/tasks/compile.all.ex:72: Mix.Tasks.Compile.All.compile/4
#22 ERROR: executor failed running [/bin/sh -c mix assets.deploy]: exit code: 1
------
 > [builder 12/17] RUN mix assets.deploy:
------
executor failed running [/bin/sh -c mix assets.deploy]: exit code: 1

Calling mix assets.deploy compiles your app either way, so it should be called after mix compile. It seems your app is simply not compiling.

As a side note, you’re doing a lot of cleanup work in the builder image which seems unnecessary with multi-stage Docker builds.