By the end of this quick guide, you will know how to compile a Phoenix app release using Exrm and run it inside a Docker container. I've found only a couple of articles that discuss getting an Elixir app up and running inside a Docker container, and even those only touched on some parts of the process. The idea is that this guide will give you a full end-to-end example of how to get all the pieces and parts working together so that you are able to deploy your Phoenix application inside a Docker container.
- You already have a working Elixir environment with the Phoenix Framework installed
- You have at least basic working knowledge of Docker, and have installed the Docker tools onto your local environment
There are only a few things you have to do in order to get things all set up. At a high level, our task list looks like this:
- Configure your Phoenix app for Exrm
- Configure Relx, the Erlang release manager that underlies Exrm [OPTIONAL]
- Setup your Docker image
At this point you will be set up for compiling releases using Exrm. Building a Dockerized release is just a matter of compiling a new release, adjusting the Dockerfile to grab the new release, and then rebuilding the Docker container.
Before you get started on this part, create a new hello_phoenix app right now that you can use as your test dummy:
mix phoenix.new hello_phoenix
First, add the exrm
dependency to your mix.exs
file. At the time of this writing, the latest Exrm version is 1.0.3.
defp deps do
[{:phoenix, "~> 1.1.4"},
{:postgrex, ">= 0.0.0"},
{:phoenix_ecto, "~> 2.0"},
...
{:exrm, "~> 1.0.3"}]
end
Next make sure all of your production dependencies are listed in the :applications
list inside application/0
of mix.exs
. In the hello_phoenix
application, this step should already be done for you. If you forget to do this in another project, Exrm may continually fail citing unmet dependencies.
NOTE: While you're poking around inside
mix.exs
, take note of the version number inside our project definition at the top of the file. This is the version number that Exrm will use when it packages up a release. When you want to bump the release version, this is where you make that change.
In production you want your production release to act as a server. Open up config/prod.exs
and add the server: true
option to the main config:
config :hello_phoenix, HelloPhoenix.Endpoint,
http: [port: 8888], # put your desired port here
url: [host: "example.com"],
cache_static_manifest: "priv/static/manifest.json",
server: true
You are now ready to go ahead and build your first release. The basic flow is four steps:
- Install dependencies
- Digest and compress assets
- Compile the Phoenix project
- Package release
Of course, you'll want to make sure you have all of our dependencies in order, so start off by making sure all of your required dependencies are installed.
$ mix do deps.get, compile
Next, compress assets for your production environment.
$ MIX_ENV=prod mix phoenix.digest
In older versions of Exrm, this next step was not required, but as of this writing, you will need to run it manually.
$ MIX_ENV=prod mix compile
And finally, the first big payoff; it's time to build the actual release with Exrm:
$ MIX_ENV=prod mix release
NOTE: if you run into errors while building a release, running with the
--verbosity=verbose
flag should give you more clues as to what is going on.
As the build output suggests, you can now try running your release as a sanity check that it all worked:
$ rel/hello_phoenix/bin/hello_phoenix console
With any luck, you should now be in an iex console, and if you visit http://localhost:8888, you should see your application. If your app doesn't appear to be responding to your browser request, make sure that you have set server: true
in config/prod.exs
Going forward, whenever you want to build a new release, you'll need to run those same 4 commands. Here they are again all in one place:
mix do deps.get, compile
MIX_ENV=prod mix phoenix.digest
MIX_ENV=prod mix compile
MIX_ENV=prod mix release
If you are like me, you are doing all this fancy release building locally on your OSX development environment. This causes some extra issues, since the default behavior of relx
(the tool that Exrm uses to actually package the release) is to bundle up the local Erlang Run-Time System (ERTS) binaries. Since our production environment is a Linux Docker image, our OSX binaries will not be compatible.
There are two solutions to this cross-platform issue, one is you have binaries for each environment stored locally, and then tell relx to include those in the release, the far simpler option is to just tell relx not to bundle up ERTS, and instead to just us whatever version is available on the host system. There are some caveats to this, but for most scenarios, this option should suffice. For those with sufficient time and interest, read this comment for slightly more details as to what this config change means.
To disable this ERTS bundling, create a new file rel/relx.config
(you may need to create the rel
directory first) and add the following:
{include_erts, false}.
{system_libs, false}.
NOTE: This file contains Erlang tuples, not Elixir tuples.
ANOTHER NOTE: Be warned that if you ever run a full implode to clean builds (
mix release.clean --implode
as opposed to the usualmix release.clean
) this file will definitely be blown away. Be sure to recreate it if you ever--implode
your builds
After making this change, feel free to sanity check that things are still working as expected:
$ MIX_ENV=prod mix release
$ rel/hello_phoenix/bin/hello_phoenix console
Alright, you now have a packaged release, it's time to get this thing running inside a Docker container.
The Dockerfile setup is just a matter of setting some environment variables about our release, creating a home where releases will live, unpacking the release, and then telling Docker how to start up the server.
Since you may already have your Docker image of choice, I will only be covering the additional configuration lines in addition to whatever already exists to set up your base image. If you don't have an image selected, I recommend using @trenpixster's elixir-dockerfile, which is the same one used for this guide. The key is to make sure that whatever base Docker image you create contains both Erlang and Elixir support.
Once you have your Dockerfile starting point, add the following commands at the end of the config. This tell Docker to pull in the application release, unpack it, and configure it. Note that you set a VERSION
environment variable here. This should match the project's version setting inside mix.esx
.
...
ENV VERSION 0.0.1
ENV MIX_HOST 8888
EXPOSE $MIX_HOST
RUN mkdir /app
WORKDIR /app
COPY ./rel/hello_phoenix/releases/$VERSION/hello_phoenix.tar.gz /app/hello_phoenix.tar.gz
RUN tar -zxvf hello_phoenix.tar.gz
WORKDIR /app/releases/$VERSION
ENTRYPOINT ["./hello_phoenix.sh"]
CMD ["foreground"]
# Start up in 'foreground' mode by default so the container stays running
NOTE: See that version number in the path of the COPY command? If you change the project's version number in your
mix.exs
file, you'll need to make the same change here
Now that you have your Dockerfile configured, build your new/updated Docker image.
$ docker build -t hello_phoenix .
Now run that baby, and give it a name, 'hello_phoenix' to make our life a tad easier, and bind it to a local port so you can hit it with our browser after you start your Phoenix application up.
$ docker run -p 8888:8888 --name hello_phoenix -d hello_phoenix
With any luck, you should see Cowboy start listening on port 8888. The easiest way to actually view your application is to open up Docker's Kitematic app, select your newly lanched hello_app
container, and you should see your Phoenix app inside the "Web Preview" sidebar. You now have your Phoenix application running inside a Docker container. For now I'll leave it as an excercise to the reader to continue on to deloying to a cloud service. Leave a comment below if you would like to see another guide on the cloud deploy process.
NOTE: to stop your running Docker container, you can either use the controls inside Kitematic, or from the command line run
docker stop hello_phoenix
and then clean up the container, withdocker rm hello_phoenix
That's it, you did it! Going forward, after you've made changes to your application, to build your new Dockerized release, you'll only need to repeat the last few steps you went over:
$ MIX_ENV=prod mix do phoenix.digest, compile, release
$ docker build -t hello_phoenix .
Comment below and share your thoughts on how you currently handle your own Phoenix deployments (whether using containers or not). This guide just touched the surface of a containerized build process, so I'd love to hear how you are integrating this or similar workflows into a continuous integration solution like Jenkins or Travis.
Brien Wankel is a Principal Engineer at the product development group Lab Zero. Do you need help with your Phoenix project, or are you thinking of migrating from Ruby to Elixir? If so, give us a shout; we'd love to see how we can help.
i will do the build in the dockerfile, that way i will get the same environment as on my server.
also i can build the image on docker hub. Any caveats this ?