Docker release build of Phoenix app stuck at mix assets.deploy

Has anyone hit this issue trying to build a Phoenix app within a docker container?

It get’s stuck at the # Rebuilding step:

[+] Building 1461.1s (24/31)                                                                    docker:orbstack
 => => transferring context: 146.18MB                                                                      0.9s
 => [stage-1 1/6] FROM  0.0s
 => CACHED [stage-1 2/6] RUN apt-get update -y &&   apt-get install -y libstdc++6 openssl libncurses5 loc  0.0s
 => CACHED [stage-1 3/6] RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen                  0.0s
 => CACHED [stage-1 4/6] WORKDIR /app                                                                      0.0s
 => CACHED [stage-1 5/6] RUN chown nobody /app                                                             0.0s
 => CACHED [builder  2/20] RUN apt-get update -y && apt-get install -y build-essential git erlang-ssl      0.0s
 => [builder  3/20] RUN mkdir /client                                                                      0.2s
 => [builder  4/20] COPY client /client                                                                    0.4s
 => [builder  5/20] WORKDIR /app                                                                           0.0s
 => [builder  6/20] RUN mix local.hex --force &&     mix local.rebar --force                              39.7s
 => [builder  7/20] COPY portal/mix.exs portal/mix.lock ./                                                 0.0s
 => [builder  8/20] RUN mix deps.get --only prod                                                          25.0s
 => [builder  9/20] RUN mkdir config                                                                       0.3s
 => [builder 10/20] COPY portal/config/config.exs portal/config/prod.exs config/                           0.0s
 => [builder 11/20] RUN mix deps.compile                                                                  58.7s
 => [builder 12/20] COPY portal/priv priv                                                                  0.1s
 => [builder 13/20] COPY portal/lib lib                                                                    0.0s
 => [builder 14/20] COPY portal/assets assets                                                              0.0s
 => [builder 15/20] RUN mix assets.deploy                                                               1321.6s
 => => # 15:18:44.982 [debug] Downloading tailwind from
 => => # wnload/v3.2.4/tailwindcss-linux-x64                                                                    
 => => # Rebuilding...              

The only non standard wrinkle is I’m using an amd64 ubuntu image to build the app on an M1 Mac. This was working fine in an earlier version so I’m starting to tear my hair out trying to figure out what’s changed! :smile:

This is my Dockerfile:

# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
# instead of Alpine to avoid DNS resolution issues in production.
# This file is based on these images:
#   - - for the build image
#   - - for the release image
#   - - resource for finding needed packages
#   - Ex: hexpm/elixir:1.16.0-erlang-26.2.1-debian-bullseye-20231009-slim
ARG UBUNTU_VERSION=bionic-20230126


FROM --platform=linux/amd64 ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git erlang-ssl \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

RUN mkdir /client
COPY client /client

# prepare build dir

# install hex + rebar
RUN mix local.hex --force && \
    mix local.rebar --force

# set build ENV
ENV MIX_ENV="prod"
ENV ERL_FLAGS="+JPperf true"

# install mix dependencies
COPY portal/mix.exs portal/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 portal/config/config.exs portal/config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY portal/priv priv

COPY portal/lib lib

COPY portal/assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
RUN mix compile

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

COPY portal/rel rel
RUN mix release
RUN ls /app/_build/${MIX_ENV}/

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

RUN apt-get update -y && \
  apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
  && 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


WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"



# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV} ./

USER nobody

# If using an environment that doesn't automatically reap zombie processes, it is
# advised to add an init process such as tini via `apt-get install`
# above and adding an entrypoint. See for details
# ENTRYPOINT ["/tini", "--"]

CMD ["/bin/bash"]

Do you have a package.json? Esbuild uses your local node version to download and compile JS assets. The default docker container created with releases doesn’t include Node. If you’re vendoring your JS dependencies yourself then you don’t need Node though.

It’s possible that isn’t your issue at all but I’ve seen it catch several people off guard, including myself.

No package.json. Just the vanilla app.js etc generated by Phoenix. It seems like an issue with tailwind. If I remove tailwind default --minify from the mix assets.deploy alias then it all works fine. I’ll try bumping the tailwind version and see what happens. It’s strange because that step works if I run it on the native machine.