feat(deployer): show the URLs each connected application is serving on - #293
Merged
Merged
Conversation
thiagoesteves
added a commit
that referenced
this pull request
Aug 13, 2026
#293) The dashboard reported the configured port of an application but not the address it answers on. Phoenix registers the supervisor of every endpoint under the endpoint module name itself, so the registered names of a node list its endpoints. Each candidate is then asked for its own url, which resolves scheme, host and port from the configuration the application is really running with and at the same time confirms the module is an endpoint. A node serving urls keeps them until it stops answering, so a redeployment that changes the address re-discovers instead of reporting the previous release. A node serving none is asked again every 30 seconds, an application joins the distribution before its endpoint is up. Risk assessment Impact: the DeployEx card and every connected application card gain a URLs block with links opening in a new tab. Applications serving no Phoenix endpoint, and those not connected over OTP distribution, render exactly as before. Blast radius: Deployer.Status struct, Deployer.Status.Application, the new Deployer.Status.Endpoints, and the two dashboard cards plus a shared component. The deployment engine, hot upgrades, the watchdog, certificates and notifications are untouched. Regression risk: low. Discovery is behind the existing Foundation.Rpc adapter with a one second timeout, every failure path returns an empty list, and the result is cached so the one second status sweep does not add per-tick rpc load. Hot upgrade note: this adds a field to %Deployer.Status{}, which the status GenServer holds in its state. The list is rebuilt every second so it self heals, but a LiveView rendering during the swap can hit a KeyError on the old struct. Rollback: plain commit revert. No data or config migration.
thiagoesteves
force-pushed
the
thiagoesteves/extract-application-urls
branch
from
August 13, 2026 12:52
c9393a2 to
bb7b77d
Compare
#293) The dashboard reported the configured port of an application but not the address it answers on. Phoenix registers the supervisor of every endpoint under the endpoint module name itself, so the registered names of a node list its endpoints. Each candidate is then asked for its own url, which resolves scheme, host and port from the configuration the application is really running with and at the same time confirms the module is an endpoint. A node serving urls keeps them until it stops answering, so a redeployment that changes the address re-discovers instead of reporting the previous release. A node serving none is asked again every 30 seconds, an application joins the distribution before its endpoint is up. Risk assessment Impact: the DeployEx card and every connected application card gain a URLs block with links opening in a new tab. Applications serving no Phoenix endpoint, and those not connected over OTP distribution, render exactly as before. Blast radius: Deployer.Status struct, Deployer.Status.Application, the new Deployer.Status.Endpoints, and the two dashboard cards plus a shared component. The deployment engine, hot upgrades, the watchdog, certificates and notifications are untouched. Regression risk: low. Discovery is behind the existing Foundation.Rpc adapter with a one second timeout, every failure path returns an empty list, and the result is cached so the one second status sweep does not add per-tick rpc load. Hot upgrade note: this adds a field to %Deployer.Status{}, which the status GenServer holds in its state. The list is rebuilt every second so it self heals, but a LiveView rendering during the swap can hit a KeyError on the old struct. Rollback: plain commit revert. No data or config migration.
Keep the per-process caching in Deployer.Status.Application via the existing cache_in_process macro, and move the RPC calls into their own module mirroring the recent Deployer.Status.Endpoints split. Risk assessment Impact: no functional change; the same otp/elixir/phoenix versions are still reported, only fetched through a dedicated module. Blast radius: Deployer.Status.Application and the new Deployer.Status.Versions. Everything else is untouched. Regression risk: low. The RPC behaviour, timeout and error handling are preserved. Rollback: plain commit revert.
Endpoints.urls/1 now returns {:ok, urls} | :error. node_urls/2 uses the
existing cache_in_process macro, so reachable nodes cache their discovered
urls and unreachable nodes are retried on the next sweep. This removes the
30-second recheck that kept asking nodes with no endpoint for urls.
Risk assessment
Impact: a node that serves no Phoenix endpoint now caches an empty list of
urls instead of being re-asked every 30 seconds. Served urls are still
dropped as soon as the node stops answering or is redeployed.
Blast radius: Deployer.Status.Endpoints, Deployer.Status.Application and the
endpoints test. The version/favicon caching is untouched.
Regression risk: low. The RPC success/failure signal is preserved, and the
cache is invalidated when the connection drops.
Rollback: plain commit revert.
Put the deployex Resource Monitoring section back at the bottom of the card and keep the served URLs block where it was added. Also make node_urls not cache an empty list. The endpoint discovery can run before the endpoint itself is ready, so an empty result is treated as not-yet-settled and retried on the next sweep. Once a URL is found it is cached as before. Risk assessment Impact: the deployex card renders its Resource Monitoring at the bottom again, and deployex/child app URLs are discovered as soon as the endpoint reports a url. Blast radius: Deployer.Status.Application, DeployexWeb deployex card, and the endpoints cache behaviour. No other features are affected. Regression risk: low. The cached URLs are still dropped on disconnect; empty results now retry rather than being frozen as `[]`. Rollback: plain commit revert.
thiagoesteves
force-pushed
the
thiagoesteves/extract-application-urls
branch
from
August 13, 2026 17:50
4772f30 to
8f4b483
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
The dashboard reported the configured port of an application but not the address it actually answers on.
Phoenix registers the supervisor of every endpoint under the endpoint module name itself (
Phoenix.Endpoint.Supervisorstarts withname: mod), so the registered names of a node already list its endpoints. The newDeployer.Status.Endpointstakes one:erlang.registered/0RPC per node, keeps the module atoms following the*.Endpointconvention, and asks each forurl/0. That resolves scheme, host and port from the configuration the application is really running with, and at the same time confirms the module is genuinely an endpoint. Roughly two RPCs per node, and every failure path returns an empty list.Caching in
Deployer.Status.Application, alongside the existing version lookups:Discovery is gated on
otp == :connected, so it never fires against a node that cannot answer. The one second status sweep adds no per-tick RPC load.DeployEx reports its own URL through the same code path.
Limitations
Only endpoints following the
*.Endpointnaming convention are found, which covers everythingmix phx.newgenerates. Applications serving no Phoenix endpoint (Erlang, Gleam, or raw cowboy/ranch) report no URL and render exactly as before.Risk assessment
Impact: the DeployEx card and every connected application card gain a URLs block with links opening in a new tab. Applications serving no Phoenix endpoint, and those not connected over OTP distribution, render exactly as before.
Blast radius: the
Deployer.Statusstruct,Deployer.Status.Application, the newDeployer.Status.Endpoints, the two dashboard cards and one shared component. The deployment engine, hot upgrades, the watchdog, certificates and notifications are guaranteed untouched.Regression risk: low. Discovery goes through the existing
Foundation.Rpcadapter with a one second timeout, every failure path returns[], and results are cached.Hot upgrade note: this adds a field to
%Deployer.Status{}, a struct held in the status GenServer state, which is check 6 in the hot upgrade rules. The list is rebuilt every second so it self heals, but a LiveView rendering during the swap can hit aKeyError.Rollback: plain commit revert. No data or config migration.
Verification
Compile
--warnings-as-errors,mix format --check-formatted,mix credo --strict,mix dialyzerand the full suite all pass. Verified end to end in the local dev app, where DeployEx discovered its ownhttp://localhost:5001through the same code path used for remote nodes. The remote hop itself was not exercised locally, the dev app releases are not connecting over distribution.🤖 Generated with Claude Code