Phoenix Walkthrough¶
It is recommended that you review the Deployment Guide, which covers Phoenix specific configurations that need to be provided in order for your application to work within a release. The guide below will walk you through a working example of using Distillery with a Phoenix 1.3 application to create a release.
The goal of this guide is to walk you through the basics of deploying a simple Phoenix application with Distillery. We are going to build a simple Phoenix, 1.3, application from scratch and take it through 4 releases. 1 main release, and 3 hot upgrade releases.
NOTE At this time this guide does not cover Ecto‘s use in releases. This will be added at a later time.
Create Phoenix App with Distillery¶
First off we will create a new Phoenix app (without Ecto) and then change into the newly created directory with the following commands:
1 2 | $ mix phx.new --no-ecto phoenix_distillery
$ cd phoenix_distillery
|
Next we will add Distillery to the deps function of our mix.exs
file.
file: mix.exs
1 2 3 4 5 6 | defp deps do [ ..., {:distillery, "~> 2.0"}, ..., ] end |
Distillery Configuration¶
First let’s modify the Phoenix config/prod.exs
file. Change this section of text:
1 2 3 4 | config :phoenix_distillery, PhoenixDistilleryWeb.Endpoint, http: [:inet6, port: System.get_env("PORT") || 4000], url: [host: "example.com", port: 80], cache_static_manifest: "priv/static/cache_manifest.json" |
to the following:
1 2 3 4 5 6 7 | config :phoenix_distillery, PhoenixDistilleryWeb.Endpoint, http: [:inet6, port: System.get_env("PORT") || 4000], url: [host: "localhost", port: System.get_env("PORT")], # This is critical for ensuring web-sockets properly authorize. cache_static_manifest: "priv/static/cache_manifest.json", server: true, root: ".", version: Application.spec(:phoenix_distillery, :vsn) |
Let’s discuss these options.
server
configures the endpoint to boot the Cowboy application http endpoint on start.root
configures the application root for serving static filesversion
ensures that the asset cache will be busted on versioned application upgrades (more on this later)
NOTE We are telling our release to use an ENV variable (PORT) by providing the tuple {:system, "PORT"}
to the port option. Your release will not start properly if the PORT
variable is not available to it on the production machine/in the production environment.
Building a Release¶
Now we have the Phoenix app created with Distillery and our configuration all ready for building a release. Execute the following commands:
1 2 3 4 | $ mix deps.get --only prod $ MIX_ENV=prod mix compile $ npm run deploy --prefix assets $ mix phx.digest |
The above commands are not unique to Distillery, they are required by Phoenix to build a production release and get all the static files in order.
Distillery Release¶
The following initializes Distillery for the project:
1 | $ mix distillery.init |
The above command will create the file rel/config.exs
in addition to an empty directory rel/plugins/
. Please refer to the Distillery walkthrough for a detailed look at the configuration options available.
To build the release the following command is executed:
1 | $ MIX_ENV=prod mix distillery.release |
To run your release, execute the following command:
1 | $ PORT=4001 _build/prod/rel/phoenix_distillery/bin/phoenix_distillery foreground |
You should be able to go to localhost:4001 and load the default Phoenix application.
NOTE The above commands can be combined into one quick command as
1 | $ npm run deploy --prefix assets && MIX_ENV=prod mix do phx.digest, release --env=prod |
NOTE: If you run mix distillery.release
with MIX_ENV=dev
(the default), then you must also ensure that you set code_reloader: false
in your configuration. If you do not, you’ll get a failure at runtime about being unable to start Phoenix.CodeReloader.Server
because it depends on Mix, which is not intended to be packaged in releases. As you won’t be doing code reloading in a release (at least not with the same mechanism), you must disable this.
Version 0.0.1¶
If you followed the above you will have generated a working release. A few notes on some of the above commands we used:
npm run deploy --prefix assets
builds your assets in production mode. More detail can be found in the Phoenix Static Asset GuideMIX_ENV=prod mix phx.digest
To compress and tag your assets for proper caching. More detail can be found in the Phoenix Mix Task GuideMIX_ENV=prod mix distillery.release --env=prod
To actually generate a release for a production environment
You might wonder “why all the hassle to build a release?” A Phoenix project in dev
mode is
supposed to be interactive with features such as live code reload and automatic webpack
asset
recompilation and extra logging. While great for development, it comes at a performance cost
and you would not want to run a production Phoenix application in dev mode.
Take that Release Anywhere¶
Create a new directory somewhere on your machine called local_deploy
and copy the release tarball you just created into it. Your command should look something like this:
cp _build/prod/rel/phoenix_distillery/releases/0.0.1/phoenix_distillery.tar.gz local_deploy/
Now cd
into local_deploy
and extract the tarball with:
tar xvf phoenix_distillery.tar.gz
Your application is ready to be started up with the following command:
PORT=4001 ./bin/phoenix_distillery start
Notice that we are explicitly setting the PORT
environment variable in this shell session.
If all has gone well, you should be able to open localhost:4001
in your browser and see the default Phoenix landing page in all its glory.
Version 0.0.2¶
For version 0.0.2, we are going to remove the Phoenix logo from the landing page and upgrade our application.
file: mix.exs
1 2 3 4 5 6 7 | def project do [ ... version: "0.0.2", # Bumping our version here ... ] end |
Remove the logo class from our application.css
file: assets/css/phoenix.css
1 2 3 4 5 6 7 8 9 | // We remove the following block of css .logo { width: 519px; height: 71px; display: inline-block; margin-bottom: 1em; background-image: url("/images/phoenix.png"); background-size: 519px 71px; } |
Remove the following line from our application layout.
file: lib/phoenix_distillery_web/templates/layout/app.html.eex
1 | <span class="logo"></span> |
Next we build an upgrade release with the following command:
npm run deploy --prefix assets && MIX_ENV=prod mix do phx.digest, release --env=prod --upgrade
This is the same command as in version 0.0.1 with the exception of --upgrade
. The upgrade flag tells Distillery to build an appup for every application included in the release. These files are then used to generate a relup which details how an upgrade (or downgrade) is applied to a running application instance.
If all went as planned, you now have a 0.0.2 release in _build/prod/rel/phoenix_distillery/releases/
. In order to deploy this tarball, you need to create a 0.0.2
directory in local_deploy/releases
and copy the 0.0.2 tarball into this directory. Your copy command should look something like this:
cp _build/prod/rel/phoenix_distillery/releases/0.0.2/phoenix_distillery.tar.gz local_deploy/releases/0.0.2/
Now all you have to do is upgrade your running instance by executing ./local_deploy/bin/phoenix_distillery upgrade 0.0.2
. If you go reload your browser you will see that the logo has now disappeared!
Version 0.0.3¶
For version 0.0.3, we are going to do something more fancy. We are going to setup a web socket which emits even numbers. The client will connect on page load and display these numbers in the console.
First we need to create a channel on the server responsible for emitting even numbers:
new file: lib/phoenix_distillery_web/channels/heartbeat_channel.ex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | defmodule PhoenixDistilleryWeb.HeartbeatChannel do use Phoenix.Channel def join("heartbeat:listen", _message, socket) do send(self, :after_join) # send a message to kick off our loop. We do this in order to take as little time as possible on the server before we send the client socket an :ok message signaling a good connection. {:ok, socket} end def handle_info(:after_join, socket) do send(self, {:beat, 0}) # initialize our heartbeat broadcast with an initial state of 0 {:noreply, socket} end def handle_info({:beat, i}, socket) do broadcast!(socket, "ping", %{body: i}) # broadcast the current heartbeat number to all connected clients Process.send_after(self, {:beat, i + 2}, 2000) # send a message to the current server with a new (even numbered) state after 2 seconds {:noreply, socket} end end |
Now we need to modify the default user_socket
to reference the HeartbeatChannel
we just created. The only defined channel should be heartbeat:*
file: lib/phoenix_distillery_web/channels/user_socket.ex
1 2 3 4 5 6 | defmodule PhoenixDistilleryWeb.UserSocket do use Phoenix.Socket channel "heartbeat:*", PhoenixDistilleryWeb.HeartbeatChannel ... end |
Now we need to enable the default socket and tell it how to handle our heartbeat message:
file: assets/js/app.js
1 2 | import "phoenix_html" import socket from "./socket" |
file: assets/js/socket.js
1 2 3 4 5 6 7 8 | ... let socket = new Socket("/socket", {params: {token: window.userToken}}) socket.connect() let channel = socket.channel("heartbeat:listen", {}) channel.join() channel.on("ping", payload => { console.log(payload.body) }) ... |
Finally we need to bump our version to 0.0.3 in mix.exs
file: mix.exs
1 2 3 4 5 6 7 | def project do [ ... version: "0.0.3", # Bumping our version here ... ] end |
With all that complete, we are now ready to generate the 0.0.3 release just as we did with 0.0.2. So we will generate a release, copy the 0.0.3 tarball into a new release directory under local_deploy
, and upgrade the application.
npm run deploy --prefix assets && MIX_ENV=prod mix do phx.digest, release --env=prod --upgrade
mkdir local_deploy/releases/0.0.3
cp _build/prod/rel/phoenix_distillery/releases/0.0.3/phoenix_distillery.tar.gz local_deploy/releases/0.0.3/
./local_deploy/bin/phoenix_distillery upgrade 0.0.3
If you go reload your browser and open your console you will be greeted by a series of increasing numbers from 0!
Version 0.0.4¶
For version 0.0.4, we are going to modify the web socket we created in version 0.0.3 to increment the heartbeat state by 1 instead of two. This is a great demonstration of how web sockets remain open during a hot upgrade.
First bump your version to 0.0.4 in mix.exs
file: mix.exs
1 2 3 4 5 6 7 | def project do [ ... version: "0.0.4", # Bumping our version here ... ] end |
Next update the HeartbeatChannel
to emit numbers incremented by one:
new file: lib/phoenix_distillery_web/channels/heartbeat_channel.ex
1 2 3 4 5 6 7 8 9 10 11 | defmodule PhoenixDistilleryWeb.HeartbeatChannel do ... def handle_info({:beat, i}, socket) do broadcast!(socket, "ping", %{body: i}) Process.send_after(self, {:beat, i + 1}, 2000) {:noreply, socket} end ... end |
With this complete, we are now ready to generate the 0.0.4 release just as we did with 0.0.3. Generate a release, copy the 0.0.4 tarball into a new release directory under local_deploy
, and upgrade the application.
npm run deploy --prefix assets && MIX_ENV=prod mix do phx.digest, release --env=prod --upgrade
mkdir local_deploy/releases/0.0.4
cp _build/prod/rel/phoenix_distillery/releases/0.0.4/phoenix_distillery.tar.gz local_deploy/releases/0.0.4/
./local_deploy/bin/phoenix_distillery upgrade 0.0.4
DO NOT RELOAD YOUR BROWSER Simply stare at your console and wait. In no time at all you will see numbers start incrementing by 1 rather than 2. You will conclude that hot upgrades are the coolest thing since sliced bread.
Conclusion¶
Hopefully this has been a good introduction to using Distillery with Phoenix. There is more ground to cover (especially with Ecto) which will be added as the project progresses.
Happy Hacking