Skip to content

feat(deployer): show the URLs each connected application is serving on - #293

Merged
thiagoesteves merged 4 commits into
mainfrom
thiagoesteves/extract-application-urls
Aug 13, 2026
Merged

feat(deployer): show the URLs each connected application is serving on#293
thiagoesteves merged 4 commits into
mainfrom
thiagoesteves/extract-application-urls

Conversation

@thiagoesteves

Copy link
Copy Markdown
Owner

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.Supervisor starts with name: mod), so the registered names of a node already list its endpoints. The new Deployer.Status.Endpoints takes one :erlang.registered/0 RPC per node, keeps the module atoms following the *.Endpoint convention, and asks each for url/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:

  • a node serving URLs keeps them until it stops answering, so a redeployment that changes the address re-discovers rather than reporting the previous release
  • a node serving none is asked again every 30 seconds, since an application joins the distribution before its endpoint is up

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 *.Endpoint naming convention are found, which covers everything mix phx.new generates. 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.Status struct, Deployer.Status.Application, the new Deployer.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.Rpc adapter 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 a KeyError.

Rollback: plain commit revert. No data or config migration.

Verification

Compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, mix dialyzer and the full suite all pass. Verified end to end in the local dev app, where DeployEx discovered its own http://localhost:5001 through 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

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
thiagoesteves force-pushed the thiagoesteves/extract-application-urls branch from c9393a2 to bb7b77d Compare August 13, 2026 12:52
@thiagoesteves thiagoesteves self-assigned this 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.
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
thiagoesteves force-pushed the thiagoesteves/extract-application-urls branch from 4772f30 to 8f4b483 Compare August 13, 2026 17:50
@thiagoesteves
thiagoesteves merged commit f697489 into main Aug 13, 2026
1 check passed
@thiagoesteves
thiagoesteves deleted the thiagoesteves/extract-application-urls branch August 13, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant