Is it possible to start a Phoenix Server manually?

Okay, here is what I ended up with:

Instead of adding MyApp.Endpoint directly to my supervision tree I am adding this LsppWeb.PhoenixPortSupervisor that uses DynamicSupervisor to start up my Phoenix Endpoint, incrementing the port if it fails to startup initially.

defmodule LsppWeb.PhoenixPortSupervisor do
  @moduledoc """
  Starts up Phoenix, but controls the port with application configuration,
  increments the port until Phoenix starts up successfully.
  """
  use GenServer

  def start_link(_, name \\ __MODULE__) do
    GenServer.start_link(__MODULE__, nil, name: name)
  end

  def init(_) do
    initial_port = initial_port()

    start_phoenix(initial_port, initial_port)
  end

  defp start_phoenix(initial_port, port) when port - initial_port < 15 do
    set_port(port)

    DynamicSupervisor.start_child(LsppWeb.DynamicSupervisor, LsppWebWeb.Endpoint)
    |> interpret_results()
    |> case do
      {:ok, _} -> {:ok, []}
      {:error, :eaddrinuse} -> start_phoenix(initial_port, port + 1)
    end
  end

  defp start_phoenix(_, _) do
    {:error, :ports_exhausted}
  end

  def get_port, do: Application.get_env(:lspp_web, :phoenix_port)

  defp set_port(port) do
    Application.put_env(:lspp_web, :phoenix_port, port)
  end

  defp interpret_results({:ok, pid}), do: {:ok, pid}

  defp interpret_results({:error, error}) do
    case error do
      {:shutdown,
       {:failed_to_start_child, {:ranch_listener_sup, LsppWebWeb.Endpoint.HTTP},
        {:shutdown,
         {:failed_to_start_child, :ranch_acceptors_sup,
          {:listen_error, LsppWebWeb.Endpoint.HTTP, :eaddrinuse}}}}} ->
        {:error, :eaddrinuse}
    end
  end

  defp initial_port do
    cond do
      port = System.get_env("PORT") ->
        String.to_integer(port)

      config = Application.get_env(:lspp_web, LsppWebWeb.Endpoint) ->
        get_in(config, [:http, :port])

      true ->
        raise "Port not set"
    end
  end
end

Then in LsppWeb.Endpoint I define an init/2 callback:

  def init(supervisor, config) do
    port = LsppWeb.PhoenixPortSupervisor.get_port()
    config = put_in(config[:http], [:inet6, {:port, port}])

    {:ok, config}
  end

Any feedback or critique is welcome. The ugliest part of the code right now is how I shuttle the new port through the application environment. I suppose since LsppWeb.PhoenixPortSupervisor is a GenServer maybe that could be handled with a GenServer.call, but then I would need another GenServer/process since currently LsppWeb.PhoenixPortSupervisor doesn’t finish it’s startup until my LsppWeb.Endpoint has finished.