Skip to content

Instantly share code, notes, and snippets.

@imranismail
Last active August 7, 2018 16:59
Show Gist options
  • Save imranismail/83acd1b5f19959f7bca740cf36084cd3 to your computer and use it in GitHub Desktop.
Save imranismail/83acd1b5f19959f7bca740cf36084cd3 to your computer and use it in GitHub Desktop.
Continuously Delivered Elixir Apps

Deployment

TODO(@imranismail)

Release

Setup

Generate a Phoenix project like so:

mix phx.new - no-ecto cacaw
mix deps.get
mix phx.server

To kickoff a release lets start by adding Distillery to deps and run mix deps.get

defp deps do
  [
    {:phoenix, github: "phoenixframework/phoenix", override: true},
    {:phoenix_pubsub, "~> 1.0"},
    {:phoenix_html, "~> 2.11"},
    {:phoenix_live_reload, "~> 1.0", only: :dev},
    {:gettext, "~> 0.11"},
    {:jason, "~> 1.0"},
+   {:cowboy, "~> 1.0"},
+   {:distillery, "~> 2.0-rc"}
  ]
end

Let's initialize our release config by running mix release.init

$ mix release.init
Generated cacaw app

An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix release` to build the release

Cutting a Release

With just that we can already cut our first release.

$ MIX_ENV=prod mix release
==> Assembling release..
==> Building release cacaw:0.1.0 using environment prod
==> Including ERTS 10.0.4 from /usr/local/Cellar/erlang/21.0.4/lib/erlang/erts-10.0.4
==> Packaging release..
Release succesfully built!
To start the release you have built, you can use one of the following tasks:

    # start a shell, like 'iex -S mix'
    > _build/prod/rel/cacaw/bin/cacaw console

    # start in the foreground, like 'mix run --no-halt'
    > _build/prod/rel/cacaw/bin/cacaw foreground

    # start in the background, must be stopped with the 'stop' command
    > _build/prod/rel/cacaw/bin/cacaw start

If you started a release elsewhere, and wish to connect to it:

    # connects a local shell to the running node
    > _build/prod/rel/cacaw/bin/cacaw remote_console

    # connects directly to the running node's console
    > _build/prod/rel/cacaw/bin/cacaw attach

For a complete listing of commands and their use:

    > _build/prod/rel/cacaw/bin/cacaw help

We can test our release by running the first command in the output and try visiting http://localhost:4000

You'll immediately notice that it can't be reached. The reason for this is that Phoenix apps require additional configurations for OTP releases.

We could configure this in our app config that gets compiled into the release however for demonstration purpose I'm going to show you how the new Distillery makes it easy to include runtime configurations to your Elixir releases!

Runtime Configuration

In Distillery 2.0 there's a new construct called Config Providers which allows us to have run time configs that are evaluated during the boot process.

These providers can come in any format. By default Distillery ships with the Elixir provider which is essentially a Mix-like config provider.

You can set them up by editing the release config in rel/config.exs

release :cacaw do
  set version: current_version(:cacaw)
  set applications: [
  :runtime_tools
  ]
+ set config_providers: [
+   {Mix.Releases.Config.Providers.Elixir, ["${RELEASE_ROOT_DIR}/config/config.exs"]}
+ ]
+ set overlays: [
+   {:template, "priv/templates/config.exs.eex", "config/config.exs"}
+ ]
end

Here we are telling Distillery that we'll have an Elixir config provider available to be used.

Then we use the overlays to generate config file from a template into the release.

Let's create the template file at priv/templates/config.exs.eex with the following content

use Mix.Config

config :cacaw, CacawWeb.Endpoint,
  server: true,
  root: ".",
  version: Application.spec(:cacaw, :vsn)

Cut a new release, start the app with the first command and visit http://localhost:4000.

It works but now we are faced with some problems with our assets. Apparently the assets are requested from a wrong host

wrong host

Let's rectify that by changing our runtime config at _build/prod/rel/config/config.exs

use Mix.Config

config :cacaw, CacawWeb.Endpoint,
  server: true,
  root: ".",
+ version: Application.spec(:cacaw, :vsn),
+ url: [host: System.get_env("CACAW_HOSTNAME") || "localhost", port: System.get_env("CACAW_PORT") || "4000"]

Restart the console command (the first command from the release output) and visit http://localhost:4000 and voila you've just changed a config without re-releasing a new build.

We are still having issues with the assets but we can rest assured that it is requesting from the correct host.

correct host

With this you can decouple non-deterministic configs from your application code using constructs like System.get_env/1 or delivering your artifact to the ops team to configure it to their content.

Next, lets look at including our frontend assets to the release to fix those 404 issue

Including Assets

When you ran your first release, it doesn't include any frontend assets. To fix this we can monkey patch our mix release command to build our frontend assets before cutting the release.

Edit your mix.exs like so:

defmodule Cacaw.MixProject do
  use Mix.Project

  def project do
    [
      app: :cacaw,
      version: "0.1.0",
      elixir: "~> 1.5",
      elixirc_paths: elixirc_paths(Mix.env),
      compilers: [:phoenix, :gettext] ++Mix.compilers,
      start_permanent: Mix.env == :prod,
+     deps: deps(),
+     aliases: aliases()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {Cacaw.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_),     do: ["lib"]
+
+ defp aliases do
+   [
+     "phx.digest": [&build_frontend/1, "phx.digest"],
+     release: ["phx.digest", "release"]
+   ]
+ end
+
+ defp build_frontend(_) do
+   System.cmd("yarn", ~w[run deploy], cd: "assets", into: IO.stream(:stdio, :line))
+ end

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, github: "phoenixframework/phoenix", override: true},
      {:phoenix_pubsub, "~> 1.0"},
      {:phoenix_html, "~> 2.11"},
      {:phoenix_live_reload, "~> 1.0", only: :dev},
      {:gettext, "~> 0.11"},
      {:jason, "~> 1.0"},
      {:cowboy, "~> 1.0"},
      {:distillery, "~> 2.0-rc"}
    ]
  end
end

With those alterations our mix release command will do three things:

  1. Builds our frontend assets which will populate the priv directory
  2. Versions our frontend assets
  3. Builds our release with them assets included
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment