A Complete Guide to Deploying Elixir & Phoenix Applications on Kubernetes — Part 2: Docker and Minikube

Rohan Relan
Polyscribe
Published in
4 min readMay 9, 2017

At Polyscribe, we use Elixir and Phoenix for our real-time collaboration and GraphQL API backends and Kubernetes for our deployment infrastructure. In this series, I walk through the setup we used from start to finish to create a system that supports the following:

  • Automatic clustering for Elixir and Phoenix channels
  • Auto-scaling to respond to spikes in demand
  • Service discovery for microservices, including those in other frameworks like Node.js
  • Maintaining the exact same environment between staging and production and easily deploying from staging to production
  • Relatively easy to setup and manage

Other posts in this series — Part 1: Setting up Distillery, Part 3: Deploying to Kubernetes, Part 4: Secret Management, Part 5: Clustering Elixir and Phoenix Channels

Now that we have our application ready to build releases, the next step is to create a Docker image to host the release and deploy that image on to a Kubernetes cluster. Before we start, some prerequisites:

  • Make sure your Elixir application building releases with Distillery configured by environment variables as described in Part 1 of this series
  • install Docker
  • install and start Minikube. Minikube will let us bring up a small Kubernetes cluster on our local machine that we can use to test our deployment

Building Docker images for our release

Because Distillery actually compiles code when it builds a release, it requires that the platform (OS, architecture etc.) in which you build your release is the same as the the platform in which you run your release. For example, you can’t build your release on a Mac and then run the release on an Ubuntu VM or within an Ubuntu container.

This is why our set up is going to use two Docker images running the exact same OS: 1) the build image (which will only be run on development/CI machines) containing all the dependencies needed to build the release 2) the release image (which will be run on the Kubernetes cluster) with minimal dependencies since releases are self-contained.

To simplify, this process, we’re going to use mix_docker, a Hex package that gives us a few Mix tasks to generate our images. Add {:mix_docker, “~> 0.3.0”} to your deps and run mix deps.get to fetch it. (If you get an error from Mix complaining about your Distillery version, you can modify your Distillery dependency in mix.exs with an override to force it to use the current version like so:{:distillery, “~> 1.0”, override: true}). We’ll also configure mix_docker by adding config :mix_docker, image: “myapp” to our config/config.exs.

Now we have two Mix tasks, mix docker.build and mix docker.release, for building our build and release images respectively. However, before we do that we’re going to modify the default build and release images provided by mix_docker . Create two new files in the root of your application named Dockerfile.build and Dockerfile.release with the following contents:

# Dockerfile.buildFROM elixir:1.4.2
ENV DEBIAN_FRONTEND=noninteractive
ENV HOME=/opt/app/ TERM=xterm# Install Hex+Rebar
RUN mix local.hex --force && \
mix local.rebar --force
WORKDIR /opt/app
ENV MIX_ENV=prod REPLACE_OS_VARS=true
# Cache elixir deps
COPY mix.exs mix.lock ./
RUN mix deps.get
COPY config ./config
RUN mix deps.compile
COPY . .
RUN mix release --env=prod

# Dockerfile.release
FROM elixir:1.4.2
ENV DEBIAN_FRONTEND=noninteractive
EXPOSE 8000
ENV PORT=8000 MIX_ENV=prod REPLACE_OS_VARS=true SHELL=/bin/bash
WORKDIR /app
COPY ./myapp.tar.gz ./
RUN tar xfz myapp.tar.gz
ENTRYPOINT ["bin/myapp"]

Sidenote: Unlike the default Dockerfiles used by mix_docker, these Dockerfiles aren’t based on Alpine Linux, which would result in smaller, stripped down images. You can always modify this process to work on Alpine, but keep in mind both the build and release image must run Alpine.

You can see that while the Dockerfile.build installs Hex/rebar and gets a copy of our code with all its dependencies to ultimately build the release, the Dockerfile.release only uses the release built in the previous step and ultimately starts the application.

Next, modify your rel/config.exs file so that in environment :prod, it says set include_erts: false. ERTS is the Erlang run time which ordinarily Distillery would include in the release. However, since our Dockerfile.release is based on an Elixir image, it already contains the Erlang run time system so we set this to false to tell Distillery to use the ERTS from the release image.

Finally, in your shell execute eval $(minikube docker-env) to reuse the Docker daemon from Minikube (this saves you from having to push images into a registry for Minikube to pull from).

Now we’re ready to build our images. Start by running mix docker.build — you should see your entire application being compiled. Once that’s complete, run mix docker.release to build the release image. We can test the Docker image by running docker run -it -p 8000:8000 -e “HOST=example.com” -e “SECRET_KEY_BASE=highlysecretkey” -e “DB_USERNAME=postgres” -e “DB_PASSWORD=postgres” -e “DB_NAME=myapp_dev” -e “DB_HOSTNAME=10.0.2.2” — rm myapp:release foreground

Note that we’re using port 8000 now and the DB_HOSTNAME is set to 10.0.2.2. This works if you’re using the VirtualBox driver for Minikube since within VirtualBox 10.0.2.2 points to the host machine. If you’re using a different driver you may need to find another way to specify your database host (using your machine’s IP address on your network may work).

To view the app, get the IP address of the Minikube VM by running minikube ip and load the URL http://<your minikube ip>:8000 in your browser. You should see your app’s landing page and logs printed to console where you’re running the Docker image.

In Part 3 we’ll get this image deployed to our minikube cluster with replication, load balancing and configuration via Kubernetes secret management.

--

--

Rohan Relan
Polyscribe

Looking for some help bringing up ML or other new technologies within your organization? Shoot me a note at rohan@rohanrelan.com