Profile picture Schedule a Meeting
c a n d l a n d . n e t

Building Releases with Distillery

Dusty Candland | | elixir, distillery

Seems like just running the app with mix is probably fine for now, but will make using a lot of the BEAM hard/impossible later. Also, the project has multiple apps, so making different builds for each seems like it might be a good idea.

This is part of a larger set of posts, see Deploying Elixir Umbrella Apps for an overview.

Setup & Building

One of the issues with Distillery is ENV variables. V2 has made some good progress in fixing this, but does require setting up configs a bit differently. I followed the recommendations in the docs which basically require a config.exs file with the runtime values injected with System.get_env.

Distillery Intro and Distillery Docker helped get everything going.

All the Distillery is setup in a rel/ directory.

rel
├── commands
│   ├── migrate.sh
│   └── seed.sh
├── config
│   └── config.exs
├── config.exs
└── plugins

We'll look at the commands directory later.

The config/config.exs is the runtime config file for all apps that have runtime configs.

use Mix.Config

port = String.to_integer(System.get_env("PORT") || "4000")

config :company, Company.Repo,
  url: System.get_env("DATABASE_URL"),
  database: "",
  ssl: true,
  pool_size: 15

config :company_admin, CompanyAdmin.Endpoint,
  http: [port: port],
  url: [host: System.get_env("HOSTNAME"), port: port],
  root: ".",
  secret_key_base: System.get_env("SECRET_KEY_BASE")

config :company_api, CompanyApi.Endpoint,
  http: [port: port],
  url: [host: System.get_env("HOSTNAME"), port: port],
  root: ".",
  secret_key_base: System.get_env("SECRET_KEY_BASE")

The use Mix.Config is important!

Distillery Config

The config.exs is for Distillery. It's pretty much the template from the docs. The Mix.Releases.Config.Providers.Elixir is what allows the use of the ENV variables.

I have two releases setup, one for the Admin and one for the Web. This does add some complications, but nothing too bad.

# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))

use Mix.Releases.Config,
  # This sets the default release built by `mix release`
  default_release: :default,
  # This sets the default environment used by `mix release`
  default_environment: Mix.env()

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/config/distillery.html

# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  # If you are running Phoenix, you should make sure that
  # server: true is set and the code reloader is disabled,
  # even in dev mode.
  # It is recommended that you build with MIX_ENV=prod and pass
  # the --env flag to Distillery explicitly if you want to use
  # dev mode.
  set(dev_mode: true)
  set(include_erts: false)
  set(cookie: :"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
end

environment :prod do
  set(include_erts: true)
  set(include_src: false)
  set(cookie: :"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")

  set(
    config_providers: [
      {Mix.Releases.Config.Providers.Elixir, ["${RELEASE_ROOT_DIR}/etc/config.exs"]}
    ]
  )

  set(
    overlays: [
      {:copy, "rel/config/config.exs", "etc/config.exs"}
    ]
  )
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default
release :admin do
  set(version: "0.0.1")

  set(
    applications: [
      :runtime_tools,
      company: :permanent,
      company_admin: :permanent
    ]
  )

  set(
    commands: [
      migrate: "rel/commands/migrate.sh",
      seed: "rel/commands/seed.sh"
    ]
  )
end

release :web do
  set(version: "0.0.1")

  set(
    applications: [
      :runtime_tools,
      fos: :permanent,
      company: :permanent,
      company_api: :permanent
    ]
  )

  set(
    commands: [
      migrate: "rel/commands/migrate.sh",
      seed: "rel/commands/seed.sh"
    ]
  )
end

Commands

Since I'm using Distillery, there isn't the ability to use mix tasks. That's what those commands files are about.

Running Migrations describes the setup for those. I put the tasks in the main Company application.

I used this pretty much as is.

  • Add the commands to the Distillery config.
  • Add the commands shell scripts.
  • Add the ReleaseTasks.ex module.

Builds

To build a release to run locally you can use mix.

mix release --profile admin:prod

The --profile is used to build for a specific release and environment.

You need to compile the code and assets first. To see that, checkout the Elixir Deploys with Make

Next Elixir and Docker

References

Webmentions

These are webmentions via the IndieWeb and webmention.io. Mention this post from your site: