View Source Jellyfish

Simplifying Hot-Upgrades for Elixir Applications

Hex.pm Version

Jellyfish is a library that generates appup files, enabling hot-upgrades for Elixir applications without downtime.

What are Appup Files?

Appup files describe how to upgrade and downgrade an application from one version to another. They contain:

  • The application name
  • Instructions to upgrade to a newer version
  • Instructions to downgrade to the original version

For detailed information about the appup format, see the Erlang appup manual.

Important Upgrade Ordering

When upgrading processes, order matters:

  • Processes are suspended during upgrades
  • In-flight requests are handled by the old version until upgrade completes
  • Upgrade dependencies first, then dependents (e.g., if proc_a depends on proc_b, upgrade proc_b first)
  • Jellyfish automatically performs topological sorting when generating appups

References:

Installation

Add jellyfish to your dependencies in mix.exs:

def deps do
  [
    {:jellyfish, "~> 0.2.0"}
  ]
end

Basic Configuration

Add the following lines in the mix.exs project:

  def project do
    [
      ...
      releases: [
        your_app_name: [
          steps: [:assemble, &Jellyfish.generate/1, :tar]
        ]
      ],
      ...
    ]
  end

Once the mix release is called, if a previous version is found, the appup file will be automatically generated and included in the release package for executing hot-upgrades.

Hot-Upgrading Dependencies (Optional)

Jellyfish generates appup files for both your application code and its dependencies. This allows you to upgrade third-party libraries at runtime alongside your own code changes.

[!WARNING] Not all code changes are safe for hot-upgrades. Before performing a hot-upgrade, Check if the dependency supports hot-upgrades between versions, review the changelog for structural changes (e.g., process state modifications, API changes), check stateful processes like GenServers, Agents, etc that may require special handling.

Appup file

If for any reason you need to change the order of the modules or add new commands in the appup file, you have 2 options:

  1. Use the EDIT_APPUP environment variable to indicate to Jellifish that you want to edit the file before the release:

    EDIT_APPUP=true MIX_ENV=prod mix release
    
  2. Untar the release, do the changes in the appup files and tar it again.

Relup file

The library focuses on generating appup files and includes them in the mix release package. It doesn't create relup files directly. The relup file is typically created during a hot upgrade with the DeployEx application.

Elixir Umbrella applications

The next sections describe how to set up and use Jellyfish with Elixir umbrella applications for hot code upgrades.

Versioning

Elixir umbrella applications contain multiple apps, each with its own mix.exs file and version. However, Jellyfish expects a single consistent version for the entire umbrella application. To ensure version consistency across all apps, we recommend two approaches:"

Versioning using a shared mix config

Create a new file mix/shared.exs at the root of your umbrella project and add the following code:

defmodule Mix.Shared do
  def version, do: "0.1.0"
end

Add the load of this file in the root Mix File Setup:

Code.require_file("mix/shared.exs")

defmodule Myumbrella.MixProject do
  use Mix.Project

  def project do
    [
      apps_path: "apps",
      version: Mix.Shared.version(),
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      releases: [
        myumbrella: [
          applications: [
            app_1: :permanent,
            app_2: :permanent,
            app_web: :permanent
          ],
          steps: [:assemble, &Jellyfish.generate/1, :tar]
        ]
      ]
      ...
    ]
  end

  defp deps do
    [
      {:jellyfish, "~> 0.2.0"}
    ]
  end
end

Each application within the umbrella should reference the same version file:

Child App Mix File Setup

Jellyfish dependency is not required for the child apps

defmodule App1.MixProject do
  use Mix.Project

  def project do
    [
      app: :app_1,
      version: Mix.Shared.version(),
      # Other configuration...
    ]
  end
  # Rest of the mix file...
end

Versioning using a text file

Create a new file version.txt at the root of your umbrella project.

Root Mix File Setup:

defmodule Myumbrella.MixProject do
  use Mix.Project

  @version File.read!("version.txt")

  def project do
    [
      apps_path: "apps",
      version: @version,
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      releases: [
        myumbrella: [
          applications: [
            app_1: :permanent,
            app_2: :permanent,
            app_web: :permanent
          ],
          steps: [:assemble, &Jellyfish.generate/1, :tar]
        ]
      ]
    ]
  end

  defp deps do
    [
      {:jellyfish, "~> 0.2.0"}
    ]
  end
end

Each application within the umbrella should reference the same version file:

Child App Mix File Setup

Jellyfish dependency is not required for the child apps

defmodule App1.MixProject do
  use Mix.Project

  @version File.read!("../../version.txt")

  def project do
    [
      app: :app_1,
      version: @version,
      # Other configuration...
    ]
  end
  # Rest of the mix file...
end

Generating Appup files

When building releases for hot code upgrades in umbrella applications, modifying the version file does not trigger the compiler to detect changes in mix.exs across all apps. In this scenario, all apps need to be recompiled to make the new version available to the compiler's tasks, which would normally require forcing compilation.

  1. Build the initial release:

    # Release the version 0.1.0
    MIX_ENV=prod mix assets.deploy
    MIX_ENV=prod mix release
    
  2. Update the version (e.g., from 0.1.0 to 0.1.1) in version.txt

  3. Build the new release with forced compilation:

    MIX_ENV=prod mix assets.deploy
    MIX_ENV=prod mix compile --force
    MIX_ENV=prod mix release
    

Why --force is needed: Modifying the version file alone doesn't trigger the compiler to detect changes across all umbrella apps. Forcing compilation ensures the new version is available to all compiler tasks.

Examples

Explore these resources for practical examples of using Jellyfish with Elixir applications:

  • Deployex - Elixir application showcasing Jellyfish's capabilities in deployment with hot-upgrades.
  • Calori - Elixir application using Jellyfish and being able to hot upgrade via DeployEx
  • Myumbrella - Elixir umbrella application configured for using Jellyfish.

Getting involved

🗨️ Contact us: Feel free to contact me on Linkedin.

Copyright (c) 2024, Thiago Esteves.

DeployEx source code is licensed under the MIT License.

References

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/jellyfish.