A proposal for improving the workflow of `iex -S mix phx.server`

This is an idea for improving the workflow of iex -S mix phx.server

The Problem and The Solution in use

When running multiple Phoenix endpoints in an environment where you only have access to single port, we often:

  • use a proxy like main_proxy.
  • set server: false for all the endpoints.

But, after :server option is set to false, the endpoint watchers won’t start anymore - that’s the problem.

I have noticed :force_watchers option and the original PR.

Indeed, it works. But I think there is still room for improvement.

A Potential Problem

Let’s imagine a case first:

  • I have an umbrella project contains multiple Phoenix endpoints.
  • I configure main_proxy for these Phoenix endpoints.
  • I set server: false for these endpoints.
  • I set force_watchers: true for these endpoints.

Now, I want to enter the dev environment:

$ iex -S mix phx.server

Everything works fine.

But, sometimes, I want to enter the dev environment without starting watchers (for example, connect to an existing node which already starts watchers):

$ iex -S mix --sname bob

However, as it turns out, the watchers are still started. (After all, we used the :force_watchers option.)

In this case, :force_watchers can’t help, and it brings conflicts on the running watchers. (For example, you are running two esbuild instances for the same assets, that’s not what you want, generally.)

How to solve this problem?

An Idea

I have a roughly formed idea:

  • seperate the functionalities of endpoints into two:
    • web server
    • watchers
  • iex -S mix phx.server controls web server and watchers at the same time:
    • That is because the underlying implementation is Application.get_env(:phoenix, :serve_endpoints).
    • It concerns endpoints. And, as said above, endpoints have two functionalities - web server and watchers.
  • :server option controls the web server
  • :force_watchers option controls the watchers

A simple change on the code here:

# unchanged
defp server?(conf) when is_list(conf) do
  Keyword.get_lazy(conf, :server, fn ->
    Application.get_env(:phoenix, :serve_endpoints, false)
  end)
end

# changed for this idea
defp watcher?(conf) do
  Keyword.get_lazy(conf, :force_watchers, fn ->
    Application.get_env(:phoenix, :serve_endpoints, false)
  end)
end

defp watcher_children(_mod, conf) do
  if watcher?(conf) do
    Enum.map(conf[:watchers], &{Phoenix.Endpoint.Watcher, &1})
  else
    []
  end
end

Use Cases

Let’s take another look and see if this idea can cover all general use cases.

Enable web server and watchers

iex -S mix phx.server

Cases:

  • normal Phoenix projects in dev environment

Enable web server only

Set server: true.

Cases:

  • normal Phoenix projects in prod environment

Enable watchers only

  • iex -S mix phx.server
  • Set server: false

Cases:

  • umbrella projects contains multiple proxied phoenix endpoints in dev environment

Disable web server and watchers

  • do nothing

Cases:

  • umbrella projects contains multiple proxied phoenix endpoints in prod environment
  • just want to run iex -S mix

Last

Personally, I think this is a good proposal, which expands the applicability of iex -S mix phx.server. :wink:

That’s what I wanted to say. Thank you very much for reading. All feedback is welcome.

If you all like this proposal, I will create a pull request.

2 Likes

Can you elaborate about this scenario? If you’re running mix phx.server and watchers I take it that this is a dev environment, but in that case why is only one port available? If you want multiple phoenix apps on one port wouldn’t the traditional answer be a reverse proxy?

Take following app as an example:

lib/
  ├── demo
  ├── demo_user_web
  |   └── endpoint.ex
  ├── demo_user_api
  |   └── endpoint.ex
  └── demo_admin_web
      └── endpoint.ex

In this app, I want to separate the concerns for multiple types of clients, so I create three groups of modules for:

  • users using web
  • users using api
  • administrators using web

As you can see, every group has one endpoint. If you want these endpoints to work altogether, one way is to assign them different ports, and reverse proxy the requests by a external reverse proxy, like you said.

But, in some production environments, you can’t use multiple different ports,like the doc of main_proxy said:

This library is useful for Gigalixir, Render, Heroku or other deployments where only one web port is exposed.

In these production environments, if you still want to use multiple endpoints, you have to move the reverse proxy into the app itself. That’s why main_proxy exists.

Indeed, we only use mix phx.server and watchers in dev environment. And, in dev environment, we has lots of ports available.

However, IMO, the development environment should be as similar to the production environment as possible. If we only expose one port in the production environment, then the development environment should also expose only one port as much as possible. This can reduce a lot of troubles caused by environmental differences.

@chrismccord @josevalim I’m sorry to bother you all at this time.

I want to share this proposal with you, but it seems like there are a lot of barriers preventing me from doing so:

  • I tried sending an email to the phoenix-core@googlegroups.com mailing list, but my email seems to be undeliverable.
  • I wanted to use GitHub issues, but the CONTRIBUTING.md file for the Phoenix repository states that I shouldn’t use issues for a proposal.
  • Finally, I sent the proposal to this place, but it seems inactive and no one saw it.

So, please allow me to @ you all. If you think this is a good proposal, I’m willing to contribute this part of the code. If you think it’s not, then I’ll let it go.

I’m not convinced this is a feature or change we need. Thanks!

1 Like

Plus doing so could potentially be a breaking change. My suggestion would be for you to create additional mix tasks. You don’t have to use mix phx.server, you can roll your own with your own defaults.

2 Likes

Just to add. Generally speaking you should not be using mix in production. It is really meant for development. In production you should probably create and deploy a release. That can be started however you want.

3 Likes

embedded ERTS :pinched_fingers: