diff --git a/backend/src/applications/applications.service.ts b/backend/src/applications/applications.service.ts
index 2887b16..5e7e213 100644
--- a/backend/src/applications/applications.service.ts
+++ b/backend/src/applications/applications.service.ts
@@ -30,6 +30,33 @@ export class ApplicationsService {
private configService: ConfigService,
) {}
+ private toDnsLabel(value: string): string {
+ return (value || '')
+ .toLowerCase()
+ .replace(/[^a-z0-9-]/g, '-')
+ .replace(/-+/g, '-')
+ .replace(/^-|-$/g, '')
+ .slice(0, 63);
+ }
+
+ private async generateRandomSubdomain(baseLabel: string): Promise {
+ const base = this.toDnsLabel(baseLabel) || 'app';
+ const suffixLenBytes = 5; // 10 hex chars
+ const suffixLen = suffixLenBytes * 2;
+
+ for (let attempt = 0; attempt < 10; attempt++) {
+ const suffix = crypto.randomBytes(suffixLenBytes).toString('hex'); // [0-9a-f]
+ const maxBaseLen = 63 - 1 - suffixLen; // base + '-' + suffix
+ const truncatedBase = base.slice(0, Math.max(1, maxBaseLen)).replace(/-+$/g, '');
+ const candidate = `${truncatedBase}-${suffix}`;
+
+ const exists = await this.appsRepository.findOne({ where: { subdomain: candidate } });
+ if (!exists) return candidate;
+ }
+
+ throw new BadRequestException('Failed to generate a unique subdomain. Please try again.');
+ }
+
async create(userId: string, dto: CreateApplicationDto, userRole?: string): Promise {
dto = normalizeCreateApplicationDto(dto);
const productType = dto.productType ?? ProductType.APPLICATION;
@@ -83,7 +110,10 @@ export class ApplicationsService {
? 80
: 3000;
- const subdomain = `${dto.name}-${userId.split('-')[0]}`;
+ const baseLabel = dto.name;
+ const subdomain = customDomain
+ ? `${this.toDnsLabel(baseLabel)}-${this.toDnsLabel(userId.split('-')[0])}`
+ : await this.generateRandomSubdomain(baseLabel);
const platformDomain = this.configService.get('platform.domain') || 'apps.cloudhost.ir';
const app = this.appsRepository.create({
diff --git a/frontend/src/app/dashboard/deploy/page.tsx b/frontend/src/app/dashboard/deploy/page.tsx
index 9d4bc8c..412cc1a 100644
--- a/frontend/src/app/dashboard/deploy/page.tsx
+++ b/frontend/src/app/dashboard/deploy/page.tsx
@@ -2086,7 +2086,7 @@ export default function DeployPage() {
Type: CNAME
- Value: {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir
+ Value: your app Platform Domain (after creation)
4. If using a root domain (without www), use a registrar that supports CNAME flattening (e.g. Cloudflare).
@@ -2096,20 +2096,8 @@ export default function DeployPage() {
{/* CNAME target copy box */}
CNAME Target:
-
-
- {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir
-
-
+
+ After the app is created, open its page and copy the “Platform Domain” value shown in the Domains/Settings section.
@@ -3055,7 +3043,7 @@ export default function DeployPage() {
Required CNAME record:
- {customDomainInput} → {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir
+ {customDomainInput} → your app Platform Domain (after creation)