![]() |
VOOZH | about |
Imagine that youโve just finished building an application in Phoenix, and now, youโre ready to share it with the rest of the world. You might be wondering how exactly to navigate the release environment and where to release your application.
๐ Run Phoenix Application DockerPut simply, you can release your project everywhere by using Docker. Elixir requires some runtime dependencies, mainly Erlang, in order to start up the BEAM. In the Deploying with Releases section of the Phoenix release docs, there is an excellent Dockerfile available that serves as a great starting point. In this tutorial, Iโll dissect this Dockerfile and explain what each step does, enabling you to pick and choose what parts of it you like and maybe what parts you need to change to serve your own projectโs needs. Letโs get started!
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Just as a teaser, Iโll include Node.js and npm in this post, which are not included in the official Dockerfile in the docs:
ARG ELIXIR_VERSION=1.14.0
ARG OTP_VERSION=25.0.3
ARG DEBIAN_VERSION=bullseye-20210902-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
In the code above, we define each step separately, which will make more sense later on in the tutorial. For now, we define which Elixir version we want to compile with, what Erlang OTP version we want, and what Linux image we want to serve as our workhorse.
Weโll start off by using a builder image:
FROM ${BUILDER_IMAGE} as builder
# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git nodejs npm curl \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs
# prepare build dir
WORKDIR /app
The code above includes most of the dependencies that weโll need, like Node.js, npm, and cURL. But, if you need additional ones, you can add them here. Keep in mind that not every image will need to add Node.js, so you can remove this step if you want to.
That takes care of the setup. From here on out, itโs all about our application:
# 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
COPY lib lib
COPY assets assets
WORKDIR assets
RUN node --version
RUN npm i -g yarn; yarn set version stable
RUN yarn install
WORKDIR ../
# compile assets
RUN mix assets.deploy
# Compile the release
RUN mix compile
First, we use mix to install Rebar v3 and Hex. Rebar handles native Erlang libraries, while mix gets our Elixir dependencies; you can compare it with what npm is for Node.js. Then, we copy over our mixfile, which denotes our dependencies for the project, as well as the lockfile and configs from our source code.
We then fetch all the dependencies and compile them. Note that this only compiles the dependencies and not our project files; these are two separate steps. Finally, we copy over our project files:
priv: Migrations and static fileslib: Our source codeassets: Our JavaScript and CSS codeThe next five steps are optional. If youโre using Node.js and npm, change your workdir to the assets folder, install the dependencies using either Yarn or npm, and then change the workdir back to src/.
At this point, we can deploy our assets, which is a special step that makes all our JavaScript and CSS files ready for deployment.
Next, we compile the rest of our Elixir source code, making every file in our project ready for the final build step, the release build:
# Changes to config/runtime.exs don't require recompiling the code COPY config/runtime.exs config/ COPY rel rel RUN mix release
Notice how we copy over the runtime config after the compilation step. This serves as a good reminder that all the configuration in every other config file is compiled into the release and therefore not changeable at this point. But, the config in our runtime config, as the name suggests, is read at runtime.
RUN mix release will build a release file that consists of everything we need to run our application.
# 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
# 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}/rel/myapp ./
USER nobody
CMD ["/app/bin/server"]
Again, the code above is lifted from the official Phoenix docs, but once again, Iโll clarify where the comments fall short.
Here, we reference our earlier ARGS, but this time, we only take the Linux image. This in itself makes our runtime Docker image exceptionally smaller.
Another benefit from our earlier setup where we installed node_modules, mix packages, and so on, is that to run our Elixir app, we only need the binary created from our mix release step at the end of the build part, reducing our build image size exceptionally.
We allow everyone to touch our app directory, change our user to a severely restricted user, and then run the app.
Although the steps in this article are long, basically, it boils down to the following single command that you can run:
> mix phx.gen.release --docker
The code above will generate a Dockerfile similar to the one we covered in this article, but with some differences. The standard Phoenix project does not use Node.js, and therefore, it does not include the Node.js steps I have included in this Dockerfile.
This Dockerfile serves as a starting point to deploy your Phoenix application, and in this article, Iโve shown how to change it to fit your needs. In this case, we included the npm and Node.js steps. From here, all you need to do is play around and figure out what specifics you need.
The cool thing about having a separate builder and runtime image is that there is no real cost to including too much data in the builder image. Yes, it will slow your build down, but most of these things can be cached in your pipeline, and locally, it is automatically cached. No matter what, your runtime image will be small and quick to release because it is a barebones Linux distribution.
I hope you enjoyed this article, and be sure to leave a comment if you have any questions.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.
Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.
TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension โ no new framework required.
Learn how to build a full React Native auth system using Better Auth and Expo โ with email/password login, Google OAuth, session persistence, and protected routes.
Hey there, want to help make our blog better?
Join LogRocketโs Content Advisory Board. Youโll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now