Jeremy Bellows - How to Create a Minimal Phoenix Elixir Server Docker Image That is Less Than 100 MB

How to Create a Minimal Phoenix Elixir Server Docker Image That is Less Than 100 MB

-


Creating small deployment packages is crucial for continuous deployments. It is inefficient (and slightly infuriating) to wait around for a deployment process to finish. By breaking the deployment package down into its simplest form, the overall size can be reduced significantly.

Elixir compiles into BEAM byte code which is executable on the Erlang Abstract Machine. All the perks of erlang optimization and processes are available for elixir application architectures.

By using the Erlang Abstract Machine and a minimal linux alpine image, it is possible to create a docker image that will be small enough for continuous deployments.

Creating a minimal phoenix elixir server docker image is simple! The open source elixir community has created excellent tooling that really helps ease in beginners into an advanced language ecosystem.

This guide assumes that the development machine in use has the following installed

  • Erlang
  • Elixir
  • Docker
  • Git

Prerequisite - Getting Started Phoenix

Hex

To install phoenix, we’ll need to install hex package manager.

Execute the following command to install hex package manager

mix local.hex

Phoenix

Phoenix is our web framework for elixir. Installing the package through hex will allow usage of phoenix tools for generating projects and other code purposes.

Run the following command to install phoenix web framework

mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

Starting a New Project

The command to create a phoenix server is

mix phoenix.new nameOfApp

This creates a project with all the necessary files to run a server.
By default, the dependency ecto is installed. Ecto is an abstract data layer library for communicating with databases; allowing data modules to be populated with content.

To read more about ecto click here for the phoenix documentation

If database communication is not required, then the flag --no-ecto will create a server without ecto.

mix phoenix.new nameOfApp --no-ecto

There should be output similar to this

jeremy@jeremy-UX305UA ~/temp $ mix phoenix.new testapp --no-ecto
* creating testapp/config/config.exs
* creating testapp/config/dev.exs
* creating testapp/config/prod.exs
* creating testapp/config/prod.secret.exs
* creating testapp/config/test.exs
* creating testapp/lib/testapp.ex
* creating testapp/lib/testapp/endpoint.ex
* creating testapp/test/views/error_view_test.exs
* creating testapp/test/support/conn_case.ex
* creating testapp/test/support/channel_case.ex
* creating testapp/test/test_helper.exs
* creating testapp/web/channels/user_socket.ex
* creating testapp/web/router.ex
* creating testapp/web/views/error_view.ex
* creating testapp/web/web.ex
* creating testapp/mix.exs
* creating testapp/README.md * creating testapp/web/gettext.ex
* creating testapp/priv/gettext/errors.pot
* creating testapp/priv/gettext/en/LC_MESSAGES/errors.po
* creating testapp/web/views/error_helpers.ex
* creating testapp/.gitignore
* creating testapp/brunch-config.js
* creating testapp/package.json
* creating testapp/web/static/css/app.css
* creating testapp/web/static/css/phoenix.css
* creating testapp/web/static/js/app.js
* creating testapp/web/static/js/socket.js
* creating testapp/web/static/assets/robots.txt
* creating testapp/web/static/assets/images/phoenix.png
* creating testapp/web/static/assets/favicon.ico
* creating testapp/test/controllers/page_controller_test.exs
* creating testapp/test/views/layout_view_test.exs
* creating testapp/test/views/page_view_test.exs
* creating testapp/web/controllers/page_controller.ex
* creating testapp/web/templates/layout/app.html.eex
* creating testapp/web/templates/page/index.html.eex
* creating testapp/web/views/layout_view.ex
* creating testapp/web/views/page_view.ex

Fetch and install dependencies? [Yn] Y
* running mix deps.get
* running npm install && node node_modules/brunch/bin/brunch build

We are all set! Run your Phoenix application:

$ cd testapp
$ mix phoenix.server

You can also run your app inside IEx (Interactive Elixir) as:

$ iex -S mix phoenix.server

Adding Dependencies

Two dependencies are needed to make this work.

Installing Distillery

To install distillery just add a reference in the mix.exs file.

In my case, the file was located at
~/JeremyBellows/temp/testapp/mix.exs

in the list of dependencies, insert the line

    {:distillery, "~> 0.10"},

The default code looks like

  defp deps do
    [{:phoenix, "~> 1.2.0"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"}]
  end

The new code should look like

  defp deps do
    [{:phoenix, "~> 1.2.0"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:distillery, "~> 0.9"}
    ]
  end

After adding distillery to the dependency list, the new dependecy will need to be downloaded using the command in the root of the application folder. In my case, the path was ~/JeremyBellows/temp/testapp.

mix deps.get

Installing Edib

Edib is installed as a mix task. This means that the application won’t have a reference to the dependency. Instead, the task is installed locally and available globally.

Navigate to the root of the application folder.

Execute the following command to install edib

mix archive.install https://git.io/edib-0.9.0.ez

The version may change from when this article was posted. Validate the version on the mix-edib repo on github.

Configuring the Release

Distillery has quite a bit of configurable options. There is official distillery documentation that discusses all of the available options.

Initial Release Configuration

Before a release can be packaged, the release configuration file needs to be generated. To do so run the following command in the root folder of your application: mix release.init

This will create a release config file that can be modified based on circumstances. The initial configuration file is adequate for setting up the build process.

Enabling Server Mode for Production

When the application starts, the application phoenix endpoint reads the configuration server and initiates the server. If this is not set then no pages will be served.

The production release package is not configured to start the server by default. To change that, navigate to the root folder of the application and edit the file config/production.exs

Edit the code block below

config :testapp, Testapp.Endpoint,
  http: [port: {:system, "PORT"}],
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/manifest.json"

Add the property server: true

The end code should look like

config :testapp, Testapp.Endpoint,
  http: [port: {:system, "PORT"}],
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/manifest.json",
  server: true

Phoenix Prod Config Server is set to true

Adding the property server: true will automatically start the server when the application is executed

Read more about this by clicking here

Creating the docker image

Sanity Check

Investigating the current status quo of a project before creating a release is usually a wise idea.

Get Dependencies

In the root folder of the application execute the following command.

mix deps.get

Run Server

In the root folder of the application execute the following command.

mix phoenix.server

In your browser, navigate to the web address
localhost:4000

If all went well, you should see a Phoenix webpage that looks like
Default Phoenix Website

To exit the server, press ctrl+c twice

Generate a Release

The server executes successfully in dev mode, but does it work in prod mode? Testing this requires having distillery package a release.

To package a release, execute the command mix release --env=prod

This will compile files into a release executable

Executing the release executable

To run the release executable, execute the following command

 rel/jeremybellows/bin/jeremybellows console

If all went well, you should see a Phoenix webpage that looks like
Default Phoenix Website

Edib magic - Using edib to create the docker image

If the application executed successfully, then the docker image can be created.

To create the docker image, execute the following command in the root folder of the application.

mix edib

Once the docker image creation is finished, execute the following command to start the server

docker run --rm -e "PORT=4000" -p 4000:4000 local/testapp:0.0.1

Here’s what the build log looks like

mix edib Console Output

Summary

To see the size of the docker image that was just created, use the command docker images. The testapp project used in this post ended up with a size of 24.14MB!

Using small contained deployment images opens up endless strategies. For example, small deployment sizes enable the use of an intermediate automated regression testing build promotion process.

If we mapped out the process it would look like

Create Docker Image
-> Deploy Docker Image to Testing Environment
-> Run Regression Tests
-> Pass?
  | Yes -> Promote docker image to production
  | No -> Reject Build

Mitigating the size costs of promoting docker images to environments saves a tremendous amount of accumulative resources. That means more time and money for your business. Alleviating the huge resource demand on deployment and testing processes frees teams to pursue other strategic objectives.