What is the difference between using {:system, "PORT"} and System.get_env("PORT") in deployment?

I want to update this old discussion and thread to say we have come up with a mechanism that no longer requires {:system, ...} and it is slowly being deprecated from Phoenix, Plug and Ecto.

James Fish pointed out the best place to do configuration is inside init/1. Doing it before calling start_link is an issue because then it won’t work with hot code reloading. Therefore with Phoenix v1.3, we are going to push dynamic configuration, such as the ones loaded from the environment, to the init functions or on_init hooks.

For example, here is how config/prod.exs looks like:

config :my_app, MyApp.Endpoint,
  http: [:inet6, {:system, "PORT"}]

And here is how it looks on Phoenix v1.3:

# For production, we often load configuration from external
# sources, such as your system environment. For this reason,
# you won't find the :http configuration below, but set inside
# MyApp.Endpoint.load_from_system_env/1 dynamically.
# Any dynamic configuration should be moved to such function.
config :my_app, MyApp.Endpoint,
  on_init: {Demo.Web.Endpoint, :load_from_system_env, []},

Where load_from_system_env is defined in the endpoint as:

@doc """
Dynamically loads configuration from the system environment
on startup.
"""
def load_from_system_env(opts) do
  port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
  {:ok, Keyword.put(opts, :http, [:inet6, port: port])}
end

This should answer all questions folks have with dynamic configuration, it doesn’t matter if it comes from System.get_env, the filesystem, etc.

Unfortunately we could not use init/1 for Phoenix, because the endpoint is already a Plug that requires an init function, and because the Phoenix Endpoint is an umbrella of multiple supervisors but for Ecto it is just the init function:

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app

  @doc """
  Dynamically loads the repository url from the
  DATABASE_URL environment variable.
  """
  def init(_, opts) do
    {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
  end
end

Which will hopefully push the community towards the proper path, as init is typically the correct place for such work.

8 Likes