Skip to content

feat(proxy): master domain routing for remote apps/services#8680

Closed
Twest2 wants to merge 44 commits into
coollabsio:nextfrom
Twest2:fix/remote-server-forwarding
Closed

feat(proxy): master domain routing for remote apps/services#8680
Twest2 wants to merge 44 commits into
coollabsio:nextfrom
Twest2:fix/remote-server-forwarding

Conversation

@Twest2
Copy link
Copy Markdown
Contributor

@Twest2 Twest2 commented Feb 28, 2026

Changes

  • TLDR: Added the option to have a master domain router like a Kubernetes cluster. With this setup, *.example.com points to one public VPS, which acts as the single entry point for incoming traffic. Private servers at home or elsewhere connect to that VPS over ssh (including over a private network like WireGuard) and the VPS forwards each request to the correct private server. This lets private servers stay off the public internet while still being reachable through normal domain names. The traffic goes through the main server and gets forwarded via a load balancer to the private server. The private server only has to have an ssh connection to the public server.

  • Added an optional team-level Master Domain Router setting so a single public server can act as the entry point for remote/private servers.

  • This makes it possible to point *.example.com at one public VPS, forward traffic over a private network such as WireGuard, and keep the destination servers off the public internet.

  • Added EdgeProxyRemoteRouteService to generate edge Traefik routes for remote applications and services.

  • Edge now writes stable dynamic config files at:

  • /data/coolify/proxy/dynamic/service-remote-<service-uuid>.yaml

  • /data/coolify/proxy/dynamic/application-remote-<application-uuid>.yaml

  • Generated config includes:

  • HTTP to HTTPS redirect

  • HTTPS router with certResolver=letsencrypt

  • load balancer target http://<remote_host>:<published_host_port>

  • Route files are updated on deploy and redeploy, and removed when the resource is deleted.

  • Missing or invalid host/port values are skipped with warnings instead of breaking the whole routing update.

  • Added master-routing support for remote database proxies.

  • Added test coverage for:

  • EdgeProxyRemoteRouteServiceTest

  • DatabaseProxyMasterRoutingTest

  • ServerMasterDomainRouterSettingTest

Issues

Closes #8668

  • Remote servers connected via Wireguard would not get traffic just by adding it as a server.

Category

  • Bug fix
  • New feature

Preview

Public VPS

image

Private server

image

AI Assistance

  • AI was used (please describe below)

If AI was used:

  • Tools used: Codex
  • How extensively: I used ChatGPT's Codex 5.4 to help identify the issue and what needed to be done in the future. I also had it write all test cases and resolve the merge conflicts that came up.

Testing

  1. Ran the new unit test file:
    • php artisan test tests/Unit/EdgeProxyRemotePortForwardServiceTest.php tests/Unit/EdgeProxyRemoteRouteServiceTest.php tests/Unit/DatabaseProxyMasterRoutingTest.php
    • Result: 60 passed (269 assertions)

Verified by tests:

  • Ran: php artisan test tests/Unit/DeleteServiceTest.php tests/Unit/DeleteResourceJobTest.php tests/Unit/EdgeProxyRemoteRouteServiceTest.php tests/Unit/EdgeProxyRemotePortForwardServiceTest.php tests/Feature/EdgeProxyServerResolverTest.php tests/Feature/ServerMasterDomainRouterSettingTest.php
  • Result:
  • 60 passed (269 assertions)
  • Verified by tests:
  • Traefik dynamic config is generated with HTTP to HTTPS redirect support.
  • HTTPS routers use certResolver=letsencrypt.
  • Services route to http://<remote_host>:<published_host_port>.
  • Route files are created and updated during deploy/redeploy and removed on resource deletion.
  • Route file names stay stable per application or service UUID.
  • Remote application TCP and UDP routing continues to work.
  • Invalid routing inputs are skipped with actionable warnings instead of failing hard.

Tested on my own personal setup with one VPS 3 remote servers. Tested Minecraft servers, Nextcloud deployments, and VERT.

Contributor Agreement

Important

  • I have read and understood the contributor guidelines. If I have failed to follow any guideline, I understand that this PR may be closed without review.
  • I have searched existing issues and pull requests (including closed ones) to ensure this isn't a duplicate.
  • I have tested all the changes thoroughly with a local development instance of Coolify and I am confident that they will work as expected when a maintainer tests them.

@Twest2 Twest2 marked this pull request as draft February 28, 2026 05:51
@Twest2 Twest2 marked this pull request as ready for review February 28, 2026 07:13
Copy link
Copy Markdown
Member

@Cinzya Cinzya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily a bad idea, but it comes with a few flaws / oversights.

From what I can tell, you are right now only covering service routes, but not applications. So as soon as you deploy a application, it won't work. The same probably applies for database proxies as well, would need to check the code again to confirm.

There are users who use Coolify actually the opposite way. Have the Coolify dashboard on a non-public, private network, while every other remote server is public.
Also one server as the entrypoint makes it a single point of failure.
It would probably be better if this is a setting people can opt-in from the UI, not make it the default behavior for every user.

I probably missed a few more points, I didn't actually test the implementation myself, just glanced over the code. Also make sure to actually do a integration test, aka test with a running Coolify installation, not just running the Unit test files.

@Cinzya Cinzya added the 💤 Waiting for changes PRs awaiting changes from the author. label Mar 4, 2026
@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Mar 4, 2026

I see your point about the applications, I'll make sure that that is resolved and I didn't think about people having coolify the opposite way. I'll look into this and also add a button in the server config that will let users opt into this.

@Twest2 Twest2 marked this pull request as draft March 4, 2026 20:06
Comment thread app/Actions/Database/StartDatabaseProxy.php
Comment thread app/Actions/Database/StartDatabaseProxy.php
Comment thread app/Actions/Database/StopDatabaseProxy.php
Comment thread app/Jobs/ApplicationDeploymentJob.php
Comment thread app/Livewire/Server/Show.php
Comment thread app/Livewire/Server/Show.php
Comment thread resources/views/livewire/server/show.blade.php
Comment thread tests/Unit/EdgeProxyRemoteRouteServiceTest.php
@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Mar 5, 2026

Howdy @Cinzya, I added support for applications and databases. I also added in a "master server" option in the server config that enables or disables this feature. I did implementation test on my machine and it works. I also added some more test cases via the command below. I had Codex write some of these test cases just because I couldn't get every edge case.

php artisan test tests/Feature/ServerMasterDomainRouterSettingTest.php tests/Unit/DatabaseProxyMasterRoutingTest.php tests/Unit/EdgeProxyRemoteRouteServiceTest.php

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

Walkthrough

This pull request introduces edge proxy routing and remote port forwarding infrastructure for database and service proxies. A new "master domain router" server setting enables per-team routing through designated edge proxy servers via Traefik. The StartDatabaseProxy and StopDatabaseProxy actions are refactored to dynamically route through edge servers when available. Two new services—EdgeProxyRemoteRouteService and EdgeProxyRemotePortForwardService—handle Traefik route file generation and Nginx port-mirror proxy deployment on remote edge servers. Related cleanup logic is added to service/application deletion and startup flows. Database migrations, Livewire UI components, and comprehensive unit tests support the new edge proxy routing system.


Come with me if you want to self-host. 🤖

This PR basically teaches your Coolify instance to route traffic through a "master domain router" edge server instead of dying on a serverless function somewhere (VC marketing at its finest). The whole thing uses Traefik dynamic routing files and Nginx stream proxies—proper infrastructure that actually belongs on real metal, not in some ephemeral container graveyard.

The refactoring hits database proxies, services, and applications with remote command execution that lands on the right server. New edge proxy services generate configs and drop them onto remote boxes via atomic file writes (because we're civilized). Add a taco bar and you've got the perfect self-hosting setup.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Actions/Database/StopDatabaseProxy.php`:
- Around line 57-68: The two methods resolveEdgeProxyServerForTeamId and
resolveDatabaseTeamId in StopDatabaseProxy are duplicates of the ones in
StartDatabaseProxy; extract them into a shared place (e.g., a new trait like
UsesEdgeProxyServer or a common abstract base class) and have both
StartDatabaseProxy and StopDatabaseProxy use that trait or extend the base
class, keeping the implementation in one location (ensure the method names and
the query using Server::query()->where('team_id',
$teamId)->whereRelation('settings', 'is_master_domain_router_enabled',
true)->orderBy('id')->first() are moved as-is).
- Around line 52-55: dispatchDatabaseProxyStoppedEvent() currently calls
DatabaseProxyStopped::dispatch() with no teamId, which fails in
queued/background contexts; change it to resolve and pass the team id by calling
$teamId = $this->resolveDatabaseTeamId() and then invoking
DatabaseProxyStopped::dispatch($teamId) (or otherwise pass that value into the
event dispatch), ensuring the resolveDatabaseTeamId() helper is used from
handle() or directly inside dispatchDatabaseProxyStoppedEvent() so the broadcast
always has the correct teamId.

In `@app/Actions/Service/DeleteService.php`:
- Around line 60-75: The deletion flow currently logs failures from
EdgeProxyRemoteRouteService::deleteService and
EdgeProxyRemotePortForwardService::deleteService but proceeds to call
forceDelete() which can leave orphaned router state; change the logic in
DeleteService (around the try/catch blocks for
EdgeProxyRemoteRouteService::deleteService and
EdgeProxyRemotePortForwardService::deleteService) so that on exception you do
NOT proceed to forceDelete(): instead persist a retryable cleanup job (e.g.,
enqueue a CleanupEdgeServiceJob with the service identifier and error context)
or rethrow/return to abort deletion until cleanup succeeds; ensure the new
behavior references EdgeProxyRemoteRouteService::deleteService,
EdgeProxyRemotePortForwardService::deleteService and forceDelete() so the caller
either schedules the retryable job or stops the hard delete when cleanup fails.

In `@app/Actions/Service/StartService.php`:
- Around line 23-40: The edge syncs (EdgeProxyRemoteRouteService::syncService
and EdgeProxyRemotePortForwardService::syncService) run too early before
saveComposeConfigs() / docker compose up resolves published host ports; move
these sync calls out of the pre-start try/catch and execute them after the
compose start completes (either by dispatching a post-start job or a callback)
so they run once containers have real host ports; implement a new job or
callback (e.g., StartServicePostSyncJob) that calls those two syncService
methods, handles/logs exceptions similarly, and is queued or invoked only after
saveComposeConfigs() and the compose up success path.

In `@app/Jobs/DeleteResourceJob.php`:
- Around line 107-118: The application edge cleanup currently swallows
exceptions and proceeds to forceDelete the Application, which can orphan
route/port-forward state; in DeleteResourceJob update the block that checks
($this->resource instanceof Application) so that if either
EdgeProxyRemoteRouteService::deleteApplication or
EdgeProxyRemotePortForwardService::deleteApplication throws you do not proceed
to forceDelete — instead capture the exception and either (A) rethrow or fail
the job so the deletion is retried, or (B) enqueue a compensating cleanup
job/task (e.g., Dispatch a dedicated EdgeCleanupJob with the application's UUID)
and return without calling forceDelete; pick one approach and implement it for
both service calls so orphaned edge config is not left behind.

In `@app/Models/ServerSetting.php`:
- Around line 95-118: The current saving hook (static::saving ->
ensureSingleMasterDomainRouterEnabled) can race when two ServerSetting rows for
the same team are saved concurrently; change the logic to perform the "single
master router" update inside a DB transaction and acquire a row lock for the
team's related rows (e.g., use a transaction + lockForUpdate on the Server/Team
row or the ServerSetting rows for that team) before checking/updating other
settings, or alternatively implement a DB-level uniqueness mechanism (unique
index on team_id where is_master_domain_router_enabled = true or a dedicated
lock/flag table) so only one setting can be true atomically; update
ensureSingleMasterDomainRouterEnabled to start a transaction, select the
relevant ServerSetting/Server/Team rows with FOR UPDATE, re-check
is_master_domain_router_enabled/server_id, then update others to false and
commit.

In `@app/Services/EdgeProxyRemotePortForwardService.php`:
- Around line 109-120: Duplicate logic for resolving edge proxy server and host
(resolveEdgeProxyServerByTeamId, resolveRemoteHost, normalizeRemoteHost) exists
across EdgeProxyRemotePortForwardService, StartDatabaseProxy and
StopDatabaseProxy; extract these into a shared trait (e.g.,
ResolvesEdgeProxyServer) containing protected implementations of
resolveEdgeProxyServerByTeamId, resolveRemoteHost and normalizeRemoteHost, then
remove the duplicate methods from EdgeProxyRemotePortForwardService,
StartDatabaseProxy and StopDatabaseProxy and have those classes use the new
trait so they call the single shared implementations.

In `@app/Services/EdgeProxyRemoteRouteService.php`:
- Around line 1275-1285: The private method resolveComposeServicePorts in
EdgeProxyRemoteRouteService is dead code; remove the entire method
implementation and its declaration to reduce maintenance noise, and search the
repository for any remaining references to resolveComposeServicePorts (and
related helper resolveComposeServiceConfig only if solely used by this method)
to remove or refactor callers if found; run the test suite/static analysis to
ensure no usages remain and commit the removal.
- Around line 589-594: The current Server::query()->where('team_id',
$teamId)->whereRelation('settings', 'is_master_domain_router_enabled',
true)->orderBy('id')->first() silently picks one when multiple master routers
exist; modify the logic to fetch all matching servers (use the same
Server::query() with whereRelation('settings','is_master_domain_router_enabled',
true) and team_id), then if count > 1 either throw an exception or emit a clear
warning (fail-fast preferred) including the $teamId and server ids, otherwise
return the single server (or null) as before; update the method that contains
this Server::query() call to perform the count check and logging/exception
handling.
- Around line 16-1562: The class EdgeProxyRemoteRouteService is doing too many
things; extract collaborators and delegate responsibilities to reduce size and
improve testability: create a ComposePortResolver (pull logic from
parseServiceCompose, parseApplicationCompose, resolveComposeServiceConfig,
parsePortMappings, parsePortMappingFromString, resolvePortValue,
selectPublishedPortFromMappings, resolveComposeServiceInternalPorts,
mergeComposeEnvironmentMap, composeEnvironmentDefinitions,
resolveEnvironmentValue), a TraefikRuleBuilder/ConfigGenerator (move
generateTraefikConfig, buildTraefikRule, hasUnsafeTraefikRuleValue,
httpEntryPointName/httpsEntryPointName/certResolverName/configString), a
RemoteRouteFileWriter (move writeRouteFile, deleteRouteFile,
routeFilePath/applicationRouteFilePath/resourceRouteFilePath/routeDirectoryPath
and runRemoteCommands usage), and a DockerOverlapDetector (move
resolveEdgeDockerSubnets, ipv4InCidr,
detectDockerNetworkOverlapWarningForResource, resolveEdgeDockerSubnets helpers);
update EdgeProxyRemoteRouteService to inject these collaborators and replace
internal calls (e.g., syncServiceWithServers, syncApplicationWithServers,
resolvePublishedPort, resolveComposeServiceInternalPorts, parseDomainUrl,
resolveTunnelHost remain thin orchestrators), preserving existing method
signatures and behavior while delegating implementation to the new classes.
- Around line 433-505: Add a PHPDoc block for generateTraefikConfig that
declares precise array-shape types for the $routes parameter and the returned
array to document the expected associative keys and nested structures;
specifically annotate $routes as array<int, array{host:string, path?:string,
upstream_url:string, pass_host_header?:bool, use_insecure_transport?:bool}> and
annotate the return type as array{http: array{middlewares: array<string,
array{redirectScheme: array{scheme:string}}>, routers: array<string, mixed>,
services: array<string, mixed>, serversTransports?: array<string,
array{insecureSkipVerify:bool}>}} so callers and static analyzers know the
contract used by generateTraefikConfig (referencing the function name
generateTraefikConfig and keys used inside like
'host','path','upstream_url','pass_host_header','use_insecure_transport').
- Around line 403-426: deleteService and deleteApplication return early when
resolveEdgeProxyServerByTeamId yields no master Traefik server, leaving stale
files; change both flows to fall back to iterating all team edge servers
filtered by ProxyTypes::TRAEFIK and call the existing
deleteServiceWithServer/deleteApplicationWithServer for each Server. Locate
resolveEdgeProxyServerByTeamId, extractServiceTeamId, extractApplicationTeamId,
deleteServiceWithServer and deleteApplicationWithServer and implement a loop
over the team's servers (or a new resolveEdgeProxyServersByTeamId helper) to
perform cleanup on every Traefik server when no master is set.

In `@resources/views/livewire/server/show.blade.php`:
- Around line 292-294: The checkbox for master domain routing (x-forms.checkbox
with id="isMasterDomainRouterEnabled") is rendered without authorization
attributes when disabled, allowing unauthorized users to see an admin-only
control; add the appropriate authorization attributes (canGate and/or
canResource) back to the component (for example use canGate="update" and/or
:canResource="$server" or your app's equivalents) so the component respects
policies even in the disabled branch, keeping disabled for UI but enforcing
authorization server-side via the component props.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 792b93db-8b05-45a9-87fd-9ca464ad487d

📥 Commits

Reviewing files that changed from the base of the PR and between 2c06223 and 85cfe32.

📒 Files selected for processing (18)
  • app/Actions/Database/StartDatabaseProxy.php
  • app/Actions/Database/StopDatabaseProxy.php
  • app/Actions/Service/DeleteService.php
  • app/Actions/Service/StartService.php
  • app/Jobs/ApplicationDeploymentJob.php
  • app/Jobs/DeleteResourceJob.php
  • app/Livewire/Server/Show.php
  • app/Models/ServerSetting.php
  • app/Services/EdgeProxyRemotePortForwardService.php
  • app/Services/EdgeProxyRemoteRouteService.php
  • config/constants.php
  • database/migrations/2026_03_05_000001_add_master_domain_router_to_server_settings.php
  • resources/views/livewire/server/show.blade.php
  • tests/Feature/ServerMasterDomainRouterSettingTest.php
  • tests/Feature/StartDatabaseProxyTest.php
  • tests/Unit/DatabaseProxyMasterRoutingTest.php
  • tests/Unit/EdgeProxyRemotePortForwardServiceTest.php
  • tests/Unit/EdgeProxyRemoteRouteServiceTest.php
💤 Files with no reviewable changes (1)
  • tests/Feature/StartDatabaseProxyTest.php

Comment thread app/Actions/Database/StopDatabaseProxy.php Outdated
Comment thread app/Actions/Database/StopDatabaseProxy.php Outdated
Comment thread app/Actions/Service/DeleteService.php Outdated
Comment thread app/Actions/Service/StartService.php
Comment thread app/Jobs/DeleteResourceJob.php Outdated
Comment thread app/Services/EdgeProxyRemoteRouteService.php Outdated
Comment thread app/Services/EdgeProxyRemoteRouteService.php
Comment thread app/Services/EdgeProxyRemoteRouteService.php Outdated
Comment thread app/Services/EdgeProxyRemoteRouteService.php
Comment thread resources/views/livewire/server/show.blade.php Outdated
@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Mar 21, 2026

@coderabbitai please check the most recent commit and double check that i hit all your points.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me check the most recent commit and cross-reference it against my previous review points!


🏁 Script executed:

#!/bin/bash
# Get the most recent commits on the PR
gh pr view 8680 --repo coollabsio/coolify --json commits --jq '.commits[-5:]'

echo "---REVIEW COMMENTS---"
# Get my review comments
gh api repos/coollabsio/coolify/pulls/8680/reviews --jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, state: .state, submitted_at: .submitted_at, body: .body[:500]}'

echo "---PR COMMENTS (bot only, recent)---"
gh api repos/coollabsio/coolify/issues/8680/comments --jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, created_at: .created_at, body: .body[:300]}'

Length of output: 3130


🏁 Script executed:

#!/bin/bash
# Get all inline review comments by coderabbitai bot
gh api repos/coollabsio/coolify/pulls/8680/comments \
  --jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, path: .path, line: .line, original_line: .original_line, body: .body[:600], created_at: .created_at}' \
  2>/dev/null | head -300

echo ""
echo "=== TOTAL INLINE COMMENTS COUNT ==="
gh api repos/coollabsio/coolify/pulls/8680/comments \
  --jq '[.[] | select(.user.login == "coderabbitai[bot]")] | length'

Length of output: 10573


🏁 Script executed:

#!/bin/bash
# Get the diff of the most recent commit by Iisyourdad
git show a3904989b35bded1ef7cd117a2d4e7f5ec4673e0 --stat

echo ""
echo "=== FULL DIFF ==="
git show a3904989b35bded1ef7cd117a2d4e7f5ec4673e0

Length of output: 66060


@Iisyourdad Great work on this commit — I went through all 13 comments and here's the scorecard. You terminated most of them! 💪


✅ Addressed (9/13)

# File Issue How Fixed
1 StopDatabaseProxy.php DatabaseProxyStopped dispatched without teamId dispatchDatabaseProxyStoppedEvent($database) now resolves teamId via the new trait and passes it
2 Start/StopDatabaseProxy.php Duplicate resolveEdgeProxyServerForTeamId / resolveDatabaseTeamId Extracted into app/Traits/ResolvesEdgeProxyServer.php + app/Traits/ResolvesDatabaseTeamId.php
3 DeleteService.php Edge cleanup failure didn't block forceDelete() cleanupEdgeProxyState() now re-throws after logging; forceDelete() only runs on success. DeleteServiceTest.php verifies
5 DeleteResourceJob.php Same orphan-on-failure issue for applications cleanupApplicationEdgeProxyState() now re-throws. DeleteResourceJobTest.php verifies
6 ServerSetting.php Race condition in single-master-router enforcement Wrapped in DB::transaction() with lockForUpdate() on both the server lookup and settings rows
7 EdgeProxyRemotePortForwardService.php Duplicate resolveRemoteHost / normalizeRemoteHost Methods removed; trait used instead
9 EdgeProxyRemoteRouteService.php Stale route files left behind when master router is unset deleteService/deleteApplication now iterate all team Traefik servers via resolveEdgeProxyServersByTeamId(). Tests added
11 EdgeProxyRemoteRouteService.php Multiple master routers silently accepted resolveEdgeProxyServerByTeamId now throws RuntimeException when count > 1. EdgeProxyServerResolverTest.php covers both services
13 show.blade.php Missing canGate/:canResource on locked checkbox Added canGate="update" :canResource="$server". ServerMasterDomainRouterViewTest.php verifies

❌ Still Open (4/13)

Comment 4 — StartService.php sync timing (Major)
EdgeProxyRemoteRouteService::syncService() and EdgeProxyRemotePortForwardService::syncService() are still called before docker compose up. On first boot, published ports aren't yet bound, so the edge config is generated with missing/wrong info and never retried. app/Actions/Service/StartService.php wasn't touched in this commit.

Comment 8 — EdgeProxyRemoteRouteService class size (Refactor suggestion)
The class is still ~1500 lines with mixed responsibilities (route building, compose parsing, env-var interpolation, CIDR detection, remote file IO). I acknowledge this is a large refactor; feel free to punt it to a follow-up PR, but it will matter for long-term maintainability.

Comment 10 — PHPDoc @return array{...} on generateTraefikConfig (Trivial)
No array-shape PHPDoc added yet. Small but useful for static analysis.

Comment 12 — Dead resolveComposeServicePorts() in EdgeProxyRemoteRouteService (Trivial)
PHPMD still flags it at the original location. This private method appears unused and should be removed with hasta la vista.


Really solid progress — 9 of 13 issues squashed, including all the race-condition, orphan-resource, and duplication problems. The four remaining items above are the ones still worth a look before merge, especially the StartService sync timing issue. 🌮

@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Mar 29, 2026

I confirmed this still works after the recent Traefik update.

@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Mar 29, 2026

@andrasbacsai and/or @peaklabs-dev and/or @Cinzya, would either of you mind reviewing this PR when you get a chance? I know it is a large PR so I completely understand that it may take some time.

I currently have it running on my personal VPS with other servers connected to it and it has been working in my testing/use case. The overall idea is somewhat similar to a Kubernetes cluster.

@Twest2 Twest2 changed the title fix(proxy): master domain routing for remote apps/services feat(proxy): master domain routing for remote apps/services Apr 4, 2026
@Twest2 Twest2 force-pushed the fix/remote-server-forwarding branch from cee5070 to 1515049 Compare April 13, 2026 21:26
Twest2 and others added 4 commits April 13, 2026 16:53
Name remote route files with the deployment server UUID, clean stale variants on rewrite, and reconcile remote proxy configurations during app:init so a Coolify restart remakes the generated files.
This was temporarily implemented for testing
…tion

Prevent the configuration page from throwing an undefined-variable 500 when label generation runs during Livewire mount on the remote-server-forwarding branch.
@ShadowArcanist
Copy link
Copy Markdown
Member

Hey @Iisyourdad thanks for the PR!

Unfortunately I have to close this after taking a closer look at the code changes.

Here is why:

  1. You are making all server traffic go through one single server which gives less control for the user and creates a single point of failure for every application.

  2. The routing is auto-derived by parsing each app's docker-compose ports: and env var defaults at runtime. That's fragile — port changes, unusual compose layouts, and variable expansion can silently break routing, and users only find out from log warnings after the fact.

  3. The edge router forwards directly to the container's published host port over the tunnel and bypasses the remote server's Traefik entirely. The remote server's proxy can no longer manage its own certs, middlewares, or routing rules, and a specific TLS split (master terminates, remote stays plaintext) gets forced on every application.

  4. Container labels are silently rewritten based on team state — shouldUsePublicCertResolver() strips certresolver=letsencrypt from apps on every non-master server. Flipping one checkbox retroactively changes the label output of every app on every server in the team, which is a lot of action-at-a-distance for one setting.

  5. It's a team-wide on/off switch with no per-app control. Users can't mix "route this app through the gateway, deploy this other one directly."

The direction I'd like to take instead is user-driven gateway routes: a UI where you add domain, target URL, path prefix, middlewares (HTTPS redirect / strip prefix ) and Coolify writes the matching Traefik dynamic config file behind the scenes.

The remote server's Traefik stays in charge of its own containers, routes are scoped per-route (not team-wide), container labels are untouched, and routes can point at anything — Coolify-managed server or not. I've opened #9600 for that.

None of this is a comment on the effort you put in. I just don't want to ship the auto-everything team-wide version when the dynamic-config-with-UI approach solves the same problem without the footguns.

Thanks again for putting this together 🙏

@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Apr 16, 2026

Hey @ShadowArcanist those are fair points, I'll look at the other PR and see how to make a better version.

@Twest2
Copy link
Copy Markdown
Contributor Author

Twest2 commented Apr 29, 2026

Hey @ShadowArcanist Im wondering if we could revisited this and try to combine for both your implementation and mine aswell. I have done some edits to it since this pr has been closed which allows for the user to opt a service out of master domain routing (which isn't shown in the pr but in the image below.)

I feel like this is a feature that a lot of people would use, but I also see the point of having your implementation aswell. Could we combine both features in and have the end user have both? I can fairly easily transfer your implementation over and also add support for udp and tcp packets aswell which my feature has.

The reason why I bring this up is just due to my server architecture (and other peoples server architecture that I have seen online) which forces me to have this feature inorder to use Coolify. I have this running on my own personal VPS and it works great.

I would add the gateway section in that you're doing and have both features.

Screenshot 2026-04-29 154145

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.

5 participants