Skip to content

Single-Domain Deployment Mode for Relaticle #69

@ManukMinasyan

Description

@ManukMinasyan

A Docker/self-hosted user cannot deploy Relaticle due to the hardcoded subdomain architecture. Current setup requires app.{domain} subdomain (e.g., app.crm.example.com), which creates issues for:

  1. SSL certificates — Wildcard certs (*.example.com) only cover one level, not app.crm.example.com
  2. Internal networks — Can't use Let's Encrypt for internal DNS; requires manual cert management
  3. Enterprise environments — Many orgs have DNS policies preventing arbitrary subdomain creation

Current hardcoded implementation in AppPanelProvider.php:

->domain('app.'.parse_url((string) config('app.url'))['host'])

Research Objectives

Find the 3 best approaches to allow Relaticle to run on a single domain for self-hosted/Docker deployments while maintaining the current subdomain architecture as default for SaaS.


Proposed Solutions to Research

Solution 1: Path-Based Routing (Single Domain Mode)

Concept: Use ->path('app') instead of ->domain('app.xxx') when env var is set.

Example:

  • Current: https://crm.example.com (landing) + https://app.crm.example.com (CRM)
  • New: https://crm.example.com (landing) + https://crm.example.com/app (CRM)

Research needed:

  • How does Filament 4 handle ->path() vs ->domain()?
  • Impact on MacroServiceProvider.php URL helpers (getAppUrl, getPublicUrl)
  • Impact on authentication redirects in routes/web.php
  • Session/cookie handling across path vs subdomain
  • Asset URL generation (ASSET_URL implications)
  • Livewire/Alpine.js routing compatibility

Filament docs reference:

// From Filament docs
->path('app')  // Makes panel available at domain.com/app
->path('')     // Makes panel available at root

Solution 2: Configurable Domain via Environment Variable

Concept: Allow full override of the app panel domain via env var.

Example .env:

# Option A: Use subdomain (current default)
APP_URL=https://crm.example.com
# Results in: app.crm.example.com

# Option B: Explicit domain override
APP_URL=https://example.com
APP_PANEL_DOMAIN=crm.example.com
# Results in: crm.example.com (no subdomain prefix)

Research needed:

  • How to conditionally apply ->domain() in AppPanelProvider.php
  • Landing page routing when app panel is on same domain
  • Conflict resolution between public routes and panel routes
  • How other Filament multi-panel apps handle this

Implementation sketch:

$appDomain = env('APP_PANEL_DOMAIN');
if ($appDomain) {
    $panel->domain($appDomain);
} else {
    $panel->domain('app.'.parse_url((string) config('app.url'))['host']);
}

Solution 3: Hybrid Mode (Domain OR Path)

Concept: Single env var to switch between subdomain and path-based routing.

Example .env:

# Subdomain mode (default, current behavior)
RELATICLE_ROUTING_MODE=subdomain

# Path mode (single domain)
RELATICLE_ROUTING_MODE=path

Research needed:

  • Full impact analysis on both routing modes
  • URL generation throughout the app (url()->getAppUrl())
  • Email links, password resets, team invitations
  • OAuth callback URLs
  • How to handle the landing page in path mode (same domain conflict)

Implementation sketch:

$routingMode = env('RELATICLE_ROUTING_MODE', 'subdomain');

if ($routingMode === 'path') {
    $panel->path('app');
} else {
    $panel->domain('app.'.parse_url((string) config('app.url'))['host']);
}

Additional Research Items

Trusted Proxies Support

Laravel 12 does NOT natively read TRUSTED_PROXIES from env. Need to implement in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $proxies = env('TRUSTED_PROXIES');
    
    if ($proxies === '*') {
        $middleware->trustProxies(at: '*');
    } elseif ($proxies) {
        $middleware->trustProxies(at: explode(',', $proxies));
    }
})

Research: Confirm this works with Laravel 12 + Filament 4. Test with Caddy, Nginx, Traefik reverse proxies.


Files to Modify

  1. app/Providers/Filament/AppPanelProvider.php — Panel domain/path config
  2. app/Providers/MacroServiceProvider.php — URL helper macros
  3. bootstrap/app.php — Trusted proxies middleware
  4. routes/web.php — Public route redirects
  5. .env.example — Document new env vars
  6. docker-compose.prod.yml — Add new env vars
  7. Documentation — Self-hosting guide updates

Test Scenarios

Scenario APP_URL Mode Expected Landing Expected App
SaaS (current) https://relaticle.com subdomain relaticle.com app.relaticle.com
Self-hosted subdomain https://crm.company.com subdomain crm.company.com app.crm.company.com
Self-hosted single domain https://crm.company.com path crm.company.com crm.company.com/app
Internal network http://192.168.1.100:8080 path 192.168.1.100:8080 192.168.1.100:8080/app

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions