Local dependency path and docker

I am totally new to the concept of Docker so I’m sure this is a dumb question.
My app currently is structured as

app_root
|_lib
|_app2
|_app3

In app_root/mix.exs I have the dependency {:app2, path: "./app2"} and in app_root/app2/mix.exs I have the dependency {:app3, path: "../app3"}. This all works happily locally and builds with mix release but when I try to deploy with Docker it complains

Cannot compile dependency :app2 because it isn’t available, please ensure the dependency is at “app2”.

What should the path be in the mix.exs for local dependencies to be found by Docker?

As best I can determine the issue is that the relative path to app2 is being determined relative to the path of the docker executable. I’ve tried changing the path in app_root/mix.exs to "#{\_\_DIR\_\_}/app2" but this does not change the result.

Partial solution so far. Had to edit the Dockerfile to COPY the directories but it’s still not right. For instance, app3 has a file in an assets directory that is not found, leading the whole app to crash on deployment.

There are quite a few Dockerfile examples in the blog sphere, I’d recommend you to find a few and compare with what you’re doing.

Dockerizing a project the first time certainly can get little annoying but it ain’t no rocket science either.

You’re already on the right path: you have to COPY all relevant files inside the container. Then there’s mix deps.get et. al. afterwards.

So here’s my Dockerfile:

# 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 ./app1/mix.exs ./app1/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 ./app1/config/config.exs ./app1/config/${MIX_ENV}.exs config/
COPY ./app3 /app3
COPY ./app2 /app2
RUN mix deps.compile

COPY ./app1/priv priv

COPY ./app1/assets assets
# compile assets
RUN mix assets.deploy

# Compile the release
COPY ./app1/lib lib

RUN mix compile

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

COPY ./app1/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/app1 ./

USER nobody

CMD ["/app/bin/server"]

# Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"

I would assume with this line COPY ./app3 /app3 that the directory and all subdirectories, including the assets are copied over. This appears not to be the case. There is no app3 directory at all in the final Docker image. Do I need to compile each app in a separate step to ensure the assets get compiled with them?

assets is considered source; in the release there should not be assets at all, only priv/static.mix only compile the assets of the current application; not for any of the dependencies. If you have some assets in a dependency that need to be compiled, you will need to do it explicitly in a separate mix invocation.

1 Like

So what does that look like in practice? Do I copy app3 then compile that app before copying over app1 and compiling app1? If I do that, do I change the dependency entry in app1/mix.exs?

I guess you can do:

RUN mix assets.deploy

in dir3?

For me, I don’t build in docker; I only deploy in docker. I don’t have a docker driven CI flow. So I just make a environment that is binary compatible with my targeted deployment environment (AMD64 Linux) and just normal release there manually, then copy into the docker image for deployment.

1 Like

That sounds like the much easier flow. I’m just starting with the default Dockerfile provided by fly.io. I did not really intend to learn how to use Docker at this time. I thought I’d just upload the compiled release and be done with it. Sounds like I can actually do that if I set up the Dockerfile for that workflow.

Posting only because it might help you avoid Docker: you can use Render’s native Elixir environment: Deploy a Phoenix App with Mix Releases | Render Docs (think buildpacks but cleaner).

2 Likes

I gave up on that assets file and just changed the local dependency to not need it. I tried dozens of permutations of compilation and copy commands to get Docker to make it work to no avail. Final stumbling block was getting the check_origin setting in runtime.exs and the internal port setting in fly.toml correct. Might have spent more time trying to deploy this stupid toy app than it took to code the whole thing. Feels like I learned less in the process because it was all just fumbling in the dark.